From 8f4cbf7dc021d9a6de33f9991772cb8ab3608af0 Mon Sep 17 00:00:00 2001 From: Revertron Date: Wed, 29 Oct 2025 16:01:41 +0100 Subject: [PATCH] Added tray icon and ability to run UI hidden, but shown by tray icon actions. --- Cargo.lock | 219 +++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 3 +- src/main.rs | 3 +- src/web_ui.rs | 77 ++++++++++++++++-- 4 files changed, 292 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40846bc..34b5873 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,7 @@ dependencies = [ "time", "tinyfiledialogs", "toml 0.9.8", + "tray-icon", "ureq", "uuid", "winapi", @@ -172,6 +173,9 @@ name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] [[package]] name = "blake2" @@ -742,6 +746,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1526,6 +1539,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.10.0", + "serde", + "unicode-segmentation", +] + [[package]] name = "kuchikiki" version = "0.8.8-speedreader" @@ -1544,12 +1568,46 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + [[package]] name = "libc" version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libredox" version = "0.1.10" @@ -1560,6 +1618,25 @@ dependencies = [ "libc", ] +[[package]] +name = "libxdo" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00333b8756a3d28e78def82067a377de7fa61b24909000aeaa2b446a948d14db" +dependencies = [ + "libxdo-sys", +] + +[[package]] +name = "libxdo-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212" +dependencies = [ + "libc", + "x11", +] + [[package]] name = "litemap" version = "0.8.0" @@ -1664,6 +1741,27 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "muda" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "libxdo", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png", + "thiserror 2.0.17", + "windows-sys 0.60.2", +] + [[package]] name = "ndk" version = "0.9.0" @@ -1814,6 +1912,16 @@ dependencies = [ "objc2", ] +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.10.0", + "objc2-core-foundation", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -1836,6 +1944,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.10.0", + "block2", "objc2", "objc2-core-foundation", ] @@ -2103,6 +2212,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "poly1305" version = "0.8.0" @@ -3054,6 +3176,27 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +[[package]] +name = "tray-icon" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d5572781bee8e3f994d7467084e1b1fd7a93ce66bd480f8156ba89dee55a2b" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png", + "thiserror 2.0.17", + "windows-sys 0.60.2", +] + [[package]] name = "typenum" version = "1.19.0" @@ -3569,6 +3712,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -3602,13 +3754,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + [[package]] name = "windows-threading" version = "0.1.0" @@ -3639,6 +3808,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -3651,6 +3826,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -3663,12 +3844,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -3681,6 +3874,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -3693,6 +3892,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -3705,6 +3910,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -3717,6 +3928,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.5.40" diff --git a/Cargo.toml b/Cargo.toml index fd74931..7a2e733 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ thread-priority = "3.0.0" # Optional dependencies regulated by features wry = { version = "0.53", optional = true } tao = { version = "0.34", optional = true } +tray-icon = { version = "0.21.2", optional = true } tinyfiledialogs = { version = "3.9.1", optional = true } open = { version = "5.3.0", optional = true } @@ -70,6 +71,6 @@ ProductName="ALFIS" FileDescription="Alternative Free Identity System" [features] -webgui = ["wry", "tao", "tinyfiledialogs", "open"] +webgui = ["wry", "tao", "tray-icon", "tinyfiledialogs", "open"] doh = ["ureq"] default = ["webgui", "doh"] diff --git a/src/main.rs b/src/main.rs index 68f53ac..7be3747 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,6 +59,7 @@ fn main() { opts.optflag("v", "version", "Print version and exit"); opts.optflag("d", "debug", "Show debug messages, more than usual"); opts.optflag("t", "trace", "Show trace messages, more than debug"); + opts.optflag("", "hide", "Hide UI, show only tray icon."); opts.optflag("b", "blocks", "List blocks from DB and exit"); opts.optflag("g", "generate", "Generate new config file. Generated config will be printed to console."); #[cfg(windows)] @@ -251,7 +252,7 @@ fn main() { }); } #[cfg(feature = "webgui")] - web_ui::run_interface(Arc::clone(&context), miner); + web_ui::run_interface(Arc::clone(&context), miner, opt_matches.opt_present("hide")); } // Without explicitly detaching the console cmd won't redraw it's prompt. diff --git a/src/web_ui.rs b/src/web_ui.rs index c4edc1a..761d6d8 100644 --- a/src/web_ui.rs +++ b/src/web_ui.rs @@ -4,6 +4,7 @@ extern crate serde_json; extern crate tinyfiledialogs as tfd; use std::sync::{Arc, Mutex, MutexGuard}; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; use std::time::Duration; @@ -28,9 +29,11 @@ use tao::{ window::WindowBuilder, }; use tao::dpi::PhysicalPosition; +use tray_icon::menu::{Menu, MenuEvent, MenuItem}; +use tray_icon::{TrayIconBuilder, TrayIconEvent}; use wry::WebViewBuilder; -pub fn run_interface(context: Arc>, miner: Arc>) { +pub fn run_interface(context: Arc>, miner: Arc>, hide: bool) { let file_content = include_str!("webview/index.html"); let mut styles = inline_style(include_str!("webview/bulma.css")); styles.push_str(&inline_style(include_str!("webview/styles.css"))); @@ -42,7 +45,25 @@ pub fn run_interface(context: Arc>, miner: Arc>) { // Create event loop and window let event_loop = EventLoopBuilder::::with_user_event().build(); - let proxy = event_loop.create_proxy(); + + // Create tray menu + let tray_menu = Menu::new(); + let show_item = MenuItem::new("Show Window", true, None); + let quit_item = MenuItem::new("Quit", true, None); + tray_menu.append(&show_item).unwrap(); + tray_menu.append(&quit_item).unwrap(); + + #[cfg(windows)] + let icon = tray_icon::Icon::from_resource(1, None).unwrap(); + // Create tray icon + #[cfg(windows)] + let _tray_icon = TrayIconBuilder::new() + .with_menu(Box::new(tray_menu)) + .with_tooltip(&title) + .with_icon(icon) + .with_menu_on_left_click(false) + .build() + .unwrap(); let window_size = tao::dpi::LogicalSize::new(1024, 720); // Get primary monitor and calculate center position @@ -65,7 +86,7 @@ pub fn run_interface(context: Arc>, miner: Arc>) { .with_inner_size(window_size) .with_min_inner_size(tao::dpi::LogicalSize::new(773, 350)) .with_resizable(true) - .with_visible(true); + .with_visible(!hide); if let Some(position) = position { builder = builder.with_position(position); @@ -89,6 +110,7 @@ pub fn run_interface(context: Arc>, miner: Arc>) { // Clone for the IPC handler let context_ipc = Arc::clone(&context); let miner_ipc = Arc::clone(&miner); + let proxy = event_loop.create_proxy(); let proxy_ipc = proxy.clone(); // Create webview @@ -173,10 +195,13 @@ pub fn run_interface(context: Arc>, miner: Arc>) { _ => threads }; let status = Arc::new(Mutex::new(UiStatus::new(threads))); + let connected_nodes = Arc::new(AtomicUsize::new(0)); + let nodes_copy = Arc::clone(&connected_nodes); register(move |_uuid, e| { let status = Arc::clone(&status); let proxy = proxy_events.clone(); + let nodes_copy = Arc::clone(&nodes_copy); thread::Builder::new().name(String::from("webui")).spawn(move || { let mut status = status.lock().unwrap(); @@ -267,6 +292,7 @@ pub fn run_interface(context: Arc>, miner: Arc>) { } } Event::NetworkStatus { blocks, domains, keys, nodes } => { + nodes_copy.store(nodes, Ordering::SeqCst); if status.mining || status.syncing || nodes < 3 { format!("setStats({}, {}, {}, {});", blocks, domains, keys, nodes) } else { @@ -289,6 +315,18 @@ pub fn run_interface(context: Arc>, miner: Arc>) { true }); + let proxy = event_loop.create_proxy(); + TrayIconEvent::set_event_handler(Some(move |event| { + let _ = proxy.send_event(UserEvent::TrayIconEvent(event)); + })); + + let proxy = event_loop.create_proxy(); + MenuEvent::set_event_handler(Some(move |event| { + let _ = proxy.send_event(UserEvent::MenuEvent(event)); + })); + + let proxy = event_loop.create_proxy(); + // Run event loop event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; @@ -298,10 +336,7 @@ pub fn run_interface(context: Arc>, miner: Arc>) { event: WindowEvent::CloseRequested, .. } => { - info!("Interface closed, exiting"); - post(Event::ActionQuit); - thread::sleep(Duration::from_millis(100)); - *control_flow = ControlFlow::Exit; + window.set_visible(false); } TaoEvent::UserEvent(user_event) => { let wv = webview_clone.lock().unwrap(); @@ -324,6 +359,32 @@ pub fn run_interface(context: Arc>, miner: Arc>) { UserEvent::ShowWarning(text) => { show_warning(&wv, &text); } + UserEvent::TrayIconEvent(event) => { + match event { + TrayIconEvent::DoubleClick { button, .. } => { + if button == tray_icon::MouseButton::Left { + window.set_visible(true); + window.set_focus(); + } + } + TrayIconEvent::Enter { .. } => { + let nodes = connected_nodes.load(Ordering::SeqCst); + let title = format!("ALFIS {}\nConnected: {nodes}", env!("CARGO_PKG_VERSION")); + let _ = _tray_icon.set_tooltip(Some(title)); + } + _ => {} + } + } + UserEvent::MenuEvent(event) => { + if event.id == show_item.id() { + window.set_visible(true); + } else if event.id == quit_item.id() { + info!("Interface closed, exiting"); + post(Event::ActionQuit); + thread::sleep(Duration::from_millis(100)); + *control_flow = ControlFlow::Exit; + } + } } } _ => {} @@ -338,6 +399,8 @@ enum UserEvent { LoadDomains, SendKeysToUi, ShowWarning(String), + TrayIconEvent(TrayIconEvent), + MenuEvent(MenuEvent) } fn check_record(data: &str) -> bool {