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).
This commit is contained in:
hinto-janaiyo 2022-12-13 09:39:09 -05:00
parent 128fa500bb
commit 79b0361152
No known key found for this signature in database
GPG key ID: B1C5A64B80691E45
6 changed files with 229 additions and 77 deletions

12
Cargo.lock generated
View file

@ -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",

View file

@ -2,7 +2,7 @@
![banner.png](https://github.com/hinto-janaiyo/gupax/blob/main/images/banner.png)
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)

View file

@ -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

View file

@ -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(), &regex);
@ -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 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);

View file

@ -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");

View file

@ -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();