From 29445e1bd12e61c26dbcf5e316f88a41fdaee0a2 Mon Sep 17 00:00:00 2001 From: hinto-janaiyo Date: Fri, 16 Dec 2022 09:47:16 -0500 Subject: [PATCH] keys/p2pool/xmrig: change key shortcuts, check for [ACCEPTABLE_*] Keyboard shortcuts [Left] & [Right] were clobbering the [TextEdit] left/right movement, so they are now [Z/X]. The shortcuts also look to make sure a TextEdit isn't in focus so that [S/R/Z/X] can actually be typed. P2Pool/XMRig now make sure the path ends with an [ACCEPTABLE_*] value (e.g: P2pool, P2POOL) before enabling the [Start] UI. --- README.md | 22 +++++++++++++++ src/README.md | 1 - src/constants.rs | 33 ++++++++++++++--------- src/gupax.rs | 36 ++++++++++++------------- src/main.rs | 70 +++++++++++++++++++++++++++++------------------- src/update.rs | 33 +++++++++++++++++++++++ 6 files changed, 135 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index c7fb4d0..b38c616 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Gupax is a (Windows|macOS|Linux) GUI for mining [**Monero**](https://github.com/ * [Advanced](#Advanced) - [Verifying](#Verifying) - [Command Line](#Command-Line) + - [Key Shortcuts](#Key-Shortcuts) - [Resolution](#Resolution) - [Tor/Arti](#TorArti) - [Logs](#Logs) @@ -186,6 +187,27 @@ By default, Gupax has `auto-update` & `auto-ping` enabled. This can only be turn --- +### Key Shortcuts +The letter keys (Z/X/S/R) will only work if nothing is in focus, i.e, you _are not_ editing a text box. + +An ALT+F4 will also trigger the exit confirm screen (if enabled). +``` +*---------------------------------------* +| Key shortcuts | +|---------------------------------------| +| F11 | Fullscreen | +| Escape | Quit screen | +| Up | Start/Restart | +| Down | Stop | +| Z | Switch to Left Tab | +| X | Switch to Right Tab | +| S | Save | +| R | Reset | +*---------------------------------------* +``` + +--- + ### Resolution The default resolution of Gupax is `1280x960` which is a `4:3` aspect ratio. diff --git a/src/README.md b/src/README.md index 9e7aa7d..7e289d9 100644 --- a/src/README.md +++ b/src/README.md @@ -2,7 +2,6 @@ * [Structure](#Structure) * [Thread Model](#Thread-Model) * [Bootstrap](#Bootstrap) -* [Disk](#Disk) * [Scale](#Scale) * [Naming Scheme](#naming-scheme) * [Sudo](#Sudo) diff --git a/src/constants.rs b/src/constants.rs index b14b094..ea2d715 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -42,17 +42,18 @@ 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 | -*--------------------------------------*"#; +r#"*---------------------------------------* +| Key shortcuts | +|---------------------------------------| +| F11 | Fullscreen | +| Escape | Quit screen | +| Up | Start/Restart | +| Down | Stop | +| Z | Switch to Left Tab | +| X | Switch to Right Tab | +| 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 @@ -219,7 +220,10 @@ pub const P2POOL_NAME: &str = "Add a unique name to identify this node; Only [A- pub const P2POOL_NODE_IP: &str = "Specify the Monero Node IP to connect to with P2Pool; It must be a valid IPv4 address or a valid domain name; Max length = 255 characters"; pub const P2POOL_RPC_PORT: &str = "Specify the RPC port of the Monero node; [1-65535]"; pub const P2POOL_ZMQ_PORT: &str = "Specify the ZMQ port of the Monero node; [1-65535]"; -pub const P2POOL_PATH_NOT_EXE: &str = "P2Pool binary not found at the given path in the Gupax tab!"; +pub const P2POOL_PATH_NOT_FILE: &str = "P2Pool binary not found at the given PATH in the Gupax tab!"; +pub const P2POOL_PATH_NOT_VALID: &str = "P2Pool binary at the given PATH in the Gupax tab doesn't look like P2Pool!"; +pub const P2POOL_PATH_OK: &str = "P2Pool was found at the given PATH"; +pub const P2POOL_PATH_EMPTY: &str = "P2Pool PATH is empty"; // Node/Pool list pub const LIST_ADD: &str = "Add the current values to the list"; @@ -259,7 +263,10 @@ pub const XMRIG_API_PORT: &str = "Specify which port to bind to for XMRig's HTTP pub const XMRIG_TLS: &str = "Enable SSL/TLS connections (needs pool support)"; pub const XMRIG_KEEPALIVE: &str = "Send keepalive packets to prevent timeout (needs pool support)"; pub const XMRIG_THREADS: &str = "Number of CPU threads to use for mining"; -pub const XMRIG_PATH_NOT_EXE: &str = "XMRig binary not found at the given path in the Gupax tab!"; +pub const XMRIG_PATH_NOT_FILE: &str = "XMRig binary not found at the given PATH in the Gupax tab!"; +pub const XMRIG_PATH_NOT_VALID: &str = "XMRig binary at the given PATH in the Gupax tab doesn't look like XMRig!"; +pub const XMRIG_PATH_OK: &str = "XMRig was found at the given PATH"; +pub const XMRIG_PATH_EMPTY: &str = "XMRig PATH is empty"; // CLI argument messages pub const ARG_HELP: &str = diff --git a/src/gupax.rs b/src/gupax.rs index b4ebe82..ad1f849 100644 --- a/src/gupax.rs +++ b/src/gupax.rs @@ -143,18 +143,15 @@ impl Gupax { let text_edit = (ui.available_width()/10.0)-SPACE; ui.horizontal(|ui| { if self.p2pool_path.is_empty() { - ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ➖").color(LIGHT_GRAY))); + ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ➖").color(LIGHT_GRAY))).on_hover_text(P2POOL_PATH_EMPTY); } else { - match crate::disk::into_absolute_path(self.p2pool_path.clone()) { - Ok(path) => { - if path.is_file() { - ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ✔").color(GREEN))) - } else { - ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ❌").color(RED))) - } - }, - _ => ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ❌").color(RED))), - }; + if !Self::path_is_file(&self.p2pool_path) { + ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ❌").color(RED))).on_hover_text(P2POOL_PATH_NOT_FILE); + } else if !crate::update::check_p2pool_path(&self.p2pool_path) { + ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ❌").color(RED))).on_hover_text(P2POOL_PATH_NOT_VALID); + } else { + ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ✔").color(GREEN))).on_hover_text(P2POOL_PATH_OK); + } } ui.spacing_mut().text_edit_width = ui.available_width() - SPACE; ui.set_enabled(!file_window.lock().unwrap().thread); @@ -165,12 +162,15 @@ impl Gupax { }); ui.horizontal(|ui| { if self.xmrig_path.is_empty() { - ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ➖").color(LIGHT_GRAY))); + ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ➖").color(LIGHT_GRAY))).on_hover_text(XMRIG_PATH_EMPTY); } else { - match Self::path_is_exe(&self.xmrig_path) { - true => ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ✔").color(GREEN))), - false => ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ❌").color(RED))), - }; + if !Self::path_is_file(&self.xmrig_path) { + ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ❌").color(RED))).on_hover_text(XMRIG_PATH_NOT_FILE); + } else if !crate::update::check_xmrig_path(&self.xmrig_path) { + ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ❌").color(RED))).on_hover_text(XMRIG_PATH_NOT_VALID); + } else { + ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ✔").color(GREEN))).on_hover_text(XMRIG_PATH_OK); + } } ui.spacing_mut().text_edit_width = ui.available_width() - SPACE; ui.set_enabled(!file_window.lock().unwrap().thread); @@ -250,8 +250,8 @@ impl Gupax { })}); } - // Checks if a path is a valid path to an executable. - pub fn path_is_exe(path: &str) -> bool { + // Checks if a path is a valid path to a file. + pub fn path_is_file(path: &str) -> bool { let path = path.to_string(); match crate::disk::into_absolute_path(path) { Ok(path) => path.is_file(), diff --git a/src/main.rs b/src/main.rs index c7eff28..3a64c6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -578,11 +578,11 @@ impl Regexes { #[derive(Debug,Clone,Eq,PartialEq)] enum KeyPressed { F11, - Left, - Right, Up, Down, Esc, + Z, + X, S, R, None, @@ -592,11 +592,11 @@ impl KeyPressed { fn is_f11(&self) -> bool { *self == Self::F11 } - fn is_left(&self) -> bool { - *self == Self::Left + fn is_z(&self) -> bool { + *self == Self::Z } - fn is_right(&self) -> bool { - *self == Self::Right + fn is_x(&self) -> bool { + *self == Self::X } fn is_up(&self) -> bool { *self == Self::Up @@ -714,8 +714,10 @@ fn init_auto(app: &mut App) { if app.state.gupax.auto_p2pool { if !Regexes::addr_ok(&app.regex, &app.state.p2pool.address) { warn!("Gupax | P2Pool address is not valid! Skipping auto-p2pool..."); - } else if !Gupax::path_is_exe(&app.state.gupax.p2pool_path) { - warn!("Gupax | P2Pool path is not an executable! Skipping auto-p2pool..."); + } else if !Gupax::path_is_file(&app.state.gupax.p2pool_path) { + warn!("Gupax | P2Pool path is not a file! Skipping auto-p2pool..."); + } else if !crate::update::check_p2pool_path(&app.state.gupax.p2pool_path) { + warn!("Gupax | P2Pool path is not valid! Skipping auto-p2pool..."); } else { Helper::start_p2pool(&app.helper, &app.state.p2pool, &app.state.gupax.absolute_p2pool_path); } @@ -725,8 +727,10 @@ fn init_auto(app: &mut App) { // [Auto-XMRig] if app.state.gupax.auto_xmrig { - if !Gupax::path_is_exe(&app.state.gupax.xmrig_path) { + if !Gupax::path_is_file(&app.state.gupax.xmrig_path) { warn!("Gupax | XMRig path is not an executable! Skipping auto-xmrig..."); + } else if !crate::update::check_xmrig_path(&app.state.gupax.xmrig_path) { + warn!("Gupax | XMRig path is not valid! Skipping auto-xmrig..."); } else if cfg!(windows) { Helper::start_xmrig(&app.helper, &app.state.xmrig, &app.state.gupax.absolute_xmrig_path, Arc::clone(&app.sudo)); } else { @@ -917,10 +921,10 @@ impl eframe::App for App { let 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::Z) { + KeyPressed::Z + } else if input.consume_key(Modifiers::NONE, Key::X) { + KeyPressed::X } else if input.consume_key(Modifiers::NONE, Key::ArrowUp) { KeyPressed::Up } else if input.consume_key(Modifiers::NONE, Key::ArrowDown) { @@ -936,12 +940,16 @@ impl eframe::App for App { } }; drop(input); + // Check if egui wants keyboard input. + // This prevents keyboard shortcuts from clobbering TextEdits. + // (Typing S in text would always [Save] instead) + let wants_input = ctx.wants_keyboard_input(); if key.is_f11() { let info = frame.info(); frame.set_fullscreen(!info.window_info.fullscreen); // Change Tabs LEFT - } else if key.is_left() { + } else if key.is_z() && !wants_input { match self.tab { Tab::About => self.tab = Tab::Xmrig, Tab::Status => self.tab = Tab::About, @@ -950,7 +958,7 @@ impl eframe::App for App { Tab::Xmrig => self.tab = Tab::P2pool, }; // Change Tabs RIGHT - } else if key.is_right() { + } else if key.is_x() && !wants_input { match self.tab { Tab::About => self.tab = Tab::Status, Tab::Status => self.tab = Tab::Gupax, @@ -1294,7 +1302,7 @@ impl eframe::App for App { ui.group(|ui| { ui.set_enabled(self.diff); let width = width / 2.0; - if key.is_r() || ui.add_sized([width, height], Button::new("Reset")).on_hover_text("Reset changes").clicked() { + if key.is_r() && !wants_input || 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; @@ -1302,7 +1310,7 @@ impl eframe::App for App { self.node_vec = self.og_node_vec.clone(); self.pool_vec = self.og_pool_vec.clone(); } - if key.is_s() || ui.add_sized([width, height], Button::new("Save")).on_hover_text("Save changes").clicked() { + if key.is_s() && !wants_input || 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(); @@ -1359,10 +1367,10 @@ impl eframe::App for App { ui.add_sized([width, height], Button::new("▶")).on_disabled_hover_text("Start P2Pool"); }); } else if p2pool_is_alive { - if key.is_up() || ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart P2Pool").clicked() { + if key.is_up() && !wants_input || 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 key.is_down() || ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop P2Pool").clicked() { + if key.is_down() && !wants_input || ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop P2Pool").clicked() { Helper::stop_p2pool(&self.helper); } ui.add_enabled_ui(false, |ui| { @@ -1379,12 +1387,15 @@ impl eframe::App for App { if !Regexes::addr_ok(&self.regex, &self.state.p2pool.address) { ui_enabled = false; text = P2POOL_ADDRESS.to_string(); - } else if !Gupax::path_is_exe(&self.state.gupax.p2pool_path) { + } else if !Gupax::path_is_file(&self.state.gupax.p2pool_path) { ui_enabled = false; - text = P2POOL_PATH_NOT_EXE.to_string(); + text = P2POOL_PATH_NOT_FILE.to_string(); + } else if !crate::update::check_p2pool_path(&self.state.gupax.p2pool_path) { + ui_enabled = false; + text = P2POOL_PATH_NOT_VALID.to_string(); } ui.set_enabled(ui_enabled); - if (ui_enabled && key.is_up()) || ui.add_sized([width, height], Button::new("▶")).on_hover_text("Start P2Pool").on_disabled_hover_text(text).clicked() { + if (ui_enabled && key.is_up() && !wants_input) || 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); } } @@ -1410,7 +1421,7 @@ impl eframe::App for App { ui.add_sized([width, height], Button::new("▶")).on_disabled_hover_text("Start XMRig"); }); } else if xmrig_is_alive { - if key.is_up() || ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart XMRig").clicked() { + if key.is_up() && !wants_input || 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 { @@ -1418,7 +1429,7 @@ impl eframe::App for App { self.error_state.ask_sudo(&self.sudo); } } - if key.is_down() || ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop XMRig").clicked() { + if key.is_down() && !wants_input || 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); @@ -1436,17 +1447,20 @@ impl eframe::App for App { }); let mut text = String::new(); let mut ui_enabled = true; - if !Gupax::path_is_exe(&self.state.gupax.xmrig_path) { + if !Gupax::path_is_file(&self.state.gupax.xmrig_path) { ui_enabled = false; - text = XMRIG_PATH_NOT_EXE.to_string(); + text = XMRIG_PATH_NOT_FILE.to_string(); + } else if !crate::update::check_xmrig_path(&self.state.gupax.xmrig_path) { + ui_enabled = false; + text = XMRIG_PATH_NOT_VALID.to_string(); } ui.set_enabled(ui_enabled); #[cfg(target_os = "windows")] - if (ui_enabled && key.is_up()) || ui.add_sized([width, height], Button::new("▶")).on_hover_text("Start XMRig").on_disabled_hover_text(text).clicked() { + if (ui_enabled && key.is_up() && !wants_input) || 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_enabled && key.is_up()) || ui.add_sized([width, height], Button::new("▶")).on_hover_text("Start XMRig").on_disabled_hover_text(text).clicked() { + if (ui_enabled && key.is_up() && !wants_input) || 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); } diff --git a/src/update.rs b/src/update.rs index fa39538..f7a021f 100644 --- a/src/update.rs +++ b/src/update.rs @@ -168,6 +168,39 @@ const UPGRADE: &str = "----------------- Upgrade ------------------"; //const MSG_EXTRACT: &'static str = "Extracting packages"; //const MSG_UPGRADE: &'static str = "Upgrading packages"; +//---------------------------------------------------------------------------------------------------- General functions +pub fn check_p2pool_path(path: &str) -> bool { + let path = match crate::disk::into_absolute_path(path.to_string()) { + Ok(p) => p, + Err(e) => return false, + }; + let path = match path.file_name() { + Some(p) => p, + None => { error!("Couldn't get P2Pool file name"); return false; }, + }; + if path == ACCEPTABLE_P2POOL[0] || path == ACCEPTABLE_P2POOL[1] || path == ACCEPTABLE_P2POOL[2] || path == ACCEPTABLE_P2POOL[3] { + true + } else { + false + } +} + +pub fn check_xmrig_path(path: &str) -> bool { + let path = match crate::disk::into_absolute_path(path.to_string()) { + Ok(p) => p, + Err(e) => return false, + }; + let path = match path.file_name() { + Some(p) => p, + None => { error!("Couldn't get XMRig file name"); return false; }, + }; + if path == ACCEPTABLE_XMRIG[0] || path == ACCEPTABLE_XMRIG[1] || path == ACCEPTABLE_XMRIG[2] || path == ACCEPTABLE_XMRIG[3] { + true + } else { + false + } +} + //---------------------------------------------------------------------------------------------------- Update struct/impl // Contains values needed during update // Progress bar structure: