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