main.rs 36 KB

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