Added tray icon and ability to run UI hidden, but shown by tray icon actions.

This commit is contained in:
Revertron
2025-10-29 16:01:41 +01:00
parent bb3a33c103
commit 8f4cbf7dc0
4 changed files with 292 additions and 10 deletions
+2 -1
View File
@@ -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.
+70 -7
View File
@@ -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<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
pub fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, 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<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
// Create event loop and window
let event_loop = EventLoopBuilder::<UserEvent>::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<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
.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<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
// 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<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
_ => 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<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
}
}
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<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
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<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
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<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
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 {