main.rs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. // Hides the cmd.exe terminal on Windows.
  19. // Enable this when making release builds.
  20. #![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
  21. use clap::Parser;
  22. use darkfi::system::CondVar;
  23. use std::sync::{Arc, OnceLock};
  24. #[macro_use]
  25. extern crate tracing;
  26. #[cfg(target_os = "android")]
  27. mod android;
  28. mod app;
  29. mod build_info;
  30. mod error;
  31. mod expr;
  32. mod gfx;
  33. mod logger;
  34. mod mesh;
  35. #[cfg(feature = "enable-netdebug")]
  36. mod net;
  37. mod plugin;
  38. mod prop;
  39. mod pubsub;
  40. //mod py;
  41. //mod ringbuf;
  42. mod scene;
  43. mod shape;
  44. mod text;
  45. mod ui;
  46. mod util;
  47. use crate::{
  48. app::{App, AppPtr},
  49. gfx::EpochIndex,
  50. prop::{Property, PropertySubType, PropertyType},
  51. scene::{CallArgType, SceneNode, SceneNodeType},
  52. util::AsyncRuntime,
  53. };
  54. #[cfg(feature = "enable-netdebug")]
  55. use net::ZeroMQAdapter;
  56. #[cfg(feature = "enable-plugins")]
  57. use {
  58. darkfi_serial::{deserialize, Decodable, Encodable},
  59. // Local imports
  60. gfx::Renderer,
  61. prop::{PropertyBool, PropertyStr, Role},
  62. scene::{SceneNodePtr, Slot},
  63. std::io::Cursor,
  64. ui::chatview,
  65. // Global imports
  66. url::Url,
  67. };
  68. // This is historical, but ideally we can fix the entire project and remove this import.
  69. pub use util::ExecutorPtr;
  70. macro_rules! t { ($($arg:tt)*) => { trace!(target: "main", $($arg)*); } }
  71. #[cfg(feature = "enable-plugins")]
  72. macro_rules! d { ($($arg:tt)*) => { trace!(target: "main", $($arg)*); } }
  73. #[cfg(any(feature = "enable-plugins", feature = "enable-netdebug"))]
  74. macro_rules! i { ($($arg:tt)*) => { trace!(target: "main", $($arg)*); } }
  75. fn panic_hook(panic_info: &std::panic::PanicHookInfo) {
  76. error!("panic occurred: {panic_info}");
  77. error!("{}", std::backtrace::Backtrace::force_capture().to_string());
  78. std::process::abort()
  79. }
  80. /// Contains values which persist between app restarts. For example on Android, we are
  81. /// running a foreground service. Everytime the UI restarts main() is called again.
  82. /// However the global state remains intact.
  83. struct God {
  84. _bg_runtime: AsyncRuntime,
  85. _bg_ex: ExecutorPtr,
  86. pub fg_runtime: AsyncRuntime,
  87. _fg_ex: ExecutorPtr,
  88. /// App must fully finish setup() before start() is allowed to begin.
  89. cv_app_is_setup: Arc<CondVar>,
  90. app: AppPtr,
  91. /// This is the main rendering API used to send commands to the gfx subsystem.
  92. /// We have a ref here so the gfx subsystem can increment the epoch counter.
  93. renderer: gfx::Renderer,
  94. /// This is how the gfx subsystem receives messages from the render API.
  95. method_recv: async_channel::Receiver<(gfx::EpochIndex, gfx::GraphicsMethod)>,
  96. /// Publisher to send input and window events to subscribers.
  97. event_pub: gfx::GraphicsEventPublisherPtr,
  98. /// A WorkerGuard for file logging used to ensure buffered logs are flushed
  99. /// to their output in the case of abrupt terminations of a process.
  100. _file_logging_guard: Option<tracing_appender::non_blocking::WorkerGuard>,
  101. }
  102. impl God {
  103. fn new() -> Self {
  104. // Abort the application on panic right away
  105. std::panic::set_hook(Box::new(panic_hook));
  106. let file_logging_guard = logger::setup_logging();
  107. info!(target: "main", "Creating the app");
  108. #[cfg(target_os = "android")]
  109. {
  110. use crate::android::get_appdata_path;
  111. // Workaround for this bug
  112. // https://gitlab.torproject.org/tpo/core/arti/-/issues/999
  113. unsafe {
  114. std::env::set_var("HOME", get_appdata_path().as_os_str());
  115. }
  116. }
  117. let exe_path = std::env::current_exe().unwrap();
  118. let basename = exe_path.parent().unwrap();
  119. std::env::set_current_dir(basename).unwrap();
  120. let bg_ex = Arc::new(smol::Executor::new());
  121. let fg_ex = Arc::new(smol::Executor::new());
  122. let sg_root = SceneNode::root();
  123. let bg_runtime = AsyncRuntime::new(bg_ex.clone(), "bg");
  124. bg_runtime.start();
  125. let fg_runtime = AsyncRuntime::new(fg_ex.clone(), "fg");
  126. let (method_send, method_recv) = async_channel::unbounded();
  127. // The UI actually needs to be running for this to reply back.
  128. // Otherwise calls will just hang.
  129. let renderer = gfx::Renderer::new(method_send);
  130. let event_pub = gfx::GraphicsEventPublisher::new();
  131. let app = App::new(sg_root.clone(), renderer.clone(), fg_ex.clone());
  132. let app2 = app.clone();
  133. let cv_app_is_setup = Arc::new(CondVar::new());
  134. let cv = cv_app_is_setup.clone();
  135. let app_task = fg_ex.spawn(async move {
  136. app2.setup().await.unwrap();
  137. cv.notify();
  138. });
  139. fg_runtime.push_task(app_task);
  140. #[cfg(feature = "enable-netdebug")]
  141. {
  142. let sg_root = sg_root.clone();
  143. let ex = bg_ex.clone();
  144. let renderer = renderer.clone();
  145. let zmq_task = bg_ex.spawn(async {
  146. i!("Enabled net debugging backend in this build");
  147. let zmq_rpc = ZeroMQAdapter::new(sg_root, renderer, ex).await;
  148. zmq_rpc.run().await;
  149. });
  150. bg_runtime.push_task(zmq_task);
  151. }
  152. #[cfg(feature = "enable-plugins")]
  153. {
  154. let ex = bg_ex.clone();
  155. let cv = cv_app_is_setup.clone();
  156. let renderer = renderer.clone();
  157. let plug_task = bg_ex.spawn(async move {
  158. load_plugins(ex, sg_root, renderer, cv).await;
  159. });
  160. bg_runtime.push_task(plug_task);
  161. }
  162. #[cfg(not(feature = "enable-plugins"))]
  163. warn!(target: "main", "Plugins are disabled in this build");
  164. Self {
  165. _bg_runtime: bg_runtime,
  166. _bg_ex: bg_ex,
  167. fg_runtime,
  168. _fg_ex: fg_ex,
  169. cv_app_is_setup,
  170. app,
  171. renderer,
  172. method_recv,
  173. event_pub,
  174. _file_logging_guard: file_logging_guard,
  175. }
  176. }
  177. /// Start the app. Can only happen once the window is ready.
  178. pub fn start_app(&self, epoch: EpochIndex) {
  179. info!(target: "main", "Starting the app");
  180. #[cfg(target_os = "android")]
  181. {
  182. use crate::android::{get_appdata_path, get_external_storage_path};
  183. info!("App internal data path: {:?}", get_appdata_path());
  184. info!("App external storage path: {:?}", get_external_storage_path());
  185. //let paths = std::fs::read_dir("/data/data/darkfi.darkfi/").unwrap();
  186. //for path in paths {
  187. // debug!("{}", path.unwrap().path().display())
  188. //}
  189. }
  190. info!("Target OS: {}", build_info::TARGET_OS);
  191. info!("Target arch: {}", build_info::TARGET_ARCH);
  192. let cwd = std::env::current_dir().unwrap();
  193. info!("Current dir: {}", cwd.display());
  194. self.fg_runtime.start_with_count(2);
  195. let app = self.app.clone();
  196. let cv = self.cv_app_is_setup.clone();
  197. let event_pub = self.event_pub.clone();
  198. smol::block_on(async move {
  199. cv.wait().await;
  200. app.start(event_pub, epoch).await;
  201. });
  202. self.app.notify_start();
  203. }
  204. /// Put the app to sleep until the next restart.
  205. pub fn stop_app(&self) {
  206. self.app.notify_stop();
  207. self.fg_runtime.stop();
  208. self.app.stop();
  209. info!(target: "main", "App stopped");
  210. }
  211. }
  212. impl std::fmt::Debug for God {
  213. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  214. write!(f, "God")
  215. }
  216. }
  217. static GOD: OnceLock<God> = OnceLock::new();
  218. #[cfg(feature = "enable-plugins")]
  219. async fn load_plugins(
  220. ex: ExecutorPtr,
  221. sg_root: SceneNodePtr,
  222. renderer: Renderer,
  223. cv: Arc<CondVar>,
  224. ) {
  225. let plugin = SceneNode::new("plugin", SceneNodeType::PluginRoot);
  226. let plugin = plugin.setup_null();
  227. sg_root.link(plugin.clone());
  228. // DarkIrc needs /window to start
  229. cv.wait().await;
  230. let darkirc = create_darkirc("darkirc");
  231. let darkirc = darkirc
  232. .setup(|me| async {
  233. plugin::DarkIrc::new(me, sg_root.clone(), ex.clone())
  234. .await
  235. .expect("DarkIrc pimpl setup")
  236. })
  237. .await;
  238. let (slot, recvr) = Slot::new("recvmsg");
  239. darkirc.register("recv", slot).unwrap();
  240. let sg_root2 = sg_root.clone();
  241. let darkirc_nick = PropertyStr::wrap(&darkirc, Role::App, "nick", 0).unwrap();
  242. let renderer2 = renderer.clone();
  243. let listen_recv = ex.spawn(async move {
  244. while let Ok(data) = recvr.recv().await {
  245. let atom = &mut renderer2.make_guard(gfxtag!("darkirc msg recv"));
  246. let mut cur = Cursor::new(&data);
  247. let channel = String::decode(&mut cur).unwrap();
  248. let timestamp = chatview::Timestamp::decode(&mut cur).unwrap();
  249. let id = chatview::MessageId::decode(&mut cur).unwrap();
  250. let nick = String::decode(&mut cur).unwrap();
  251. let msg = String::decode(&mut cur).unwrap();
  252. let node_path = format!("/window/content/{channel}_chat_layer/content/chatty");
  253. t!("Attempting to relay message to {node_path}");
  254. let Some(chatview) = sg_root2.lookup_node(&node_path) else {
  255. d!("Ignoring message since {node_path} doesn't exist");
  256. continue
  257. };
  258. // I prefer to just re-encode because the code is clearer.
  259. let mut data = vec![];
  260. timestamp.encode(&mut data).unwrap();
  261. id.encode(&mut data).unwrap();
  262. nick.encode(&mut data).unwrap();
  263. msg.encode(&mut data).unwrap();
  264. if let Err(err) = chatview.call_method("insert_line", data).await {
  265. error!(
  266. target: "app",
  267. "Call method {node_path}::insert_line({timestamp}, {id}, {nick}, '{msg}'): {err:?}"
  268. );
  269. }
  270. // Apply coloring when you get a message
  271. let chat_path = format!("/window/content/{channel}_chat_layer");
  272. let chat_layer = sg_root2.lookup_node(chat_path).unwrap();
  273. if chat_layer.get_property_bool("is_visible").unwrap() {
  274. continue
  275. }
  276. let node_path = format!("/window/content/menu_layer/{channel}_channel_label");
  277. let menu_label = sg_root2.lookup_node(&node_path).unwrap();
  278. let prop = menu_label.get_property("text_color").unwrap();
  279. if msg.contains(&darkirc_nick.get()) {
  280. // Nick highlight
  281. prop.set_f32(atom, Role::App, 0, 0.56).unwrap();
  282. prop.set_f32(atom, Role::App, 1, 0.61).unwrap();
  283. prop.set_f32(atom, Role::App, 2, 1.).unwrap();
  284. prop.set_f32(atom, Role::App, 3, 1.).unwrap();
  285. } else {
  286. // Normal channel activity
  287. prop.set_f32(atom, Role::App, 0, 0.36).unwrap();
  288. prop.set_f32(atom, Role::App, 1, 1.).unwrap();
  289. prop.set_f32(atom, Role::App, 2, 0.51).unwrap();
  290. prop.set_f32(atom, Role::App, 3, 1.).unwrap();
  291. }
  292. }
  293. });
  294. let (slot, recvr) = Slot::new("connect");
  295. darkirc.register("connect", slot).unwrap();
  296. let sg_root2 = sg_root.clone();
  297. let listen_connect = ex.spawn(async move {
  298. let net0 = sg_root2.lookup_node("/window/content/netstatus_layer/net0").unwrap();
  299. let net1 = sg_root2.lookup_node("/window/content/netstatus_layer/net1").unwrap();
  300. let net2 = sg_root2.lookup_node("/window/content/netstatus_layer/net2").unwrap();
  301. let net3 = sg_root2.lookup_node("/window/content/netstatus_layer/net3").unwrap();
  302. let net0_is_visible = PropertyBool::wrap(&net0, Role::App, "is_visible", 0).unwrap();
  303. let net1_is_visible = PropertyBool::wrap(&net1, Role::App, "is_visible", 0).unwrap();
  304. let net2_is_visible = PropertyBool::wrap(&net2, Role::App, "is_visible", 0).unwrap();
  305. let net3_is_visible = PropertyBool::wrap(&net3, Role::App, "is_visible", 0).unwrap();
  306. while let Ok(data) = recvr.recv().await {
  307. let (peers_count, is_dag_synced): (u32, bool) = deserialize(&data).unwrap();
  308. let atom = &mut renderer.make_guard(gfxtag!("netstatus change"));
  309. if peers_count == 0 {
  310. net0_is_visible.set(atom, true);
  311. net1_is_visible.set(atom, false);
  312. net2_is_visible.set(atom, false);
  313. net3_is_visible.set(atom, false);
  314. continue
  315. }
  316. assert!(peers_count > 0);
  317. if !is_dag_synced {
  318. net0_is_visible.set(atom, false);
  319. net1_is_visible.set(atom, true);
  320. net2_is_visible.set(atom, false);
  321. net3_is_visible.set(atom, false);
  322. continue
  323. }
  324. assert!(peers_count > 0 && is_dag_synced);
  325. if peers_count == 1 {
  326. net0_is_visible.set(atom, false);
  327. net1_is_visible.set(atom, false);
  328. net2_is_visible.set(atom, true);
  329. net3_is_visible.set(atom, false);
  330. continue
  331. }
  332. net0_is_visible.set(atom, false);
  333. net1_is_visible.set(atom, false);
  334. net2_is_visible.set(atom, false);
  335. net3_is_visible.set(atom, true);
  336. }
  337. });
  338. plugin.link(darkirc);
  339. let fud = create_fud("fud");
  340. let sg_root2 = sg_root.clone();
  341. let fud = fud
  342. .setup(|me| async {
  343. plugin::FudPlugin::new(me, sg_root2, ex.clone()).await.expect("Fud pimpl setup")
  344. })
  345. .await;
  346. let (slot, recv) = Slot::new("file_status_update");
  347. let _ = fud.register("file_status_updated", slot);
  348. let sg_root2 = sg_root.clone();
  349. let listen_file_status = ex.spawn(async move {
  350. while let Ok(data) = recv.recv().await {
  351. let window = sg_root2.lookup_node("/window/content").unwrap();
  352. let mut cur = Cursor::new(&data);
  353. let url = Url::decode(&mut cur).unwrap();
  354. let status = chatview::FileMessageStatus::decode(&mut cur).unwrap();
  355. for child in window.get_children() {
  356. if let Some(chatty) = child.lookup_node("/content/chatty") {
  357. let mut data = vec![];
  358. url.encode(&mut data).unwrap();
  359. status.encode(&mut data).unwrap();
  360. let _ = chatty.call_method("set_file_status", data).await;
  361. }
  362. }
  363. }
  364. });
  365. plugin.link(fud);
  366. i!("Plugins loaded");
  367. futures::join!(listen_recv, listen_connect, listen_file_status);
  368. }
  369. pub fn create_darkirc(name: &str) -> SceneNode {
  370. t!("create_darkirc({name})");
  371. let mut node = SceneNode::new(name, SceneNodeType::Plugin);
  372. let mut prop = Property::new("nick", PropertyType::Str, PropertySubType::Null);
  373. prop.set_ui_text("Nick", "Nickname");
  374. prop.set_defaults_str(vec!["anon".to_string()]).unwrap();
  375. node.add_property(prop).unwrap();
  376. node.add_signal(
  377. "recv",
  378. "Message received",
  379. vec![
  380. ("channel", "Channel", CallArgType::Str),
  381. ("timestamp", "Timestamp", CallArgType::Uint64),
  382. ("id", "ID", CallArgType::Hash),
  383. ("nick", "Nick", CallArgType::Str),
  384. ("msg", "Message", CallArgType::Str),
  385. ],
  386. )
  387. .unwrap();
  388. node.add_signal(
  389. "connect",
  390. "Connections and disconnects",
  391. vec![
  392. ("peers_count", "Peers Count", CallArgType::Uint32),
  393. ("dag_synced", "Is DAG Synced", CallArgType::Bool),
  394. ],
  395. )
  396. .unwrap();
  397. node.add_method(
  398. "send",
  399. vec![("channel", "Channel", CallArgType::Str), ("msg", "Message", CallArgType::Str)],
  400. None,
  401. )
  402. .unwrap();
  403. node.add_method("reconnect", vec![], None).unwrap();
  404. node
  405. }
  406. pub fn create_fud(name: &str) -> SceneNode {
  407. t!("create_fud({name})");
  408. let mut node = SceneNode::new(name, SceneNodeType::Plugin);
  409. let mut prop = Property::new("ready", PropertyType::Bool, PropertySubType::Null);
  410. prop.set_defaults_bool(vec![false]).unwrap();
  411. node.add_property(prop).unwrap();
  412. node.add_signal(
  413. "file_status_updated",
  414. "File download status updated",
  415. vec![("url", "File URL", CallArgType::Str), ("status", "File status", CallArgType::Str)],
  416. )
  417. .unwrap();
  418. node.add_method("get", vec![("url", "Url", CallArgType::Str)], None).unwrap();
  419. node.add_method("track_file", vec![("url", "Url", CallArgType::Str)], None).unwrap();
  420. node
  421. }
  422. /// Simple program to greet a person
  423. #[derive(Parser, Debug)]
  424. #[command(version, about, long_about = None)]
  425. struct Args {
  426. /// On Linux use the X11 backend
  427. #[arg(long)]
  428. linux_x11_backend: bool,
  429. /// On Linux use the wayland backend
  430. #[arg(long)]
  431. linux_wayland_backend: bool,
  432. }
  433. fn main() {
  434. let args = Args::parse();
  435. GOD.get_or_init(God::new);
  436. // Reuse renderer and event_pub
  437. // No need for setup(), just wait for gfx start then call .start()
  438. // ZMQ, darkirc stay running
  439. let linux_backend = if args.linux_wayland_backend {
  440. if args.linux_x11_backend {
  441. miniquad::conf::LinuxBackend::WaylandWithX11Fallback
  442. } else {
  443. miniquad::conf::LinuxBackend::WaylandOnly
  444. }
  445. } else if args.linux_x11_backend {
  446. miniquad::conf::LinuxBackend::X11Only
  447. } else {
  448. miniquad::conf::LinuxBackend::WaylandWithX11Fallback
  449. };
  450. gfx::run_gui(linux_backend);
  451. debug!(target: "main", "Started GFX backend");
  452. }
  453. /*
  454. use rustpython_vm::{self as pyvm, convert::ToPyObject};
  455. fn main() {
  456. let module = pyvm::Interpreter::without_stdlib(Default::default()).enter(|vm| {
  457. let source = r#"
  458. def foo():
  459. open("hihi", "w")
  460. return 110
  461. #max(1 + lw/3, 4*10) + foo(2, True)
  462. "#;
  463. //let code_obj = vm
  464. // .compile(source, pyvm::compiler::Mode::Exec, "<embedded>".to_owned())
  465. // .map_err(|err| vm.new_syntax_error(&err, Some(source))).unwrap();
  466. //code_obj
  467. pyvm::import::import_source(vm, "lain", source).unwrap()
  468. });
  469. fn foo(x: u32, y: bool) -> u32 {
  470. if y {
  471. 2 * x
  472. } else {
  473. x
  474. }
  475. }
  476. let res = pyvm::Interpreter::without_stdlib(Default::default()).enter(|vm| {
  477. let globals = vm.ctx.new_dict();
  478. globals.set_item("lw", vm.ctx.new_int(110).to_pyobject(vm), vm).unwrap();
  479. globals.set_item("lh", vm.ctx.new_int(4).to_pyobject(vm), vm).unwrap();
  480. globals.set_item("foo", vm.new_function("foo", foo).into(), vm).unwrap();
  481. let scope = pyvm::scope::Scope::new(None, globals);
  482. let foo_fn = module.get_attr("foo", vm).unwrap();
  483. foo_fn.call((), vm).unwrap()
  484. //vm.run_code_obj(code_obj, scope).unwrap()
  485. });
  486. println!("{:?}", res);
  487. }
  488. */