main.rs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  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 darkfi::tx::Transaction;
  24. use darkfi::util::parse::encode_base10;
  25. use darkfi_money_contract::model::DARK_TOKEN_ID;
  26. use darkfi_serial::{Decodable, Encodable, deserialize};
  27. use std::sync::{Arc, OnceLock};
  28. #[macro_use]
  29. extern crate tracing;
  30. #[cfg(target_os = "android")]
  31. mod android;
  32. mod app;
  33. mod build_info;
  34. mod error;
  35. mod expr;
  36. mod gfx;
  37. mod logger;
  38. mod mesh;
  39. #[cfg(feature = "enable-netdebug")]
  40. mod net;
  41. #[cfg(feature = "enable-plugins")]
  42. mod plugin;
  43. mod prop;
  44. mod pubsub;
  45. //mod py;
  46. //mod ringbuf;
  47. mod scene;
  48. mod shape;
  49. mod text;
  50. mod ui;
  51. mod util;
  52. use crate::{
  53. app::{App, AppPtr},
  54. gfx::EpochIndex,
  55. prop::{Property, PropertySubType, PropertyType},
  56. scene::{CallArgType, SceneNode, SceneNodePtr, SceneNodeType},
  57. util::AsyncRuntime,
  58. };
  59. #[cfg(feature = "enable-netdebug")]
  60. use net::ZeroMQAdapter;
  61. #[cfg(feature = "enable-plugins")]
  62. use {
  63. // Local imports
  64. gfx::Renderer,
  65. prop::{PropertyBool, PropertyStr, Role},
  66. scene::Slot,
  67. std::io::Cursor,
  68. ui::chatview,
  69. // Global imports
  70. url::Url,
  71. };
  72. // This is historical, but ideally we can fix the entire project and remove this import.
  73. pub use util::ExecutorPtr;
  74. macro_rules! t { ($($arg:tt)*) => { trace!(target: "main", $($arg)*); } }
  75. #[cfg(feature = "enable-plugins")]
  76. macro_rules! d { ($($arg:tt)*) => { trace!(target: "main", $($arg)*); } }
  77. #[cfg(any(feature = "enable-plugins", feature = "enable-netdebug"))]
  78. macro_rules! i { ($($arg:tt)*) => { trace!(target: "main", $($arg)*); } }
  79. fn panic_hook(panic_info: &std::panic::PanicHookInfo) {
  80. error!("panic occurred: {panic_info}");
  81. error!("{}", std::backtrace::Backtrace::force_capture().to_string());
  82. std::process::abort()
  83. }
  84. /// Contains values which persist between app restarts. For example on Android, we are
  85. /// running a foreground service. Everytime the UI restarts main() is called again.
  86. /// However the global state remains intact.
  87. struct God {
  88. _bg_runtime: AsyncRuntime,
  89. _bg_ex: ExecutorPtr,
  90. pub fg_runtime: AsyncRuntime,
  91. _fg_ex: ExecutorPtr,
  92. /// App must fully finish setup() before start() is allowed to begin.
  93. cv_app_is_setup: Arc<CondVar>,
  94. app: AppPtr,
  95. /// This is the main rendering API used to send commands to the gfx subsystem.
  96. /// We have a ref here so the gfx subsystem can increment the epoch counter.
  97. renderer: gfx::Renderer,
  98. /// This is how the gfx subsystem receives messages from the render API.
  99. method_recv: async_channel::Receiver<(gfx::EpochIndex, gfx::GraphicsMethod)>,
  100. /// Publisher to send input and window events to subscribers.
  101. event_pub: gfx::GraphicsEventPublisherPtr,
  102. /// A WorkerGuard for file logging used to ensure buffered logs are flushed
  103. /// to their output in the case of abrupt terminations of a process.
  104. _file_logging_guard: Option<tracing_appender::non_blocking::WorkerGuard>,
  105. }
  106. impl God {
  107. fn new() -> Self {
  108. // Abort the application on panic right away
  109. std::panic::set_hook(Box::new(panic_hook));
  110. let file_logging_guard = logger::setup_logging();
  111. info!(target: "main", "Creating the app");
  112. #[cfg(target_os = "android")]
  113. {
  114. use crate::android::get_appdata_path;
  115. // Workaround for this bug
  116. // https://gitlab.torproject.org/tpo/core/arti/-/issues/999
  117. unsafe {
  118. std::env::set_var("HOME", get_appdata_path().as_os_str());
  119. }
  120. }
  121. let exe_path = std::env::current_exe().unwrap();
  122. let basename = exe_path.parent().unwrap();
  123. std::env::set_current_dir(basename).unwrap();
  124. let bg_ex = Arc::new(smol::Executor::new());
  125. let fg_ex = Arc::new(smol::Executor::new());
  126. let sg_root = SceneNode::root();
  127. let bg_runtime = AsyncRuntime::new(bg_ex.clone(), "bg");
  128. bg_runtime.start();
  129. let fg_runtime = AsyncRuntime::new(fg_ex.clone(), "fg");
  130. let (method_send, method_recv) = async_channel::unbounded();
  131. // The UI actually needs to be running for this to reply back.
  132. // Otherwise calls will just hang.
  133. let renderer = gfx::Renderer::new(method_send);
  134. let event_pub = gfx::GraphicsEventPublisher::new();
  135. let app = App::new(sg_root.clone(), renderer.clone(), fg_ex.clone());
  136. let app2 = app.clone();
  137. let cv_app_is_setup = Arc::new(CondVar::new());
  138. let cv = cv_app_is_setup.clone();
  139. let app_task = fg_ex.spawn(async move {
  140. app2.setup().await.unwrap();
  141. cv.notify();
  142. });
  143. fg_runtime.push_task(app_task);
  144. #[cfg(feature = "enable-netdebug")]
  145. {
  146. let sg_root = sg_root.clone();
  147. let ex = bg_ex.clone();
  148. let renderer = renderer.clone();
  149. let zmq_task = bg_ex.spawn(async {
  150. i!("Enabled net debugging backend in this build");
  151. let zmq_rpc = ZeroMQAdapter::new(sg_root, renderer, ex).await;
  152. zmq_rpc.run().await;
  153. });
  154. bg_runtime.push_task(zmq_task);
  155. }
  156. #[cfg(feature = "enable-plugins")]
  157. {
  158. let ex = bg_ex.clone();
  159. let cv = cv_app_is_setup.clone();
  160. let renderer = renderer.clone();
  161. let plug_task = bg_ex.spawn(async move {
  162. load_plugins(ex, sg_root, renderer, cv).await;
  163. });
  164. bg_runtime.push_task(plug_task);
  165. }
  166. #[cfg(not(feature = "enable-plugins"))]
  167. warn!(target: "main", "Plugins are disabled in this build");
  168. Self {
  169. _bg_runtime: bg_runtime,
  170. _bg_ex: bg_ex,
  171. fg_runtime,
  172. _fg_ex: fg_ex,
  173. cv_app_is_setup,
  174. app,
  175. renderer,
  176. method_recv,
  177. event_pub,
  178. _file_logging_guard: file_logging_guard,
  179. }
  180. }
  181. /// Start the app. Can only happen once the window is ready.
  182. pub fn start_app(&self, epoch: EpochIndex) {
  183. info!(target: "main", "Starting the app");
  184. #[cfg(target_os = "android")]
  185. {
  186. use crate::android::{get_appdata_path, get_external_storage_path};
  187. info!("App internal data path: {:?}", get_appdata_path());
  188. info!("App external storage path: {:?}", get_external_storage_path());
  189. //let paths = std::fs::read_dir("/data/data/darkfi.darkfi/").unwrap();
  190. //for path in paths {
  191. // debug!("{}", path.unwrap().path().display())
  192. //}
  193. }
  194. info!("Target OS: {}", build_info::TARGET_OS);
  195. info!("Target arch: {}", build_info::TARGET_ARCH);
  196. let cwd = std::env::current_dir().unwrap();
  197. info!("Current dir: {}", cwd.display());
  198. self.fg_runtime.start_with_count(2);
  199. let app = self.app.clone();
  200. let cv = self.cv_app_is_setup.clone();
  201. let event_pub = self.event_pub.clone();
  202. smol::block_on(async move {
  203. cv.wait().await;
  204. app.start(event_pub, epoch).await;
  205. });
  206. self.app.notify_start();
  207. }
  208. /// Put the app to sleep until the next restart.
  209. pub fn stop_app(&self) {
  210. self.app.notify_stop();
  211. self.fg_runtime.stop();
  212. self.app.stop();
  213. info!(target: "main", "App stopped");
  214. }
  215. }
  216. impl std::fmt::Debug for God {
  217. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  218. write!(f, "God")
  219. }
  220. }
  221. static GOD: OnceLock<God> = OnceLock::new();
  222. #[cfg(feature = "enable-plugins")]
  223. async fn load_plugins(
  224. ex: ExecutorPtr,
  225. sg_root: SceneNodePtr,
  226. renderer: Renderer,
  227. cv: Arc<CondVar>,
  228. ) {
  229. let plugin = SceneNode::new("plugin", SceneNodeType::PluginRoot);
  230. let plugin = plugin.setup_null();
  231. sg_root.link(plugin.clone());
  232. // DarkIrc needs /window to start
  233. cv.wait().await;
  234. let darkirc = create_darkirc("darkirc");
  235. let darkirc = darkirc
  236. .setup(|me| async {
  237. plugin::DarkIrc::new(me, sg_root.clone(), ex.clone())
  238. .await
  239. .expect("DarkIrc pimpl setup")
  240. })
  241. .await;
  242. let (slot, recvr) = Slot::new("recvmsg");
  243. darkirc.register("recv", slot).unwrap();
  244. let sg_root2 = sg_root.clone();
  245. let darkirc_nick = PropertyStr::wrap(&darkirc, Role::App, "nick", 0).unwrap();
  246. let renderer2 = renderer.clone();
  247. let listen_recv = ex.spawn(async move {
  248. while let Ok(data) = recvr.recv().await {
  249. let atom = &mut renderer2.make_guard(gfxtag!("darkirc msg recv"));
  250. let mut cur = Cursor::new(&data);
  251. let channel = String::decode(&mut cur).unwrap();
  252. let timestamp = chatview::Timestamp::decode(&mut cur).unwrap();
  253. let id = chatview::MessageId::decode(&mut cur).unwrap();
  254. let nick = String::decode(&mut cur).unwrap();
  255. let msg = String::decode(&mut cur).unwrap();
  256. let node_path = format!("/window/content/{channel}_chat_layer/content/chatty");
  257. t!("Attempting to relay message to {node_path}");
  258. let Some(chatview) = sg_root2.lookup_node(&node_path) else {
  259. d!("Ignoring message since {node_path} doesn't exist");
  260. continue
  261. };
  262. // I prefer to just re-encode because the code is clearer.
  263. let mut data = vec![];
  264. timestamp.encode(&mut data).unwrap();
  265. id.encode(&mut data).unwrap();
  266. nick.encode(&mut data).unwrap();
  267. msg.encode(&mut data).unwrap();
  268. if let Err(err) = chatview.call_method("insert_line", data).await {
  269. error!(
  270. target: "app",
  271. "Call method {node_path}::insert_line({timestamp}, {id}, {nick}, '{msg}'): {err:?}"
  272. );
  273. }
  274. // Apply coloring when you get a message
  275. let chat_path = format!("/window/content/{channel}_chat_layer");
  276. let chat_layer = sg_root2.lookup_node(chat_path).unwrap();
  277. if chat_layer.get_property_bool("is_visible").unwrap() {
  278. continue
  279. }
  280. let node_path = format!("/window/content/menu_layer/{channel}_channel_label");
  281. let menu_label = sg_root2.lookup_node(&node_path).unwrap();
  282. let prop = menu_label.get_property("text_color").unwrap();
  283. if msg.contains(&darkirc_nick.get()) {
  284. // Nick highlight
  285. prop.set_f32(atom, Role::App, 0, 0.56).unwrap();
  286. prop.set_f32(atom, Role::App, 1, 0.61).unwrap();
  287. prop.set_f32(atom, Role::App, 2, 1.).unwrap();
  288. prop.set_f32(atom, Role::App, 3, 1.).unwrap();
  289. } else {
  290. // Normal channel activity
  291. prop.set_f32(atom, Role::App, 0, 0.36).unwrap();
  292. prop.set_f32(atom, Role::App, 1, 1.).unwrap();
  293. prop.set_f32(atom, Role::App, 2, 0.51).unwrap();
  294. prop.set_f32(atom, Role::App, 3, 1.).unwrap();
  295. }
  296. }
  297. });
  298. let (slot, recvr) = Slot::new("connect");
  299. darkirc.register("connect", slot).unwrap();
  300. let sg_root2 = sg_root.clone();
  301. let renderer2 = renderer.clone();
  302. let listen_connect = ex.spawn(async move {
  303. let net0 = sg_root2.lookup_node("/window/content/netstatus_layer/net0").unwrap();
  304. let net1 = sg_root2.lookup_node("/window/content/netstatus_layer/net1").unwrap();
  305. let net2 = sg_root2.lookup_node("/window/content/netstatus_layer/net2").unwrap();
  306. let net3 = sg_root2.lookup_node("/window/content/netstatus_layer/net3").unwrap();
  307. let net0_is_visible = PropertyBool::wrap(&net0, Role::App, "is_visible", 0).unwrap();
  308. let net1_is_visible = PropertyBool::wrap(&net1, Role::App, "is_visible", 0).unwrap();
  309. let net2_is_visible = PropertyBool::wrap(&net2, Role::App, "is_visible", 0).unwrap();
  310. let net3_is_visible = PropertyBool::wrap(&net3, Role::App, "is_visible", 0).unwrap();
  311. while let Ok(data) = recvr.recv().await {
  312. let (peers_count, is_dag_synced): (u32, bool) = deserialize(&data).unwrap();
  313. let atom = &mut renderer2.make_guard(gfxtag!("netstatus change"));
  314. if peers_count == 0 {
  315. net0_is_visible.set(atom, true);
  316. net1_is_visible.set(atom, false);
  317. net2_is_visible.set(atom, false);
  318. net3_is_visible.set(atom, false);
  319. continue
  320. }
  321. assert!(peers_count > 0);
  322. if !is_dag_synced {
  323. net0_is_visible.set(atom, false);
  324. net1_is_visible.set(atom, true);
  325. net2_is_visible.set(atom, false);
  326. net3_is_visible.set(atom, false);
  327. continue
  328. }
  329. assert!(peers_count > 0 && is_dag_synced);
  330. if peers_count == 1 {
  331. net0_is_visible.set(atom, false);
  332. net1_is_visible.set(atom, false);
  333. net2_is_visible.set(atom, true);
  334. net3_is_visible.set(atom, false);
  335. continue
  336. }
  337. net0_is_visible.set(atom, false);
  338. net1_is_visible.set(atom, false);
  339. net2_is_visible.set(atom, false);
  340. net3_is_visible.set(atom, true);
  341. }
  342. });
  343. plugin.link(darkirc);
  344. let fud = create_fud("fud");
  345. let sg_root2 = sg_root.clone();
  346. let fud = fud
  347. .setup(|me| async {
  348. plugin::FudPlugin::new(me, sg_root2, ex.clone()).await.expect("Fud pimpl setup")
  349. })
  350. .await;
  351. let (slot, recv) = Slot::new("file_status_update");
  352. let _ = fud.register("file_status_updated", slot);
  353. let sg_root2 = sg_root.clone();
  354. let listen_file_status = ex.spawn(async move {
  355. while let Ok(data) = recv.recv().await {
  356. let window = sg_root2.lookup_node("/window/content").unwrap();
  357. let mut cur = Cursor::new(&data);
  358. let url = Url::decode(&mut cur).unwrap();
  359. let status = chatview::FileMessageStatus::decode(&mut cur).unwrap();
  360. for child in window.get_children() {
  361. if let Some(chatty) = child.lookup_node("/content/chatty") {
  362. let mut data = vec![];
  363. url.encode(&mut data).unwrap();
  364. status.encode(&mut data).unwrap();
  365. let _ = chatty.call_method("set_file_status", data).await;
  366. }
  367. }
  368. }
  369. });
  370. plugin.link(fud);
  371. let drk = create_drk("drk");
  372. let sg_root2 = sg_root.clone();
  373. let ex2 = ex.clone();
  374. let (drk_pimpl_send, drk_pimpl_recv) = smol::channel::bounded(1);
  375. let drk = drk
  376. .setup(move |me| async move {
  377. // Drk uses rusqlite which is not Send, so we run it on a dedicated thread
  378. let handle = std::thread::spawn(move || {
  379. let (pimpl, local_ex) = smol::block_on(plugin::DrkPlugin::new(me, sg_root2, ex2))
  380. .expect("Drk pimpl setup");
  381. // Send Pimpl back to the setup closure
  382. smol::block_on(drk_pimpl_send.send(pimpl)).unwrap();
  383. // Block on local executor to process spawned tasks forever
  384. smol::block_on(local_ex.run(async { futures::future::pending::<()>().await }));
  385. });
  386. // Wait for Pimpl to be created
  387. let pimpl = drk_pimpl_recv.recv().await.unwrap();
  388. drop(handle);
  389. pimpl
  390. })
  391. .await;
  392. let (slot, recvr) = Slot::new("connect");
  393. drk.register("connect", slot).unwrap();
  394. let sg_root2 = sg_root.clone();
  395. let renderer2 = renderer.clone();
  396. let listen_connect = ex.spawn(async move {
  397. let net0 = sg_root2.lookup_node("/window/content/wallet/netstatus_layer/net0").unwrap();
  398. let net1 = sg_root2.lookup_node("/window/content/wallet/netstatus_layer/net1").unwrap();
  399. let net2 = sg_root2.lookup_node("/window/content/wallet/netstatus_layer/net2").unwrap();
  400. let net3 = sg_root2.lookup_node("/window/content/wallet/netstatus_layer/net3").unwrap();
  401. let net0_is_visible = PropertyBool::wrap(&net0, Role::App, "is_visible", 0).unwrap();
  402. let net1_is_visible = PropertyBool::wrap(&net1, Role::App, "is_visible", 0).unwrap();
  403. let net2_is_visible = PropertyBool::wrap(&net2, Role::App, "is_visible", 0).unwrap();
  404. let net3_is_visible = PropertyBool::wrap(&net3, Role::App, "is_visible", 0).unwrap();
  405. while let Ok(data) = recvr.recv().await {
  406. let status: u8 = deserialize(&data).unwrap();
  407. let atom = &mut renderer2.make_guard(gfxtag!("blockchain netstatus change"));
  408. match status {
  409. 1 => {
  410. net0_is_visible.set(atom, false);
  411. net1_is_visible.set(atom, true);
  412. net2_is_visible.set(atom, false);
  413. net3_is_visible.set(atom, false);
  414. },
  415. 2 => {
  416. net0_is_visible.set(atom, false);
  417. net1_is_visible.set(atom, false);
  418. net2_is_visible.set(atom, true);
  419. net3_is_visible.set(atom, false);
  420. },
  421. 3 => {
  422. net0_is_visible.set(atom, false);
  423. net1_is_visible.set(atom, false);
  424. net2_is_visible.set(atom, false);
  425. net3_is_visible.set(atom, true);
  426. },
  427. _ => {
  428. net0_is_visible.set(atom, true);
  429. net1_is_visible.set(atom, false);
  430. net2_is_visible.set(atom, false);
  431. net3_is_visible.set(atom, false);
  432. }
  433. }
  434. }
  435. });
  436. let (slot, recv) = Slot::new("balances_update");
  437. let _ = drk.register("balances_updated", slot);
  438. let sg_root2 = sg_root.clone();
  439. let renderer2 = renderer.clone();
  440. let drk_node2 = drk.clone();
  441. let listen_balances = ex.spawn(async move {
  442. use crate::ui::TokenRow;
  443. use darkfi_money_contract::model::TokenId;
  444. use darkfi_serial::Encodable;
  445. let update = async || {
  446. d!("drk balances_updated signal received");
  447. // Fetch and update main wallet tokens table
  448. if let Ok(Some(response_data)) = drk_node2.call_method("get_balances", vec![]).await {
  449. let atom = &mut renderer2.make_guard(gfxtag!("wallet - refresh tokens"));
  450. let mut cur = std::io::Cursor::new(response_data);
  451. if let Ok(balances) = Vec::<(String, TokenId, u64)>::decode(&mut cur) {
  452. let token_rows: Vec<TokenRow> = balances
  453. .iter()
  454. .enumerate()
  455. .map(|(i, (symbol, token_id, balance))| {
  456. TokenRow {
  457. id: *token_id,
  458. symbol: symbol.clone(),
  459. balance: encode_base10(*balance, 8),
  460. }
  461. })
  462. .collect();
  463. let mut data: Vec<u8> = vec![];
  464. for row in &token_rows {
  465. let _ = TokenRow::encode(row, &mut data);
  466. }
  467. if let Some(tokens_table) = sg_root2.lookup_node("/window/content/wallet/main_layer/tokens_table") {
  468. let _ = tokens_table.call_method("set_tokens", data.clone()).await;
  469. }
  470. if let Some(send_tokens_table) = sg_root2.lookup_node("/window/content/wallet/send_step1_layer/tokens_table") {
  471. let _ = send_tokens_table.call_method("set_tokens", data).await;
  472. }
  473. // Update main wallet balance
  474. if let Some(drk_row) = token_rows.iter().find(|row| row.id == *DARK_TOKEN_ID) {
  475. if let Some(balance_node) = sg_root2.lookup_node("/window/content/wallet/main_layer/wallet_balance") {
  476. balance_node.set_property_str(atom, Role::App, "text", format!("DRK {}", drk_row.balance)).unwrap();
  477. }
  478. }
  479. if let Some(tx_status_layer) = sg_root2.lookup_node("/window/content/wallet/tx_status_layer") {
  480. let tx_id = tx_status_layer.get_property_str("tx_id").unwrap();
  481. if !tx_id.is_empty() {
  482. let mut tx_id_data = vec![];
  483. tx_id.encode(&mut tx_id_data).unwrap();
  484. if let Ok(Some(data)) = drk_node2.call_method("get_tx_status", tx_id_data).await {
  485. let mut cur = std::io::Cursor::new(data);
  486. let status_text = String::decode(&mut cur).unwrap();
  487. if let Some(status_node) = tx_status_layer.lookup_node("/status") {
  488. status_node.set_property_str(atom, Role::App, "text", status_text).unwrap();
  489. }
  490. }
  491. }
  492. }
  493. }
  494. }
  495. };
  496. update().await;
  497. while let Ok(_) = recv.recv().await {
  498. update().await;
  499. }
  500. });
  501. let (slot, recv) = Slot::new("tx_updated");
  502. let _ = drk.register("tx_updated", slot);
  503. let sg_root2 = sg_root.clone();
  504. let listen_tx = ex.spawn(async move {
  505. while let Ok(data) = recv.recv().await {
  506. if let Some(tx_status_layer) = sg_root2.lookup_node("/window/content/wallet/tx_status_layer") {
  507. let _ = tx_status_layer.call_method("set_tx_status", data).await;
  508. }
  509. }
  510. });
  511. // Listen for tx_built signal - emitted when transaction is built (non-blocking)
  512. let (slot, recv) = Slot::new("tx_built");
  513. let _ = drk.register("tx_built", slot);
  514. let sg_root2 = sg_root.clone();
  515. let renderer2 = renderer.clone();
  516. let listen_tx_built = ex.spawn(async move {
  517. while let Ok(data) = recv.recv().await {
  518. let mut cur = std::io::Cursor::new(data);
  519. let amount = String::decode(&mut cur).unwrap();
  520. let token_symbol = String::decode(&mut cur).unwrap();
  521. let recipient_str = String::decode(&mut cur).unwrap();
  522. // Decode transaction and pass to wallet schema
  523. let tx = Transaction::decode(&mut cur).unwrap();
  524. // Update tx_status_layer with built transaction
  525. let atom = &mut renderer2.make_guard(gfxtag!("tx built"));
  526. if let Some(tx_status) = sg_root2.lookup_node("/window/content/wallet/tx_status_layer") {
  527. let mut tx_status_data = vec![];
  528. None::<String>.encode(&mut tx_status_data).unwrap();
  529. Some("Broadcasting transaction...".to_string()).encode(&mut tx_status_data).unwrap();
  530. Some(amount).encode(&mut tx_status_data).unwrap();
  531. Some(token_symbol).encode(&mut tx_status_data).unwrap();
  532. Some(recipient_str).encode(&mut tx_status_data).unwrap();
  533. let _ = tx_status.call_method("set_tx_status", tx_status_data).await;
  534. // Call set_built_tx to store transaction for later broadcast
  535. let mut set_built_tx_data = vec![];
  536. tx.encode(&mut set_built_tx_data).unwrap();
  537. let _ = tx_status.call_method("set_built_tx", set_built_tx_data).await;
  538. }
  539. // Hide step3 layer
  540. if let Some(step4_layer) = sg_root2.lookup_node("/window/content/wallet/send_step3_layer") {
  541. step4_layer.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
  542. }
  543. // Show step4 layer
  544. if let Some(step4_layer) = sg_root2.lookup_node("/window/content/wallet/send_step4_layer") {
  545. step4_layer.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
  546. }
  547. }
  548. });
  549. // Listen for tx_built_error signal - emitted when transaction building fails
  550. let (slot, recv) = Slot::new("tx_built_error");
  551. let _ = drk.register("tx_built_error", slot);
  552. let sg_root2 = sg_root.clone();
  553. let renderer2 = renderer.clone();
  554. let listen_tx_built_error = ex.spawn(async move {
  555. while let Ok(data) = recv.recv().await {
  556. let mut cur = std::io::Cursor::new(data);
  557. let error_message = String::decode(&mut cur).unwrap();
  558. let atom = &mut renderer2.make_guard(gfxtag!("tx built error"));
  559. // TODO: display error somewhere
  560. // Reset button state
  561. if let Some(btn_node) = sg_root2.lookup_node("/window/content/wallet/send_step3_layer/send_amount_button") {
  562. btn_node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
  563. if let Some(label_node) = sg_root2.lookup_node("/window/content/wallet/send_step3_layer/send_amount_button_label") {
  564. label_node.set_property_str(atom, Role::App, "text", "add amount").unwrap();
  565. }
  566. }
  567. }
  568. });
  569. plugin.link(drk);
  570. i!("Plugins loaded");
  571. futures::join!(listen_recv, listen_connect, listen_file_status, listen_balances, listen_tx, listen_tx_built, listen_tx_built_error);
  572. }
  573. pub fn create_darkirc(name: &str) -> SceneNode {
  574. t!("create_darkirc({name})");
  575. let mut node = SceneNode::new(name, SceneNodeType::Plugin);
  576. let mut prop = Property::new("nick", PropertyType::Str, PropertySubType::Null);
  577. prop.set_ui_text("Nick", "Nickname");
  578. prop.set_defaults_str(vec!["anon".to_string()]).unwrap();
  579. node.add_property(prop).unwrap();
  580. node.add_signal(
  581. "recv",
  582. "Message received",
  583. vec![
  584. ("channel", "Channel", CallArgType::Str),
  585. ("timestamp", "Timestamp", CallArgType::Uint64),
  586. ("id", "ID", CallArgType::Hash),
  587. ("nick", "Nick", CallArgType::Str),
  588. ("msg", "Message", CallArgType::Str),
  589. ],
  590. )
  591. .unwrap();
  592. node.add_signal(
  593. "connect",
  594. "Connections and disconnects",
  595. vec![
  596. ("peers_count", "Peers Count", CallArgType::Uint32),
  597. ("dag_synced", "Is DAG Synced", CallArgType::Bool),
  598. ],
  599. )
  600. .unwrap();
  601. node.add_method(
  602. "send",
  603. vec![("channel", "Channel", CallArgType::Str), ("msg", "Message", CallArgType::Str)],
  604. None,
  605. )
  606. .unwrap();
  607. node.add_method("reconnect", vec![], None).unwrap();
  608. node
  609. }
  610. pub fn create_fud(name: &str) -> SceneNode {
  611. t!("create_fud({name})");
  612. let mut node = SceneNode::new(name, SceneNodeType::Plugin);
  613. let mut prop = Property::new("ready", PropertyType::Bool, PropertySubType::Null);
  614. prop.set_defaults_bool(vec![false]).unwrap();
  615. node.add_property(prop).unwrap();
  616. node.add_signal(
  617. "file_status_updated",
  618. "File download status updated",
  619. vec![("url", "File URL", CallArgType::Str), ("status", "File status", CallArgType::Str)],
  620. )
  621. .unwrap();
  622. node.add_method("get", vec![("url", "Url", CallArgType::Str)], None).unwrap();
  623. node.add_method("track_file", vec![("url", "Url", CallArgType::Str)], None).unwrap();
  624. node
  625. }
  626. pub fn create_drk(name: &str) -> SceneNode {
  627. t!("create_drk({name})");
  628. let mut node = SceneNode::new(name, SceneNodeType::Plugin);
  629. node.add_signal(
  630. "connect",
  631. "Connections and disconnects",
  632. vec![
  633. ("connected", "Is darkfid connected", CallArgType::Bool),
  634. ],
  635. )
  636. .unwrap();
  637. node.add_method(
  638. "get_default_address",
  639. vec![],
  640. Some(vec![("address", "Default address", CallArgType::Str)]),
  641. ).unwrap();
  642. node.add_method(
  643. "get_balances",
  644. vec![],
  645. Some(vec![("balances", "Token balances", CallArgType::Hash)]),
  646. ).unwrap();
  647. node.add_method(
  648. "get_tx_status",
  649. vec![("tx_id", "Transaction hash", CallArgType::Str)],
  650. Some(vec![("status_text", "Status text", CallArgType::Str)]),
  651. ).unwrap();
  652. node.add_method(
  653. "build_tx",
  654. vec![
  655. ("amount", "Amount", CallArgType::Str),
  656. ("token_id", "Token ID", CallArgType::Hash),
  657. ("recipient", "Recipient address", CallArgType::Str),
  658. ],
  659. Some(vec![("tx", "Transaction", CallArgType::Hash)]),
  660. ).unwrap();
  661. node.add_method(
  662. "broadcast_tx",
  663. vec![("tx", "Transaction", CallArgType::Hash)],
  664. Some(vec![("status_text", "Status text", CallArgType::Str)]),
  665. ).unwrap();
  666. node.add_signal("balances_updated", "Balances changed", vec![]).unwrap();
  667. node.add_signal(
  668. "tx_updated",
  669. "Transaction status updated",
  670. vec![
  671. ("tx_id", "Transaction ID", CallArgType::Str),
  672. ("status_text", "Transaction status text", CallArgType::Str),
  673. ],
  674. ).unwrap();
  675. node.add_signal(
  676. "tx_built",
  677. "Transaction built - for wallet send flow",
  678. vec![
  679. ("amount", "Amount", CallArgType::Str),
  680. ("token_symbol", "Token symbol", CallArgType::Str),
  681. ("recipient_str", "Recipient address", CallArgType::Str),
  682. ],
  683. ).unwrap();
  684. node.add_signal(
  685. "tx_built_error",
  686. "Transaction build error",
  687. vec![
  688. ("error_message", "Error message", CallArgType::Str),
  689. ],
  690. ).unwrap();
  691. node
  692. }
  693. /// Simple program to greet a person
  694. #[derive(Parser, Debug)]
  695. #[command(version, about, long_about = None)]
  696. struct Args {
  697. /// On Linux use the X11 backend
  698. #[arg(long)]
  699. linux_x11_backend: bool,
  700. /// On Linux use the wayland backend
  701. #[arg(long)]
  702. linux_wayland_backend: bool,
  703. }
  704. fn main() {
  705. let args = Args::parse();
  706. GOD.get_or_init(God::new);
  707. // Reuse renderer and event_pub
  708. // No need for setup(), just wait for gfx start then call .start()
  709. // ZMQ, darkirc stay running
  710. let linux_backend = if args.linux_wayland_backend {
  711. if args.linux_x11_backend {
  712. miniquad::conf::LinuxBackend::WaylandWithX11Fallback
  713. } else {
  714. miniquad::conf::LinuxBackend::WaylandOnly
  715. }
  716. } else if args.linux_x11_backend {
  717. miniquad::conf::LinuxBackend::X11Only
  718. } else {
  719. miniquad::conf::LinuxBackend::WaylandWithX11Fallback
  720. };
  721. gfx::run_gui(linux_backend);
  722. debug!(target: "main", "Started GFX backend");
  723. }
  724. /*
  725. use rustpython_vm::{self as pyvm, convert::ToPyObject};
  726. fn main() {
  727. let module = pyvm::Interpreter::without_stdlib(Default::default()).enter(|vm| {
  728. let source = r#"
  729. def foo():
  730. open("hihi", "w")
  731. return 110
  732. #max(1 + lw/3, 4*10) + foo(2, True)
  733. "#;
  734. //let code_obj = vm
  735. // .compile(source, pyvm::compiler::Mode::Exec, "<embedded>".to_owned())
  736. // .map_err(|err| vm.new_syntax_error(&err, Some(source))).unwrap();
  737. //code_obj
  738. pyvm::import::import_source(vm, "lain", source).unwrap()
  739. });
  740. fn foo(x: u32, y: bool) -> u32 {
  741. if y {
  742. 2 * x
  743. } else {
  744. x
  745. }
  746. }
  747. let res = pyvm::Interpreter::without_stdlib(Default::default()).enter(|vm| {
  748. let globals = vm.ctx.new_dict();
  749. globals.set_item("lw", vm.ctx.new_int(110).to_pyobject(vm), vm).unwrap();
  750. globals.set_item("lh", vm.ctx.new_int(4).to_pyobject(vm), vm).unwrap();
  751. globals.set_item("foo", vm.new_function("foo", foo).into(), vm).unwrap();
  752. let scope = pyvm::scope::Scope::new(None, globals);
  753. let foo_fn = module.get_attr("foo", vm).unwrap();
  754. foo_fn.call((), vm).unwrap()
  755. //vm.run_code_obj(code_obj, scope).unwrap()
  756. });
  757. println!("{:?}", res);
  758. }
  759. */