From f0d335170ae653e824b8a42f85b6b945f51c6ec7 Mon Sep 17 00:00:00 2001 From: hinto-janaiyo <hinto.janaiyo@protonmail.com> Date: Tue, 13 Dec 2022 09:39:09 -0500 Subject: [PATCH] helper/main: fix [Arc<Mutex>] deadlocks, add keyboard shortcuts There was a deadlock happening between the [Helper]'s [gui_api_p2pool] and the GUI's [gui_api_p2pool], since the locking order was different. The watchdog loop locking order was fixed as well. This was a pain to track down, better than a data race... I guess. Oh and keyboard shortcuts were added in this commit too. Comment from code: // The ordering of these locks is _very_ important. They MUST be in sync // with how the main GUI thread locks stuff or a deadlock will occur // given enough time. They will eventually both want to lock the [Arc<Mutex>] // the other thread is already locking. Yes, I figured this out the hard way, // hence the vast amount of debug!() messages. // // Example of different order (BAD!): // // GUI Main -> locks [p2pool] first // Helper -> locks [gui_api_p2pool] first // GUI Status Tab -> trys to lock [gui_api_p2pool] -> CAN'T // Helper -> trys to lock [p2pool] -> CAN'T // // These two threads are now in a deadlock because both // are trying to access locks the other one already has. // // The locking order here must be in the same chronological // order as the main GUI thread (top to bottom). --- Cargo.lock | 12 +-- README.md | 2 +- src/constants.rs | 13 +++ src/helper.rs | 70 ++++++++++------ src/main.rs | 203 +++++++++++++++++++++++++++++++++++++---------- src/status.rs | 6 +- 6 files changed, 229 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2423628..95dd9c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2796,9 +2796,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "cf1c2c742266c2f1041c914ba65355a83ae8747b05f208319784083583494b4b" [[package]] name = "pathdiff" @@ -3420,18 +3420,18 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "serde" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" +checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" +checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" dependencies = [ "proc-macro2", "quote", diff --git a/README.md b/README.md index 9fa440f..b34db61 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@  Gupax is a (Windows|macOS|Linux) GUI for mining [**Monero**](https://github.com/monero-project/monero) on [**P2Pool**](https://github.com/SChernykh/p2pool), using [**XMRig**](https://github.com/xmrig/xmrig). -**If you just want to see a short 1-minute video on how to download and run Gupax: [click here.](#Video)** +**To see a 1-minute video on how to download and run Gupax: [click here.](#Video)** ## Contents * [What is Monero/P2Pool/XMRig/Gupax?](#what-is-monero-p2pool-xmrig-and-gupax) diff --git a/src/constants.rs b/src/constants.rs index d172da1..bca5a3a 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -40,6 +40,19 @@ pub const HORIZONTAL: &str = "--------------------------------------------"; // The text to separate my "process stopped, here's stats" text from the process output in the console. pub const HORI_CONSOLE: &str = "---------------------------------------------------------------------------------------------------------------------------"; +// Keyboard shortcuts +pub const KEYBOARD_SHORTCUTS: &str = +r#"*--------------------------------------* +| Key shortcuts | +|--------------------------------------| +| F11 | Fullscreen | +| Escape | Quit screen | +| Left/Right | Switch Tabs | +| Up | Start/Restart | +| Down | Stop | +| S | Save | +| R | Reset | +*--------------------------------------*"#; // P2Pool & XMRig default API stuff #[cfg(target_os = "windows")] pub const P2POOL_API_PATH: &str = r"local\stats"; // The default relative FS path of P2Pool's local API diff --git a/src/helper.rs b/src/helper.rs index 02be2b9..45692b9 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -558,6 +558,12 @@ impl Helper { } drop(lock); + + // Check if logs need resetting + debug!("P2Pool Watchdog | Attempting log reset check"); + Self::check_reset_output_full(&output_full, ProcessName::P2pool); + Self::check_reset_gui_p2pool_output(&gui_api); + // Always update from output debug!("P2Pool Watchdog | Starting [update_from_output()]"); PubP2poolApi::update_from_output(&pub_api, &output_full, &output_buf, start.elapsed(), ®ex); @@ -572,11 +578,6 @@ impl Helper { } } - // Check if logs need resetting - debug!("P2Pool Watchdog | Attempting log reset check"); - Self::check_reset_output_full(&output_full, ProcessName::P2pool); - Self::check_reset_gui_p2pool_output(&gui_api); - // Sleep (only if 900ms hasn't passed) let elapsed = now.elapsed().as_millis(); // Since logic goes off if less than 1000, casting should be safe @@ -910,6 +911,11 @@ impl Helper { } drop(lock); + // Check if logs need resetting + debug!("XMRig Watchdog | Attempting log reset check"); + Self::check_reset_output_full(&output_full, ProcessName::Xmrig); + Self::check_reset_gui_xmrig_output(&gui_api); + // Always update from output debug!("XMRig Watchdog | Starting [update_from_output()]"); PubXmrigApi::update_from_output(&pub_api, &output_buf, start.elapsed()); @@ -923,11 +929,6 @@ impl Helper { warn!("XMRig Watchdog | Could not send HTTP API request to: {}", api_ip_port); } - // Check if logs need resetting - debug!("XMRig Watchdog | Attempting log reset check"); - Self::check_reset_output_full(&output_full, ProcessName::Xmrig); - Self::check_reset_gui_xmrig_output(&gui_api); - // Sleep (only if 900ms hasn't passed) let elapsed = now.elapsed().as_millis(); // Since logic goes off if less than 1000, casting should be safe @@ -978,8 +979,33 @@ impl Helper { // The "helper" thread. Syncs data between threads here and the GUI. pub fn spawn_helper(helper: &Arc<Mutex<Self>>, mut sysinfo: sysinfo::System, pid: sysinfo::Pid, max_threads: usize) { - let mut helper = Arc::clone(helper); - let mut pub_sys = Arc::clone(&helper.lock().unwrap().pub_sys); + // The ordering of these locks is _very_ important. They MUST be in sync with how the main GUI thread locks stuff + // or a deadlock will occur given enough time. They will eventually both want to lock the [Arc<Mutex>] the other + // thread is already locking. Yes, I figured this out the hard way, hence the vast amount of debug!() messages. + // Example of different order (BAD!): + // + // GUI Main -> locks [p2pool] first + // Helper -> locks [gui_api_p2pool] first + // GUI Status Tab -> trys to lock [gui_api_p2pool] -> CAN'T + // Helper -> trys to lock [p2pool] -> CAN'T + // + // These two threads are now in a deadlock because both + // are trying to access locks the other one already has. + // + // The locking order here must be in the same chronological + // order as the main GUI thread (top to bottom). + + let helper = Arc::clone(helper); + let lock = helper.lock().unwrap(); + let p2pool = Arc::clone(&lock.p2pool); + let xmrig = Arc::clone(&lock.xmrig); + let pub_sys = Arc::clone(&lock.pub_sys); + let gui_api_p2pool = Arc::clone(&lock.gui_api_p2pool); + let gui_api_xmrig = Arc::clone(&lock.gui_api_xmrig); + let pub_api_p2pool = Arc::clone(&lock.pub_api_p2pool); + let pub_api_xmrig = Arc::clone(&lock.pub_api_xmrig); + drop(lock); + let sysinfo_cpu = sysinfo::CpuRefreshKind::everything(); let sysinfo_processes = sysinfo::ProcessRefreshKind::new().with_cpu(); @@ -991,23 +1017,21 @@ impl Helper { let start = Instant::now(); debug!("Helper | ----------- Start of loop -----------"); - // // Ignore the invasive [debug!()] messages on the right side of the code. // The reason why they are there are so that it's extremely easy to track // down the culprit of an [Arc<Mutex>] deadlock. I know, they're ugly. - // // 2. Lock... EVERYTHING! - let mut lock = helper.lock().unwrap(); debug!("Helper | Locking (1/8) ... [helper]"); + let mut lock = helper.lock().unwrap(); debug!("Helper | Locking (1/8) ... [helper]"); + let p2pool = p2pool.lock().unwrap(); debug!("Helper | Locking (2/8) ... [p2pool]"); + let xmrig = xmrig.lock().unwrap(); debug!("Helper | Locking (3/8) ... [xmrig]"); + let mut lock_pub_sys = pub_sys.lock().unwrap(); debug!("Helper | Locking (4/8) ... [pub_sys]"); + let mut gui_api_p2pool = gui_api_p2pool.lock().unwrap(); debug!("Helper | Locking (5/8) ... [gui_api_p2pool]"); + let mut gui_api_xmrig = gui_api_xmrig.lock().unwrap(); debug!("Helper | Locking (6/8) ... [gui_api_xmrig]"); + let mut pub_api_p2pool = pub_api_p2pool.lock().unwrap(); debug!("Helper | Locking (7/8) ... [pub_api_p2pool]"); + let mut pub_api_xmrig = pub_api_xmrig.lock().unwrap(); debug!("Helper | Locking (8/8) ... [pub_api_xmrig]"); // Calculate Gupax's uptime always. lock.uptime = HumanTime::into_human(lock.instant.elapsed()); - let mut gui_api_p2pool = lock.gui_api_p2pool.lock().unwrap(); debug!("Helper | Locking (2/8) ... [gui_api_p2pool]"); - let mut gui_api_xmrig = lock.gui_api_xmrig.lock().unwrap(); debug!("Helper | Locking (3/8) ... [gui_api_xmrig]"); - let mut pub_api_p2pool = lock.pub_api_p2pool.lock().unwrap(); debug!("Helper | Locking (4/8) ... [pub_api_p2pool]"); - let mut pub_api_xmrig = lock.pub_api_xmrig.lock().unwrap(); debug!("Helper | Locking (5/8) ... [pub_api_xmrig]"); - let p2pool = lock.p2pool.lock().unwrap(); debug!("Helper | Locking (6/8) ... [p2pool]"); - let xmrig = lock.xmrig.lock().unwrap(); debug!("Helper | Locking (7/8) ... [xmrig]"); - let mut lock_pub_sys = pub_sys.lock().unwrap(); debug!("Helper | Locking (8/8) ... [pub_sys]"); // If [P2Pool] is alive... if p2pool.is_alive() { debug!("Helper | P2Pool is alive! Running [combine_gui_pub_api()]"); @@ -1056,7 +1080,7 @@ impl Helper { // 5. End loop } - // 5. Something has gone terribly wrong if the helper exited this loop. + // 6. Something has gone terribly wrong if the helper exited this loop. let text = "HELPER THREAD HAS ESCAPED THE LOOP...!"; error!("{}", text);error!("{}", text);error!("{}", text);panic!("{}", text); diff --git a/src/main.rs b/src/main.rs index b0e1c43..9b83b0e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -562,9 +562,60 @@ impl Regexes { } } +//---------------------------------------------------------------------------------------------------- [Pressed] enum +// These represent the keys pressed during the frame. +// I could use egui's [Key] but there is no option for +// a [None] and wrapping [key_pressed] like [Option<egui::Key>] +// meant that I had to destructure like this: +// if let Some(egui::Key)) = key_pressed { /* do thing */ } +// +// That's ugly, so these are used instead so a simple compare can be used. +#[derive(Debug,Clone,Eq,PartialEq)] +enum KeyPressed { + F11, + Left, + Right, + Up, + Down, + Esc, + S, + R, + None, +} + +impl KeyPressed { + fn is_f11(&self) -> bool { + *self == Self::F11 + } + fn is_left(&self) -> bool { + *self == Self::Left + } + fn is_right(&self) -> bool { + *self == Self::Right + } + fn is_up(&self) -> bool { + *self == Self::Up + } + fn is_down(&self) -> bool { + *self == Self::Down + } + fn is_esc(&self) -> bool { + *self == Self::Esc + } + fn is_s(&self) -> bool { + *self == Self::S + } + fn is_r(&self) -> bool { + *self == Self::R + } + fn is_none(&self) -> bool { + *self == Self::None + } +} + //---------------------------------------------------------------------------------------------------- Init functions fn init_text_styles(ctx: &egui::Context, width: f32) { - let scale = width / 26.666; + let scale = width / 30.0; let mut style = (*ctx.style()).clone(); style.text_styles = [ (Small, FontId::new(scale/3.0, Proportional)), @@ -859,15 +910,74 @@ impl eframe::App for App { debug!("App | ----------- Start of [update()] -----------"); // If [F11] was pressed, reverse [fullscreen] bool - if ctx.input_mut().consume_key(Modifiers::NONE, Key::F11) { + let mut input = ctx.input_mut(); + let mut key: KeyPressed = { + if input.consume_key(Modifiers::NONE, Key::F11) { + KeyPressed::F11 + } else if input.consume_key(Modifiers::NONE, Key::ArrowLeft) { + KeyPressed::Left + } else if input.consume_key(Modifiers::NONE, Key::ArrowRight) { + KeyPressed::Right + } else if input.consume_key(Modifiers::NONE, Key::ArrowUp) { + KeyPressed::Up + } else if input.consume_key(Modifiers::NONE, Key::ArrowDown) { + KeyPressed::Down + } else if input.consume_key(Modifiers::NONE, Key::Escape) { + KeyPressed::Esc + } else if input.consume_key(Modifiers::NONE, Key::S) { + KeyPressed::S + } else if input.consume_key(Modifiers::NONE, Key::R) { + KeyPressed::R + } else { + KeyPressed::None + } + }; + drop(input); + + if key.is_f11() { let info = frame.info(); frame.set_fullscreen(!info.window_info.fullscreen); + // Change Tabs LEFT + } else if key.is_left() { + match self.tab { + Tab::About => self.tab = Tab::Xmrig, + Tab::Status => self.tab = Tab::About, + Tab::Gupax => self.tab = Tab::Status, + Tab::P2pool => self.tab = Tab::Gupax, + Tab::Xmrig => self.tab = Tab::P2pool, + }; + // Change Tabs RIGHT + } else if key.is_right() { + match self.tab { + Tab::About => self.tab = Tab::Status, + Tab::Status => self.tab = Tab::Gupax, + Tab::Gupax => self.tab = Tab::P2pool, + Tab::P2pool => self.tab = Tab::Xmrig, + Tab::Xmrig => self.tab = Tab::About, + }; } // Refresh AT LEAST once a second debug!("App | Refreshing frame once per second"); ctx.request_repaint_after(SECOND); + // Get P2Pool/XMRig process state. + // These values are checked multiple times so + // might as well check only once here to save + // on a bunch of [.lock().unwrap()]s. + debug!("App | Locking and collecting P2Pool state..."); + let p2pool = self.p2pool.lock().unwrap(); + let p2pool_is_alive = p2pool.is_alive(); + let p2pool_is_waiting = p2pool.is_waiting(); + let p2pool_state = p2pool.state; + drop(p2pool); + debug!("App | Locking and collecting XMRig state..."); + let xmrig = self.xmrig.lock().unwrap(); + let xmrig_is_alive = xmrig.is_alive(); + let xmrig_is_waiting = xmrig.is_waiting(); + let xmrig_state = xmrig.state; + drop(xmrig); + // This sets the top level Ui dimensions. // Used as a reference for other uis. debug!("App | Setting width/height"); @@ -950,8 +1060,8 @@ impl eframe::App for App { StayQuit => { let mut text = "".to_string(); if *self.update.lock().unwrap().updating.lock().unwrap() { text = format!("{}\nUpdate is in progress...! Quitting may cause file corruption!", text); } - if self.p2pool.lock().unwrap().is_alive() { text = format!("{}\nP2Pool is online...!", text); } - if self.xmrig.lock().unwrap().is_alive() { text = format!("{}\nXMRig is online...!", text); } + if p2pool_is_alive { text = format!("{}\nP2Pool is online...!", text); } + if xmrig_is_alive { text = format!("{}\nXMRig is online...!", text); } ui.add_sized([width, height], Label::new("--- Are you sure you want to quit? ---")); ui.add_sized([width, height], Label::new(text)) }, @@ -985,18 +1095,15 @@ impl eframe::App for App { }; let height = ui.available_height(); - // Capture [Esc] key - let esc = ctx.input_mut().consume_key(Modifiers::NONE, Key::Escape); - match self.error_state.buttons { YesNo => { if ui.add_sized([width, height/2.0], Button::new("Yes")).clicked() { self.error_state.reset() } // If [Esc] was pressed, assume [No] - if esc || ui.add_sized([width, height/2.0], Button::new("No")).clicked() { exit(0); } + if key.is_esc() || ui.add_sized([width, height/2.0], Button::new("No")).clicked() { exit(0); } }, StayQuit => { // If [Esc] was pressed, assume [Stay] - if esc || ui.add_sized([width, height/2.0], Button::new("Stay")).clicked() { + if key.is_esc() || ui.add_sized([width, height/2.0], Button::new("Stay")).clicked() { self.error_state = ErrorState::new(); } if ui.add_sized([width, height/2.0], Button::new("Quit")).clicked() { exit(0); } @@ -1020,7 +1127,7 @@ impl eframe::App for App { Err(e) => self.error_state.set(format!("State reset fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit), }; } - if esc || ui.add_sized([width, height/2.0], Button::new("No")).clicked() { self.error_state.reset() } + if key.is_esc() || ui.add_sized([width, height/2.0], Button::new("No")).clicked() { self.error_state.reset() } }, ResetNode => { if ui.add_sized([width, height/2.0], Button::new("Yes")).clicked() { @@ -1038,7 +1145,7 @@ impl eframe::App for App { Err(e) => self.error_state.set(format!("Node reset fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit), }; } - if esc || ui.add_sized([width, height/2.0], Button::new("No")).clicked() { self.error_state.reset() } + if key.is_esc() || ui.add_sized([width, height/2.0], Button::new("No")).clicked() { self.error_state.reset() } }, ErrorButtons::Sudo => { let sudo_width = width/10.0; @@ -1067,13 +1174,13 @@ impl eframe::App for App { let color = if hide { BLACK } else { BRIGHT_YELLOW }; if ui.add_sized([box_width, height], Button::new(RichText::new("👁").color(color))).on_hover_text(PASSWORD_HIDE).clicked() { sudo.hide = !sudo.hide; } }); - if (esc && !sudo.testing) || ui.add_sized([width, height*4.0], Button::new("Leave")).clicked() { self.error_state.reset(); }; + if (key.is_esc() && !sudo.testing) || ui.add_sized([width, height*4.0], Button::new("Leave")).clicked() { self.error_state.reset(); }; // If [test_sudo()] finished, reset error state. if sudo.success { self.error_state.reset(); } }, - Okay|WindowsAdmin => if esc || ui.add_sized([width, height], Button::new("Okay")).clicked() { self.error_state.reset(); }, + Okay|WindowsAdmin => if key.is_esc() || ui.add_sized([width, height], Button::new("Okay")).clicked() { self.error_state.reset(); }, Quit => if ui.add_sized([width, height], Button::new("Quit")).clicked() { exit(1); }, } })}); @@ -1148,14 +1255,14 @@ impl eframe::App for App { ui.separator(); // [P2Pool/XMRig] Status use ProcessState::*; - match self.p2pool.lock().unwrap().state { + match p2pool_state { Alive => ui.add_sized([width, height], Label::new(RichText::new("P2Pool ⏺").color(GREEN))).on_hover_text(P2POOL_ALIVE), Dead => ui.add_sized([width, height], Label::new(RichText::new("P2Pool ⏺").color(GRAY))).on_hover_text(P2POOL_DEAD), Failed => ui.add_sized([width, height], Label::new(RichText::new("P2Pool ⏺").color(RED))).on_hover_text(P2POOL_FAILED), Middle|Waiting => ui.add_sized([width, height], Label::new(RichText::new("P2Pool ⏺").color(YELLOW))).on_hover_text(P2POOL_MIDDLE), }; ui.separator(); - match self.xmrig.lock().unwrap().state { + match xmrig_state { Alive => ui.add_sized([width, height], Label::new(RichText::new("XMRig ⏺").color(GREEN))).on_hover_text(XMRIG_ALIVE), Dead => ui.add_sized([width, height], Label::new(RichText::new("XMRig ⏺").color(GRAY))).on_hover_text(XMRIG_DEAD), Failed => ui.add_sized([width, height], Label::new(RichText::new("XMRig ⏺").color(RED))).on_hover_text(XMRIG_FAILED), @@ -1172,7 +1279,7 @@ impl eframe::App for App { ui.group(|ui| { ui.set_enabled(self.diff); let width = width / 2.0; - if ui.add_sized([width, height], Button::new("Reset")).on_hover_text("Reset changes").clicked() { + if key.is_r() || ui.add_sized([width, height], Button::new("Reset")).on_hover_text("Reset changes").clicked() { let og = self.og.lock().unwrap().clone(); self.state.gupax = og.gupax; self.state.p2pool = og.p2pool; @@ -1180,7 +1287,7 @@ impl eframe::App for App { self.node_vec = self.og_node_vec.clone(); self.pool_vec = self.og_pool_vec.clone(); } - if ui.add_sized([width, height], Button::new("Save")).on_hover_text("Save changes").clicked() { + if key.is_s() || ui.add_sized([width, height], Button::new("Save")).on_hover_text("Save changes").clicked() { match State::save(&mut self.state, &self.state_path) { Ok(_) => { let mut og = self.og.lock().unwrap(); @@ -1230,17 +1337,17 @@ impl eframe::App for App { }); ui.group(|ui| { let width = (ui.available_width()/3.0)-5.0; - if self.p2pool.lock().unwrap().is_waiting() { + if p2pool_is_waiting { ui.add_enabled_ui(false, |ui| { ui.add_sized([width, height], Button::new("⟲")).on_disabled_hover_text("Restart P2Pool"); ui.add_sized([width, height], Button::new("⏹")).on_disabled_hover_text("Stop P2Pool"); ui.add_sized([width, height], Button::new("▶")).on_disabled_hover_text("Start P2Pool"); }); - } else if self.p2pool.lock().unwrap().is_alive() { - if ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart P2Pool").clicked() { + } else if p2pool_is_alive { + if key.is_up() || ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart P2Pool").clicked() { Helper::restart_p2pool(&self.helper, &self.state.p2pool, &self.state.gupax.absolute_p2pool_path); } - if ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop P2Pool").clicked() { + if key.is_down() || ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop P2Pool").clicked() { Helper::stop_p2pool(&self.helper); } ui.add_enabled_ui(false, |ui| { @@ -1260,7 +1367,7 @@ impl eframe::App for App { ui.set_enabled(false); text = P2POOL_PATH_NOT_EXE.to_string(); } - if ui.add_sized([width, height], Button::new("▶")).on_hover_text("Start P2Pool").on_disabled_hover_text(text).clicked() { + if key.is_up() || ui.add_sized([width, height], Button::new("▶")).on_hover_text("Start P2Pool").on_disabled_hover_text(text).clicked() { Helper::start_p2pool(&self.helper, &self.state.p2pool, &self.state.gupax.absolute_p2pool_path); } } @@ -1279,14 +1386,14 @@ impl eframe::App for App { }); ui.group(|ui| { let width = (ui.available_width()/3.0)-5.0; - if self.xmrig.lock().unwrap().is_waiting() { + if xmrig_is_waiting { ui.add_enabled_ui(false, |ui| { ui.add_sized([width, height], Button::new("⟲")).on_disabled_hover_text("Restart XMRig"); ui.add_sized([width, height], Button::new("⏹")).on_disabled_hover_text("Stop XMRig"); ui.add_sized([width, height], Button::new("▶")).on_disabled_hover_text("Start XMRig"); }); - } else if self.xmrig.lock().unwrap().is_alive() { - if ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart XMRig").clicked() { + } else if xmrig_is_alive { + if key.is_up() || ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart XMRig").clicked() { if cfg!(windows) { Helper::restart_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path, Arc::clone(&self.sudo)); } else if cfg!(target_os = "macos") { @@ -1297,7 +1404,7 @@ impl eframe::App for App { self.error_state.ask_sudo(&self.sudo); } } - if ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop XMRig").clicked() { + if key.is_down() || ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop XMRig").clicked() { if cfg!(target_os = "macos") { self.sudo.lock().unwrap().signal = ProcessSignal::Stop; self.error_state.ask_sudo(&self.sudo); @@ -1319,11 +1426,11 @@ impl eframe::App for App { text = XMRIG_PATH_NOT_EXE.to_string(); } #[cfg(target_os = "windows")] - if ui.add_sized([width, height], Button::new("▶")).on_hover_text("Start XMRig").on_disabled_hover_text(text).clicked() { + if key.is_up() || ui.add_sized([width, height], Button::new("▶")).on_hover_text("Start XMRig").on_disabled_hover_text(text).clicked() { Helper::start_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path, Arc::clone(&self.sudo)); } #[cfg(target_family = "unix")] - if ui.add_sized([width, height], Button::new("▶")).on_hover_text("Start XMRig").on_disabled_hover_text(text).clicked() { + if key.is_up() || ui.add_sized([width, height], Button::new("▶")).on_hover_text("Start XMRig").on_disabled_hover_text(text).clicked() { self.sudo.lock().unwrap().signal = ProcessSignal::Start; self.error_state.ask_sudo(&self.sudo); } @@ -1346,29 +1453,37 @@ impl eframe::App for App { match self.tab { Tab::About => { debug!("App | Entering [About] Tab"); + let width = self.width; + let height = self.height/30.0; + let max_height = self.height; ui.add_space(10.0); ui.vertical_centered(|ui| { - // Display [Gupax] banner at max, 1/4 the available length - self.img.banner.show_max_size(ui, Vec2::new(self.width, self.height/4.0)); - ui.label("Gupax is a cross-platform GUI for mining"); - ui.hyperlink_to("[Monero]", "https://www.github.com/monero-project/monero"); - ui.label("on the decentralized"); - ui.hyperlink_to("[P2Pool]", "https://www.github.com/SChernykh/p2pool"); - ui.label("using the dedicated"); - ui.hyperlink_to("[XMRig]", "https://www.github.com/xmrig/xmrig"); - ui.label("miner for max hashrate"); + ui.set_max_height(max_height); + // Display [Gupax] banner + self.img.banner.show_max_size(ui, Vec2::new(width, height*3.0)); + ui.add_sized([width, height], Label::new("Gupax is a cross-platform GUI for mining")); + ui.add_sized([width, height], Hyperlink::from_label_and_url("[Monero]", "https://www.github.com/monero-project/monero")); + ui.add_sized([width, height], Label::new("on")); + ui.add_sized([width, height], Hyperlink::from_label_and_url("[P2Pool]", "https://www.github.com/SChernykh/p2pool")); + ui.add_sized([width, height], Label::new("using")); + ui.add_sized([width, height], Hyperlink::from_label_and_url("[XMRig]", "https://www.github.com/xmrig/xmrig")); - ui.add_space(ui.available_height()/1.8); - ui.hyperlink_to("Powered by egui", "https://github.com/emilk/egui"); - ui.hyperlink_to("Made by hinto-janaiyo".to_string(), "https://gupax.io"); - ui.label("egui is licensed under MIT & Apache-2.0"); - ui.label("Gupax, P2Pool, and XMRig are licensed under GPLv3"); - if cfg!(debug_assertions) { ui.label(format!("{}", self.now.elapsed().as_secs_f64())); } + ui.add_space(SPACE*3.0); + ui.style_mut().override_text_style = Some(Monospace); + ui.add_sized([width, height], Label::new(KEYBOARD_SHORTCUTS)); + ui.style_mut().override_text_style = Some(Body); + ui.add_space(SPACE*3.0); + + ui.add_sized([width, height], Hyperlink::from_label_and_url("Powered by egui", "https://github.com/emilk/egui")); + ui.add_sized([width, height], Hyperlink::from_label_and_url("Made by hinto-janaiyo".to_string(), "https://gupax.io")); + ui.add_sized([width, height], Label::new("egui is licensed under MIT & Apache-2.0")); + ui.add_sized([width, height], Label::new("Gupax, P2Pool, and XMRig are licensed under GPLv3")); + if cfg!(debug_assertions) { ui.label(format!("Gupax is running in debug mode - {}", self.now.elapsed().as_secs_f64())); } }); } Tab::Status => { debug!("App | Entering [Status] Tab"); - Status::show(&self.pub_sys, &self.p2pool_api, &self.xmrig_api, &self.p2pool_img, &self.xmrig_img, self.p2pool.lock().unwrap().is_alive(), self.xmrig.lock().unwrap().is_alive(), self.width, self.height, ctx, ui); + Status::show(&self.pub_sys, &self.p2pool_api, &self.xmrig_api, &self.p2pool_img, &self.xmrig_img, p2pool_is_alive, xmrig_is_alive, self.width, self.height, ctx, ui); } Tab::Gupax => { debug!("App | Entering [Gupax] Tab"); diff --git a/src/status.rs b/src/status.rs index e45d4b4..73b15f9 100644 --- a/src/status.rs +++ b/src/status.rs @@ -36,7 +36,7 @@ use egui::{ pub struct Status {} impl Status { -pub fn show(sys: &Arc<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>, xmrig_api: &Arc<Mutex<PubXmrigApi>>, p2pool_img: &Arc<Mutex<ImgP2pool>>, xmrig_img: &Arc<Mutex<ImgXmrig>>, p2pool_online: bool, xmrig_online: bool, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) { +pub fn show(sys: &Arc<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>, xmrig_api: &Arc<Mutex<PubXmrigApi>>, p2pool_img: &Arc<Mutex<ImgP2pool>>, xmrig_img: &Arc<Mutex<ImgXmrig>>, p2pool_alive: bool, xmrig_alive: bool, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) { let width = (width/3.0)-(SPACE*1.666); let min_height = height/1.14; let height = height/25.0; @@ -64,7 +64,7 @@ pub fn show(sys: &Arc<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>, xmrig_ // [P2Pool] ui.group(|ui| { ui.vertical(|ui| { debug!("Status Tab | Rendering [P2Pool]"); - ui.set_enabled(p2pool_online); + ui.set_enabled(p2pool_alive); ui.set_min_height(min_height); ui.add_sized([width, height*2.0], Label::new(RichText::new("[P2Pool]").color(LIGHT_GRAY).text_style(TextStyle::Name("MonospaceLarge".into())))).on_hover_text("P2Pool is online").on_disabled_hover_text("P2Pool is offline"); let api = p2pool_api.lock().unwrap(); @@ -89,7 +89,7 @@ pub fn show(sys: &Arc<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>, xmrig_ // [XMRig] ui.group(|ui| { ui.vertical(|ui| { debug!("Status Tab | Rendering [XMRig]"); - ui.set_enabled(xmrig_online); + ui.set_enabled(xmrig_alive); ui.set_min_height(min_height); ui.add_sized([width, height*2.0], Label::new(RichText::new("[XMRig]").color(LIGHT_GRAY).text_style(TextStyle::Name("MonospaceLarge".into())))).on_hover_text("XMRig is online").on_disabled_hover_text("XMRig is offline"); let api = xmrig_api.lock().unwrap();