main.rs 36 KB

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