gupaxx/src/main.rs

1293 lines
52 KiB
Rust
Raw Normal View History

2022-10-01 16:58:22 +00:00
// Gupax - GUI Uniting P2Pool And XMRig
//
// Copyright (c) 2022 hinto-janaiyo
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// Hide console in Windows
2022-11-22 02:01:50 +00:00
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
//---------------------------------------------------------------------------------------------------- Imports
// egui/eframe
use egui::{
TextStyle::*,
color::Color32,
FontFamily::Proportional,
TextStyle,Spinner,
Layout,Align,
FontId,Label,RichText,Stroke,Vec2,Button,SelectableLabel,
Key,Modifiers,TextEdit,
CentralPanel,TopBottomPanel,
Hyperlink,
};
2022-10-01 16:58:22 +00:00
use egui_extras::RetainedImage;
use eframe::{egui,NativeOptions};
// Logging
2022-10-01 16:58:22 +00:00
use log::*;
use env_logger::{Builder,WriteStyle};
// Regex
use regex::Regex;
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
// Serde
use serde::{Serialize,Deserialize};
// std
use std::{
env,
io::Write,
process::exit,
sync::{Arc,Mutex},
time::Instant,
path::PathBuf,
};
// Modules
2022-10-18 19:26:21 +00:00
mod ferris;
2022-10-01 16:58:22 +00:00
mod constants;
mod node;
mod disk;
2022-10-01 16:58:22 +00:00
mod status;
mod gupax;
mod p2pool;
mod xmrig;
mod update;
mod helper;
use {ferris::*,constants::*,node::*,disk::*,status::*,update::*,gupax::*,helper::*};
2022-10-01 16:58:22 +00:00
// Sudo (dummy values for Windows)
mod sudo;
use crate::sudo::*;
#[cfg(target_family = "unix")]
extern crate sudo as sudo_check;
//---------------------------------------------------------------------------------------------------- Struct + Impl
// The state of the outer main [App].
// See the [State] struct in [state.rs] for the
// actual inner state of the tab settings.
2022-10-01 16:58:22 +00:00
pub struct App {
// Misc state
tab: Tab, // What tab are we on?
width: f32, // Top-level width
height: f32, // Top-level height
// Alpha (transparency)
// This value is used to incrementally increase/decrease
// the transparency when resizing. Basically, it fades
// in/out of black to hide jitter when resizing with [init_text_styles()]
alpha: u8,
// This is a one time trigger so [init_text_styles()] isn't
// called 60x a second when resizing the window. Instead,
// it only gets called if this bool is true and the user
// is hovering over egui (ctx.is_pointer_over_area()).
must_resize: bool, // Sets the flag so we know to [init_text_styles()]
resizing: bool, // Are we in the process of resizing? (For black fade in/out)
// State
og: Arc<Mutex<State>>, // og = Old state to compare against
state: State, // state = Working state (current settings)
update: Arc<Mutex<Update>>, // State for update data [update.rs]
file_window: Arc<Mutex<FileWindow>>, // State for the path selector in [Gupax]
ping: Arc<Mutex<Ping>>, // Ping data found in [node.rs]
og_node_vec: Vec<(String, Node)>, // Manual Node database
node_vec: Vec<(String, Node)>, // Manual Node database
og_pool_vec: Vec<(String, Pool)>, // Manual Pool database
pool_vec: Vec<(String, Pool)>, // Manual Pool database
diff: bool, // This bool indicates state changes
// Restart state:
// If Gupax updated itself, this represents that the
// user should (but isn't required to) restart Gupax.
restart: Arc<Mutex<Restart>>,
// Error State:
// These values are essentially global variables that
// indicate if an error message needs to be displayed
// (it takes up the whole screen with [error_msg] and buttons for ok/quit/etc)
error_state: ErrorState,
// Helper/API State:
// This holds everything related to the data processed by the "helper thread".
// This includes the "helper" threads public P2Pool/XMRig's API.
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
helper: Arc<Mutex<Helper>>, // [Helper] state, mostly for Gupax uptime
p2pool: Arc<Mutex<Process>>, // [P2Pool] process state
xmrig: Arc<Mutex<Process>>, // [XMRig] process state
p2pool_api: Arc<Mutex<PubP2poolApi>>, // Public ready-to-print P2Pool API made by the "helper" thread
xmrig_api: Arc<Mutex<PubXmrigApi>>, // Public ready-to-print XMRig API made by the "helper" thread
p2pool_img: Arc<Mutex<ImgP2pool>>, // A one-time snapshot of what data P2Pool started with
xmrig_img: Arc<Mutex<ImgXmrig>>, // A one-time snapshot of what data XMRig started with
// Buffer State
p2pool_console: String, // The buffer between the p2pool console and the [Helper]
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
xmrig_console: String, // The buffer between the xmrig console and the [Helper]
// Sudo State
sudo: Arc<Mutex<SudoState>>, // This is just a dummy struct on [Windows].
2022-10-16 21:29:24 +00:00
// State from [--flags]
no_startup: bool,
// Static stuff
2022-10-16 21:29:24 +00:00
now: Instant, // Internal timer
2022-10-19 18:35:32 +00:00
exe: String, // Path for [Gupax] binary
2022-11-02 22:18:41 +00:00
dir: String, // Directory [Gupax] binary is in
2022-10-16 21:29:24 +00:00
resolution: Vec2, // Frame resolution
os: &'static str, // OS
admin: bool, // Are we admin? (for Windows)
os_data_path: PathBuf, // OS data path (e.g: ~/.local/share/gupax)
state_path: PathBuf, // State file path
node_path: PathBuf, // Node file path
pool_path: PathBuf, // Pool file path
version: &'static str, // Gupax version
2022-10-16 21:29:24 +00:00
name_version: String, // [Gupax vX.X.X]
img: Images, // Custom Struct holding pre-compiled bytes of [Images]
regex: Regexes, // Custom Struct holding pre-made [Regex]'s
2022-10-01 16:58:22 +00:00
}
impl App {
2022-10-16 21:29:24 +00:00
fn cc(cc: &eframe::CreationContext<'_>, app: Self) -> Self {
let resolution = cc.integration_info.window_info.size;
init_text_styles(&cc.egui_ctx, resolution[0] as f32);
2022-10-01 16:58:22 +00:00
Self {
2022-10-16 21:29:24 +00:00
resolution,
..app
}
}
fn new(now: Instant) -> Self {
info!("Initializing App Struct...");
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
let p2pool = Arc::new(Mutex::new(Process::new(ProcessName::P2pool, String::new(), PathBuf::new())));
let xmrig = Arc::new(Mutex::new(Process::new(ProcessName::Xmrig, String::new(), PathBuf::new())));
let p2pool_api = Arc::new(Mutex::new(PubP2poolApi::new()));
let xmrig_api = Arc::new(Mutex::new(PubXmrigApi::new()));
let p2pool_img = Arc::new(Mutex::new(ImgP2pool::new()));
let xmrig_img = Arc::new(Mutex::new(ImgXmrig::new()));
let mut app = Self {
tab: Tab::default(),
ping: Arc::new(Mutex::new(Ping::new())),
width: APP_DEFAULT_WIDTH,
height: APP_DEFAULT_HEIGHT,
must_resize: false,
og: Arc::new(Mutex::new(State::new())),
state: State::new(),
2022-11-02 22:18:41 +00:00
update: Arc::new(Mutex::new(Update::new(String::new(), PathBuf::new(), PathBuf::new(), true))),
file_window: FileWindow::new(),
og_node_vec: Node::new_vec(),
node_vec: Node::new_vec(),
og_pool_vec: Pool::new_vec(),
pool_vec: Pool::new_vec(),
restart: Arc::new(Mutex::new(Restart::No)),
diff: false,
error_state: ErrorState::new(),
helper: Arc::new(Mutex::new(Helper::new(now, p2pool.clone(), xmrig.clone(), p2pool_api.clone(), xmrig_api.clone(), p2pool_img.clone(), xmrig_img.clone()))),
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
p2pool,
xmrig,
p2pool_api,
xmrig_api,
p2pool_img,
xmrig_img,
p2pool_console: String::with_capacity(10),
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
xmrig_console: String::with_capacity(10),
sudo: Arc::new(Mutex::new(SudoState::new())),
resizing: false,
alpha: 0,
no_startup: false,
now,
admin: false,
exe: String::new(),
dir: String::new(),
resolution: Vec2::new(APP_DEFAULT_HEIGHT, APP_DEFAULT_WIDTH),
os: OS,
os_data_path: PathBuf::new(),
state_path: PathBuf::new(),
node_path: PathBuf::new(),
pool_path: PathBuf::new(),
version: GUPAX_VERSION,
name_version: format!("Gupax {}", GUPAX_VERSION),
img: Images::new(),
regex: Regexes::new(),
2022-10-16 21:29:24 +00:00
};
//---------------------------------------------------------------------------------------------------- App init data that *could* panic
let mut panic = String::new();
2022-11-02 22:18:41 +00:00
// Get exe path
app.exe = match get_exe() {
2022-10-19 18:35:32 +00:00
Ok(exe) => exe,
Err(e) => { panic = format!("get_exe(): {}", e); app.error_state.set(panic.clone(), ErrorFerris::Panic, ErrorButtons::Quit); String::new() },
2022-10-19 18:35:32 +00:00
};
2022-11-02 22:18:41 +00:00
// Get exe directory path
app.dir = match get_exe_dir() {
Ok(dir) => dir,
Err(e) => { panic = format!("get_exe_dir(): {}", e); app.error_state.set(panic.clone(), ErrorFerris::Panic, ErrorButtons::Quit); String::new() },
2022-11-02 22:18:41 +00:00
};
// Get OS data path
app.os_data_path = match get_gupax_data_path() {
Ok(dir) => dir,
Err(e) => { panic = format!("get_os_data_path(): {}", e); app.error_state.set(panic.clone(), ErrorFerris::Panic, ErrorButtons::Quit); PathBuf::new() },
};
// Set [*.toml] path
app.state_path = app.os_data_path.clone();
app.state_path.push("state.toml");
app.node_path = app.os_data_path.clone();
app.node_path.push("node.toml");
app.pool_path = app.os_data_path.clone();
app.pool_path.push("pool.toml");
// Apply arg state
// It's not safe to [--reset] if any of the previous variables
// are unset (null path), so make sure we just abort if the [panic] String contains something.
let mut app = parse_args(app, panic);
// Read disk state
use TomlError::*;
app.state = match State::get(&app.state_path) {
Ok(toml) => toml,
Err(err) => {
error!("State ... {}", err);
match err {
Io(e) => app.error_state.set(format!("State file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Path(e) => app.error_state.set(format!("State file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Serialize(e) => app.error_state.set(format!("State file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Deserialize(e) => app.error_state.set(format!("State file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
2022-11-20 19:46:43 +00:00
Format(e) => app.error_state.set(format!("State file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Merge(e) => app.error_state.set(format!("State file: {}", e), ErrorFerris::Error, ErrorButtons::ResetState),
};
State::new()
},
};
app.og = Arc::new(Mutex::new(app.state.clone()));
// Read node list
app.og_node_vec = match Node::get(&app.node_path) {
Ok(toml) => toml,
Err(err) => {
error!("Node ... {}", err);
match err {
Io(e) => app.error_state.set(format!("Node list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Path(e) => app.error_state.set(format!("Node list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Serialize(e) => app.error_state.set(format!("Node list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Deserialize(e) => app.error_state.set(format!("Node list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Format(e) => app.error_state.set(format!("Node file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Merge(e) => app.error_state.set(format!("Node list: {}", e), ErrorFerris::Error, ErrorButtons::ResetState),
};
Node::new_vec()
},
};
// Read pool list
app.og_pool_vec = match Pool::get(&app.pool_path) {
Ok(toml) => toml,
Err(err) => {
error!("Pool ... {}", err);
match err {
Io(e) => app.error_state.set(format!("Pool list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Path(e) => app.error_state.set(format!("Pool list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Serialize(e) => app.error_state.set(format!("Pool list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Deserialize(e) => app.error_state.set(format!("Pool list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Format(e) => app.error_state.set(format!("Pool file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Merge(e) => app.error_state.set(format!("Pool list: {}", e), ErrorFerris::Error, ErrorButtons::ResetState),
};
Pool::new_vec()
},
};
app.pool_vec = app.og_pool_vec.clone();
//----------------------------------------------------------------------------------------------------
let mut og = app.og.lock().unwrap(); // Lock [og]
// Handle max threads
og.xmrig.max_threads = num_cpus::get();
let current = og.xmrig.current_threads;
let max = og.xmrig.max_threads;
if current > max {
og.xmrig.current_threads = max;
}
// Handle [node_vec] overflow
if og.p2pool.selected_index > app.og_node_vec.len() {
warn!("App | Overflowing manual node index [{} > {}], resetting to 1", og.p2pool.selected_index, app.og_node_vec.len());
let (name, node) = app.og_node_vec[0].clone();
og.p2pool.selected_index = 0;
og.p2pool.selected_name = name.clone();
og.p2pool.selected_ip = node.ip.clone();
og.p2pool.selected_rpc = node.rpc.clone();
og.p2pool.selected_zmq = node.zmq.clone();
app.state.p2pool.selected_index = 0;
app.state.p2pool.selected_name = name;
app.state.p2pool.selected_ip = node.ip;
app.state.p2pool.selected_rpc = node.rpc;
app.state.p2pool.selected_zmq = node.zmq;
}
// Handle [pool_vec] overflow
if og.xmrig.selected_index > app.og_pool_vec.len() {
warn!("App | Overflowing manual pool index [{} > {}], resetting to 1", og.xmrig.selected_index, app.og_pool_vec.len());
let (name, pool) = app.og_pool_vec[0].clone();
og.xmrig.selected_index = 0;
og.xmrig.selected_name = name.clone();
og.xmrig.selected_ip = pool.ip.clone();
og.xmrig.selected_port = pool.port.clone();
app.state.xmrig.selected_index = 0;
app.state.xmrig.selected_name = name;
app.state.xmrig.selected_ip = pool.ip;
app.state.xmrig.selected_port = pool.port;
}
// Apply TOML values to [Update]
let p2pool_path = og.gupax.absolute_p2pool_path.clone();
let xmrig_path = og.gupax.absolute_xmrig_path.clone();
let tor = og.gupax.update_via_tor;
2022-11-02 22:18:41 +00:00
app.update = Arc::new(Mutex::new(Update::new(app.exe.clone(), p2pool_path, xmrig_path, tor)));
2022-11-22 02:01:50 +00:00
// Set state version as compiled in version
og.version.lock().unwrap().gupax = GUPAX_VERSION.to_string();
app.state.version.lock().unwrap().gupax = GUPAX_VERSION.to_string();
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
// Set saved [Tab]
app.tab = app.state.gupax.tab;
drop(og); // Unlock [og]
info!("App ... OK");
// Spawn the "Helper" thread.
info!("Helper | Spawning helper thread...");
Helper::spawn_helper(&app.helper);
info!("Helper ... OK");
// Check for privilege. Should be Admin on [Windows] and NOT root on Unix.
#[cfg(target_os = "windows")]
if !is_elevated::is_elevated() {
app.admin = false;
error!("Windows | Admin user not detected!");
app.error_state.set(format!("Gupax was not launched as Administrator!\nBe warned, XMRig might have less hashrate!"), ErrorFerris::Sudo, ErrorButtons::WindowsAdmin);
}
#[cfg(target_family = "unix")]
if sudo_check::check() != sudo_check::RunningAs::User {
let id = sudo_check::check();
error!("Unix | Regular user not detected: [{:?}]", id);
app.error_state.set(format!("Gupax was launched as: [{:?}]\nPlease launch Gupax with regular user permissions.", id), ErrorFerris::Panic, ErrorButtons::Quit);
}
app
2022-10-01 16:58:22 +00:00
}
}
//---------------------------------------------------------------------------------------------------- [Tab] Enum + Impl
2022-10-01 16:58:22 +00:00
// The tabs inside [App].
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Tab {
2022-10-01 16:58:22 +00:00
About,
Status,
Gupax,
P2pool,
Xmrig,
}
2022-10-01 16:58:22 +00:00
impl Default for Tab {
fn default() -> Self {
Self::About
}
}
//---------------------------------------------------------------------------------------------------- [Restart] Enum
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Restart {
No, // We don't need to restart
Yes, // We updated, user should probably (but isn't required to) restart
}
//---------------------------------------------------------------------------------------------------- [ErrorState] struct
2022-11-24 04:03:56 +00:00
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2022-11-21 01:21:47 +00:00
pub enum ErrorButtons {
YesNo,
StayQuit,
ResetState,
ResetNode,
Okay,
Quit,
Sudo,
WindowsAdmin,
2022-11-21 01:21:47 +00:00
}
2022-11-24 04:03:56 +00:00
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2022-11-21 01:21:47 +00:00
pub enum ErrorFerris {
Happy,
Oops,
Error,
Panic,
Sudo,
2022-11-21 01:21:47 +00:00
}
pub struct ErrorState {
error: bool, // Is there an error?
msg: String, // What message to display?
ferris: ErrorFerris, // Which ferris to display?
buttons: ErrorButtons, // Which buttons to display?
}
impl ErrorState {
pub fn new() -> Self {
Self {
error: false,
msg: "Unknown Error".to_string(),
ferris: ErrorFerris::Oops,
buttons: ErrorButtons::Okay,
}
}
// Convenience function to enable the [App] error state
pub fn set(&mut self, msg: impl Into<String>, ferris: ErrorFerris, buttons: ErrorButtons) {
if self.error {
// If a panic error is already set, return
if self.ferris == ErrorFerris::Panic { return }
// If we shouldn't be overriding the current error, return
match self.buttons {
ErrorButtons::YesNo => (), // Not important
ErrorButtons::Okay => (), // Not important
_ => return, // Overwrite, Quits, etc
}
}
*self = Self {
error: true,
msg: msg.into(),
ferris,
buttons,
};
}
// Just sets the current state to new, resetting it.
pub fn reset(&mut self) {
*self = Self::new();
}
// Instead of creating a whole new screen and system, this (ab)uses ErrorState
// to ask for the [sudo] when starting XMRig. Yes, yes I know, it's called "ErrorState"
// but rewriting the UI code and button stuff might be worse.
// It also resets the current [SudoState]
pub fn ask_sudo(&mut self, state: &Arc<Mutex<SudoState>>) {
*self = Self {
error: true,
msg: String::new(),
ferris: ErrorFerris::Sudo,
buttons: ErrorButtons::Sudo,
};
SudoState::reset(&state)
}
}
//---------------------------------------------------------------------------------------------------- [Images] struct
struct Images {
banner: RetainedImage,
happy: RetainedImage,
oops: RetainedImage,
error: RetainedImage,
panic: RetainedImage,
sudo: RetainedImage,
}
impl Images {
fn new() -> Self {
Self {
banner: RetainedImage::from_image_bytes("banner.png", BYTES_BANNER).unwrap(),
happy: RetainedImage::from_image_bytes("happy.png", FERRIS_HAPPY).unwrap(),
oops: RetainedImage::from_image_bytes("oops.png", FERRIS_OOPS).unwrap(),
error: RetainedImage::from_image_bytes("error.png", FERRIS_ERROR).unwrap(),
panic: RetainedImage::from_image_bytes("panic.png", FERRIS_PANIC).unwrap(),
sudo: RetainedImage::from_image_bytes("panic.png", FERRIS_SUDO).unwrap(),
}
}
}
//---------------------------------------------------------------------------------------------------- [Regexes] struct
#[derive(Clone, Debug)]
2022-11-16 19:40:25 +00:00
pub struct Regexes {
pub name: Regex,
pub address: Regex,
pub ipv4: Regex,
pub domain: Regex,
pub port: Regex,
}
impl Regexes {
fn new() -> Self {
Regexes {
name: Regex::new("^[A-Za-z0-9-_]+( [A-Za-z0-9-_]+)*$").unwrap(),
address: Regex::new("^4[A-Za-z1-9]+$").unwrap(), // This still needs to check for (l, I, o, 0)
ipv4: Regex::new(r#"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"#).unwrap(),
domain: Regex::new(r#"^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$"#).unwrap(),
port: Regex::new(r#"^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$"#).unwrap(),
}
}
// Check if a Monero address is correct.
pub fn addr_ok(&self, address: &str) -> bool {
address.len() == 95 && Regex::is_match(&self.address, &address) && !address.contains('0') && !address.contains('O') && !address.contains('l')
}
}
//---------------------------------------------------------------------------------------------------- Init functions
2022-10-01 16:58:22 +00:00
fn init_text_styles(ctx: &egui::Context, width: f32) {
let scale = width / 26.666;
let mut style = (*ctx.style()).clone();
style.text_styles = [
(Small, FontId::new(scale/3.0, Proportional)),
(Body, FontId::new(scale/2.0, Proportional)),
(Button, FontId::new(scale/2.0, Proportional)),
(Monospace, FontId::new(scale/2.0, egui::FontFamily::Monospace)),
2022-10-01 16:58:22 +00:00
(Heading, FontId::new(scale/1.5, Proportional)),
(Name("Tab".into()), FontId::new(scale*1.2, Proportional)),
(Name("Bottom".into()), FontId::new(scale/2.0, Proportional)),
(Name("MonospaceSmall".into()), FontId::new(scale/2.5, egui::FontFamily::Monospace)),
(Name("MonospaceLarge".into()), FontId::new(scale/1.5, egui::FontFamily::Monospace)),
2022-10-01 16:58:22 +00:00
].into();
style.spacing.icon_width_inner = width / 35.0;
style.spacing.icon_width = width / 25.0;
style.spacing.icon_spacing = 20.0;
style.spacing.scroll_bar_width = width / 150.0;
2022-10-01 16:58:22 +00:00
ctx.set_style(style);
ctx.set_pixels_per_point(1.0);
ctx.request_repaint();
2022-10-01 16:58:22 +00:00
}
2022-11-21 01:21:47 +00:00
fn init_logger(now: Instant) {
2022-10-01 16:58:22 +00:00
use env_logger::fmt::Color;
2022-11-21 01:21:47 +00:00
Builder::new().format(move |buf, record| {
2022-10-01 16:58:22 +00:00
let mut style = buf.style();
2022-11-24 04:03:56 +00:00
let level = match record.level() {
Level::Error => { style.set_color(Color::Red); "ERROR" },
Level::Warn => { style.set_color(Color::Yellow); "WARN" },
Level::Info => { style.set_color(Color::White); "INFO" },
Level::Debug => { style.set_color(Color::Blue); "DEBUG" },
Level::Trace => { style.set_color(Color::Magenta); "TRACE" },
2022-10-01 16:58:22 +00:00
};
writeln!(
buf,
"[{}] [{}] [{}:{}] {}",
2022-10-01 16:58:22 +00:00
style.set_bold(true).value(level),
2022-11-22 02:01:50 +00:00
buf.style().set_dimmed(true).value(format!("{:.3}", now.elapsed().as_secs_f32())),
2022-10-01 16:58:22 +00:00
buf.style().set_dimmed(true).value(record.file().unwrap_or("???")),
buf.style().set_dimmed(true).value(record.line().unwrap_or(0)),
record.args(),
)
2022-11-21 01:21:47 +00:00
}).filter_level(LevelFilter::Info).write_style(WriteStyle::Always).parse_default_env().format_timestamp_millis().init();
info!("init_logger() ... OK");
}
2022-10-01 16:58:22 +00:00
fn init_options(initial_window_size: Option<Vec2>) -> NativeOptions {
2022-10-01 16:58:22 +00:00
let mut options = eframe::NativeOptions::default();
options.min_window_size = Some(Vec2::new(APP_MIN_WIDTH, APP_MIN_HEIGHT));
options.max_window_size = Some(Vec2::new(APP_MAX_WIDTH, APP_MAX_HEIGHT));
options.initial_window_size = initial_window_size;
2022-10-01 16:58:22 +00:00
options.follow_system_theme = false;
options.default_theme = eframe::Theme::Dark;
let icon = image::load_from_memory(BYTES_ICON).expect("Failed to read icon bytes").to_rgba8();
2022-10-01 16:58:22 +00:00
let (icon_width, icon_height) = icon.dimensions();
options.icon_data = Some(eframe::IconData {
rgba: icon.into_raw(),
width: icon_width,
height: icon_height,
});
info!("init_options() ... OK");
options
}
fn init_auto(app: &mut App) {
// Return early if [--no-startup] was not passed
if app.no_startup {
info!("[--no-startup] flag passed, skipping init_auto()...");
return
} else if app.error_state.error {
info!("App error detected, skipping init_auto()...");
return
} else {
info!("Starting init_auto()...");
}
2022-11-11 04:42:57 +00:00
// [Auto-Update]
if app.state.gupax.auto_update {
Update::spawn_thread(&app.og, &app.state.gupax, &app.state_path, &app.update, &mut app.error_state, &app.restart);
2022-11-11 04:42:57 +00:00
} else {
info!("Skipping auto-update...");
}
// [Auto-Ping]
let auto_node = app.og.lock().unwrap().p2pool.auto_node;
let simple = app.og.lock().unwrap().p2pool.simple;
if auto_node && simple {
Ping::spawn_thread(&app.ping)
2022-11-11 04:42:57 +00:00
} else {
info!("Skipping auto-ping...");
}
}
//---------------------------------------------------------------------------------------------------- Reset functions
fn reset_state(path: &PathBuf) -> Result<(), TomlError> {
match State::create_new(path) {
Ok(_) => { info!("Resetting [state.toml] ... OK"); Ok(()) },
Err(e) => { error!("Resetting [state.toml] ... FAIL ... {}", e); Err(e) },
}
}
fn reset_nodes(path: &PathBuf) -> Result<(), TomlError> {
match Node::create_new(path) {
Ok(_) => { info!("Resetting [node.toml] ... OK"); Ok(()) },
Err(e) => { error!("Resetting [node.toml] ... FAIL ... {}", e); Err(e) },
}
}
2022-11-25 02:03:42 +00:00
fn reset_pools(path: &PathBuf) -> Result<(), TomlError> {
match Pool::create_new(path) {
Ok(_) => { info!("Resetting [pool.toml] ... OK"); Ok(()) },
Err(e) => { error!("Resetting [pool.toml] ... FAIL ... {}", e); Err(e) },
}
}
fn reset(path: &PathBuf, state: &PathBuf, node: &PathBuf, pool: &PathBuf) {
let mut code = 0;
// Attempt to remove directory first
match std::fs::remove_dir_all(path) {
Ok(_) => info!("Removing OS data path ... OK"),
Err(e) => { error!("Removing OS data path ... FAIL ... {}", e); code = 1; },
}
2022-11-21 01:21:47 +00:00
// Recreate
match create_gupax_dir(path) {
Ok(_) => (),
Err(_) => code = 1,
}
match reset_state(state) {
Ok(_) => (),
Err(_) => code = 1,
}
match reset_nodes(node) {
Ok(_) => (),
Err(_) => code = 1,
}
2022-11-25 02:03:42 +00:00
match reset_pools(pool) {
Ok(_) => (),
Err(_) => code = 1,
}
match code {
0 => println!("\nGupax reset ... OK"),
2022-11-25 02:03:42 +00:00
_ => eprintln!("\nGupax reset ... FAIL"),
}
exit(code);
}
2022-10-16 21:29:24 +00:00
//---------------------------------------------------------------------------------------------------- Misc functions
fn parse_args<S: Into<String>>(mut app: App, panic: S) -> App {
2022-10-16 21:29:24 +00:00
info!("Parsing CLI arguments...");
let mut args: Vec<String> = env::args().collect();
if args.len() == 1 { info!("No args ... OK"); return app } else { args.remove(0); info!("Args ... {:?}", args); }
// [help/version], exit early
for arg in &args {
match arg.as_str() {
"--help" => { println!("{}", ARG_HELP); exit(0); },
"--version" => {
println!("Gupax {} [OS: {}, Commit: {}]\n\n{}", GUPAX_VERSION, OS_NAME, &COMMIT[..40], ARG_COPYRIGHT);
2022-10-16 21:29:24 +00:00
exit(0);
},
"--ferris" => { println!("{}", FERRIS_ANSI); exit(0); },
2022-10-16 21:29:24 +00:00
_ => (),
}
}
// Abort on panic
let panic = panic.into();
if ! panic.is_empty() {
info!("[Gupax error] {}", panic);
exit(1);
}
2022-10-16 21:29:24 +00:00
// Everything else
for arg in args {
match arg.as_str() {
"--state" => { info!("Printing state..."); print_disk_file(&app.state_path); }
"--nodes" => { info!("Printing node list..."); print_disk_file(&app.node_path); }
2022-11-25 02:03:42 +00:00
"--reset-state" => if let Ok(()) = reset_state(&app.state_path) { println!("\nState reset ... OK"); exit(0); } else { eprintln!("\nState reset ... FAIL"); exit(1) },
"--reset-nodes" => if let Ok(()) = reset_nodes(&app.node_path) { println!("\nNode reset ... OK"); exit(0) } else { eprintln!("\nNode reset ... FAIL"); exit(1) },
"--reset-pools" => if let Ok(()) = reset_pools(&app.pool_path) { println!("\nPool reset ... OK"); exit(0) } else { eprintln!("\nPool reset ... FAIL"); exit(1) },
"--reset-all" => reset(&app.os_data_path, &app.state_path, &app.node_path, &app.pool_path),
"--no-startup" => app.no_startup = true,
2022-11-25 02:03:42 +00:00
_ => { eprintln!("\n[Gupax error] Invalid option: [{}]\nFor help, use: [--help]", arg); exit(1); },
2022-10-16 21:29:24 +00:00
}
}
app
}
// Get absolute [Gupax] binary path
pub fn get_exe() -> Result<String, std::io::Error> {
2022-10-19 18:35:32 +00:00
match std::env::current_exe() {
2022-11-16 19:40:25 +00:00
Ok(path) => { Ok(path.display().to_string()) },
2022-11-24 04:03:56 +00:00
Err(err) => { error!("Couldn't get absolute Gupax PATH"); Err(err) },
2022-10-19 18:35:32 +00:00
}
}
// Get absolute [Gupax] directory path
pub fn get_exe_dir() -> Result<String, std::io::Error> {
match std::env::current_exe() {
Ok(mut path) => { path.pop(); Ok(path.display().to_string()) },
2022-11-24 04:03:56 +00:00
Err(err) => { error!("Couldn't get exe basepath PATH"); Err(err) },
}
}
// Clean any [gupax_update_.*] directories
// The trailing random bits must be exactly 10 alphanumeric characters
pub fn clean_dir() -> Result<(), anyhow::Error> {
let regex = Regex::new("^gupax_update_[A-Za-z0-9]{10}$").unwrap();
for entry in std::fs::read_dir(get_exe_dir()?)? {
let entry = entry?;
2022-11-02 22:18:41 +00:00
if ! entry.path().is_dir() { continue }
2022-11-24 04:03:56 +00:00
if Regex::is_match(&regex, entry.file_name().to_str().ok_or_else(|| anyhow::Error::msg("Basename failed"))?) {
let path = entry.path();
match std::fs::remove_dir_all(&path) {
Ok(_) => info!("Remove [{}] ... OK", path.display()),
Err(e) => warn!("Remove [{}] ... FAIL ... {}", path.display(), e),
}
}
}
Ok(())
}
// Print disk files to console
fn print_disk_file(path: &PathBuf) {
2022-11-24 04:03:56 +00:00
match std::fs::read_to_string(path) {
Ok(string) => { print!("{}", string); exit(0); },
Err(e) => { error!("{}", e); exit(1); },
}
}
//---------------------------------------------------------------------------------------------------- Main [App] frame
fn main() {
let now = Instant::now();
2022-11-21 01:21:47 +00:00
init_logger(now);
let mut app = App::new(now);
init_auto(&mut app);
let selected_width = app.state.gupax.selected_width as f32;
let selected_height = app.state.gupax.selected_height as f32;
let initial_window_size = if selected_width > APP_MAX_WIDTH || selected_height > APP_MAX_HEIGHT {
warn!("App | Set width or height was greater than the maximum! Starting with the default resolution...");
Some(Vec2::new(APP_DEFAULT_WIDTH, APP_DEFAULT_HEIGHT))
} else {
Some(Vec2::new(app.state.gupax.selected_width as f32, app.state.gupax.selected_height as f32))
};
let options = init_options(initial_window_size);
match clean_dir() {
Ok(_) => info!("Temporary folder cleanup ... OK"),
Err(e) => warn!("Could not cleanup [gupax_tmp] folders: {}", e),
}
2022-11-21 01:21:47 +00:00
info!("Init ... DONE");
eframe::run_native(&app.name_version.clone(), options, Box::new(|cc| Box::new(App::cc(cc, app))),);
}
2022-10-01 16:58:22 +00:00
impl eframe::App for App {
fn on_close_event(&mut self) -> bool {
if self.state.gupax.ask_before_quit {
self.error_state.set("", ErrorFerris::Oops, ErrorButtons::StayQuit);
false
} else {
true
}
}
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
// *-------*
// | DEBUG |
// *-------*
// If [F11] was pressed, reverse [fullscreen] bool
if ctx.input_mut().consume_key(Modifiers::NONE, Key::F11) {
let info = frame.info();
frame.set_fullscreen(!info.window_info.fullscreen);
}
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
// Refresh AT LEAST once a second
ctx.request_repaint_after(SECOND);
// This sets the top level Ui dimensions.
// Used as a reference for other uis.
CentralPanel::default().show(ctx, |ui| {
let available_width = ui.available_width();
if self.width != available_width {
self.width = available_width;
if self.now.elapsed().as_secs() > 5 {
self.must_resize = true;
}
};
self.height = ui.available_height();
});
// This resizes fonts/buttons/etc globally depending on the width.
// This is separate from the [self.width != available_width] logic above
// because placing [init_text_styles()] above would mean calling it 60x a second
// while the user was readjusting the frame. It's a pretty heavy operation and looks
// buggy when calling it that many times. Looking for a [must_resize] in addtion to
// checking if the user is hovering over the app means that we only have call it once.
if self.must_resize && ctx.is_pointer_over_area() {
self.resizing = true;
self.must_resize = false;
}
// This (ab)uses [Area] and [TextEdit] to overlay a full black layer over whatever UI we had before.
// It incrementally becomes more opaque until [self.alpha] >= 250, when we just switch to pure black (no alpha).
// When black, we're safe to [init_text_styles()], and then incrementally go transparent, until we remove the layer.
if self.resizing {
egui::Area::new("resize_layer").order(egui::Order::Foreground).anchor(egui::Align2::CENTER_CENTER, (0.0, 0.0)).show(ctx, |ui| {
if self.alpha < 250 {
egui::Frame::none().fill(Color32::from_rgba_premultiplied(0,0,0,self.alpha)).show(ui, |ui| {
ui.add_sized([ui.available_width()+SPACE, ui.available_height()+SPACE], egui::TextEdit::multiline(&mut ""));
});
ctx.request_repaint();
self.alpha += 10;
} else {
egui::Frame::none().fill(Color32::from_rgb(0,0,0)).show(ui, |ui| {
ui.add_sized([ui.available_width()+SPACE, ui.available_height()+SPACE], egui::TextEdit::multiline(&mut ""));
});
ctx.request_repaint();
info!("App | Resizing frame to match new internal resolution: [{}x{}]", self.width, self.height);
init_text_styles(ctx, self.width);
self.resizing = false;
}
});
} else if self.alpha != 0 {
egui::Area::new("resize_layer").order(egui::Order::Foreground).anchor(egui::Align2::CENTER_CENTER, (0.0, 0.0)).show(ctx, |ui| {
egui::Frame::none().fill(Color32::from_rgba_premultiplied(0,0,0,self.alpha)).show(ui, |ui| {
ui.add_sized([ui.available_width()+SPACE, ui.available_height()+SPACE], egui::TextEdit::multiline(&mut ""));
})
});
self.alpha -= 10;
ctx.request_repaint();
}
// If there's an error, display [ErrorState] on the whole screen until user responds
if self.error_state.error {
CentralPanel::default().show(ctx, |ui| {
ui.vertical_centered(|ui| {
// Set width/height/font
let width = self.width;
let height = self.height/4.0;
ui.style_mut().override_text_style = Some(Name("MonospaceLarge".into()));
// Display ferris
use ErrorFerris::*;
use ErrorButtons::*;
let ferris = match self.error_state.ferris {
Happy => &self.img.happy,
Oops => &self.img.oops,
Error => &self.img.error,
Panic => &self.img.panic,
ErrorFerris::Sudo => &self.img.sudo,
};
ferris.show_max_size(ui, Vec2::new(width, height));
// Error/Quit screen
match self.error_state.buttons {
StayQuit => {
let mut text = "".to_string();
if *self.update.lock().unwrap().updating.lock().unwrap() { text = format!("{}\nUpdate is in progress...!", text); }
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
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); }
ui.add_sized([width, height], Label::new("--- Are you sure you want to quit? ---"));
ui.add_sized([width, height], Label::new(text))
},
ResetState => {
ui.add_sized([width, height], Label::new(format!("--- Gupax has encountered an error! ---\n{}", &self.error_state.msg)));
ui.add_sized([width, height], Label::new("Reset Gupax state? (Your settings)"))
},
ResetNode => {
ui.add_sized([width, height], Label::new(format!("--- Gupax has encountered an error! ---\n{}", &self.error_state.msg)));
ui.add_sized([width, height], Label::new("Reset the manual node list?"))
},
ErrorButtons::Sudo => {
let text = format!("Why does XMRig need admin priviledge?\n{}", XMRIG_ADMIN_REASON);
let height = height/4.0;
ui.add_sized([width, height], Label::new(format!("--- Gupax needs sudo/admin priviledge for XMRig! ---\n{}", &self.error_state.msg)));
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
ui.add_sized([width/2.0, height], Label::new(text));
ui.add_sized([width, height], Hyperlink::from_label_and_url("Click here for more info.", "https://xmrig.com/docs/miner/randomx-optimization-guide"))
},
_ => {
match self.error_state.ferris {
Panic => ui.add_sized([width, height], Label::new("--- Gupax has encountered an un-recoverable error! ---")),
Happy => ui.add_sized([width, height], Label::new("--- Success! ---")),
_ => ui.add_sized([width, height], Label::new("--- Gupax has encountered an error! ---")),
};
let height = height/2.0;
ui.add_sized([width, height], Label::new(&self.error_state.msg));
// Show GitHub rant link for Windows admin problems.
ui.add_sized([width, height], Hyperlink::from_label_and_url("[Why does Gupax need to be Admin? (on Windows)]", "https://github.com/hinto-janaiyo/gupax/tree/main/src#why-does-gupax-need-to-be-admin-on-windows"))
},
};
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); }
},
StayQuit => {
// If [Esc] was pressed, assume [Stay]
if 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); }
},
// This code handles the [state.toml/node.toml] resetting, [panic!]'ing if it errors once more
// Another error after this either means an IO error or permission error, which Gupax can't fix.
// [Yes/No] buttons
ResetState => {
if ui.add_sized([width, height/2.0], Button::new("Yes")).clicked() {
match reset_state(&self.state_path) {
Ok(_) => {
match State::get(&self.state_path) {
Ok(s) => {
self.state = s;
self.og = Arc::new(Mutex::new(self.state.clone()));
self.error_state.set("State read OK", ErrorFerris::Happy, ErrorButtons::Okay);
},
Err(e) => self.error_state.set(format!("State read fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
}
},
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() }
},
ResetNode => {
if ui.add_sized([width, height/2.0], Button::new("Yes")).clicked() {
match reset_nodes(&self.node_path) {
Ok(_) => {
match Node::get(&self.node_path) {
Ok(s) => {
self.node_vec = s;
self.og_node_vec = self.node_vec.clone();
self.error_state.set("Node read OK", ErrorFerris::Happy, ErrorButtons::Okay);
},
Err(e) => self.error_state.set(format!("Node read fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
}
},
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() }
},
ErrorButtons::Sudo => {
let sudo_width = width/10.0;
let height = ui.available_height()/4.0;
let mut sudo = self.sudo.lock().unwrap();
let hide = sudo.hide.clone();
ui.style_mut().override_text_style = Some(Monospace);
if sudo.testing {
ui.add_sized([width, height], Spinner::new().size(height));
ui.set_enabled(false);
} else {
ui.add_sized([width, height], Label::new(&sudo.msg));
}
ui.add_space(height);
let height = ui.available_height()/5.0;
// Password input box with a hider.
ui.horizontal(|ui| {
let response = ui.add_sized([sudo_width*8.0, height], TextEdit::hint_text(TextEdit::singleline(&mut sudo.pass).password(hide), PASSWORD_TEXT));
let box_width = (ui.available_width()/2.0)-5.0;
if (response.lost_focus() && ui.input().key_pressed(Key::Enter)) ||
ui.add_sized([box_width, height], Button::new("Enter")).on_hover_text(PASSWORD_ENTER).clicked() {
if !sudo.testing {
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
SudoState::test_sudo(self.sudo.clone(), &self.helper.clone(), &self.state.xmrig, &self.state.gupax.absolute_xmrig_path);
}
}
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 || ui.add_sized([width, height*4.0], Button::new("Leave")).clicked() { self.error_state.reset(); };
// If [test_sudo()] finished, reset error state.
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
if sudo.success {
self.error_state.reset();
}
},
Okay|WindowsAdmin => if 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); },
}
})});
return
}
// Compare [og == state] and the [node_vec] and enable diff if found.
// The struct fields are compared directly because [Version]
// contains Arc<Mutex>'s that cannot be compared easily.
// They don't need to be compared anyway.
let og = self.og.lock().unwrap();
if og.gupax != self.state.gupax || og.p2pool != self.state.p2pool || og.xmrig != self.state.xmrig || self.og_node_vec != self.node_vec {
self.diff = true;
} else {
self.diff = false;
}
drop(og);
2022-10-01 16:58:22 +00:00
// Top: Tabs
TopBottomPanel::top("top").show(ctx, |ui| {
2022-10-28 19:45:13 +00:00
let width = (self.width - (SPACE*10.0))/5.0;
let height = self.height/12.0;
ui.group(|ui| {
ui.add_space(4.0);
ui.horizontal(|ui| {
let style = ui.style_mut();
style.override_text_style = Some(Name("Tab".into()));
style.visuals.widgets.inactive.fg_stroke.color = Color32::from_rgb(100, 100, 100);
style.visuals.selection.bg_fill = Color32::from_rgb(255, 120, 120);
style.visuals.selection.stroke = Stroke { width: 5.0, color: Color32::from_rgb(255, 255, 255) };
if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::About, "About")).clicked() { self.tab = Tab::About; }
ui.separator();
if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Status, "Status")).clicked() { self.tab = Tab::Status; }
ui.separator();
if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Gupax, "Gupax")).clicked() { self.tab = Tab::Gupax; }
ui.separator();
if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::P2pool, "P2Pool")).clicked() { self.tab = Tab::P2pool; }
ui.separator();
if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Xmrig, "XMRig")).clicked() { self.tab = Tab::Xmrig; }
});
ui.add_space(4.0);
2022-10-01 16:58:22 +00:00
});
});
2022-10-01 16:58:22 +00:00
// Bottom: app info + state/process buttons
TopBottomPanel::bottom("bottom").show(ctx, |ui| {
let height = self.height/20.0;
2022-10-01 16:58:22 +00:00
ui.style_mut().override_text_style = Some(Name("Bottom".into()));
ui.horizontal(|ui| {
ui.group(|ui| {
let width = ((self.width/2.0)/4.0)-(SPACE*2.0);
// [Gupax Version]
// Is yellow if the user updated and should (but isn't required to) restart.
match *self.restart.lock().unwrap() {
Restart::Yes => ui.add_sized([width, height], Label::new(RichText::new(&self.name_version).color(YELLOW))).on_hover_text(GUPAX_SHOULD_RESTART),
_ => ui.add_sized([width, height], Label::new(&self.name_version)).on_hover_text(GUPAX_UP_TO_DATE),
};
2022-10-01 16:58:22 +00:00
ui.separator();
// [OS]
// Check if admin for windows.
// Unix SHOULDN'T be running as root, and the check is done when
// [App] is initialized, so no reason to check here.
#[cfg(target_os = "windows")]
if self.admin {
ui.add_sized([width, height], Label::new(self.os));
} else {
ui.add_sized([width, height], Label::new(RichText::new(self.os).color(RED))).on_hover_text(WINDOWS_NOT_ADMIN);
}
#[cfg(target_family = "unix")]
2022-10-01 16:58:22 +00:00
ui.add_sized([width, height], Label::new(self.os));
ui.separator();
// [P2Pool/XMRig] Status
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
use ProcessState::*;
match self.p2pool.lock().unwrap().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),
};
2022-10-01 16:58:22 +00:00
ui.separator();
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
match self.xmrig.lock().unwrap().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),
Middle|Waiting => ui.add_sized([width, height], Label::new(RichText::new("XMRig ⏺").color(YELLOW))).on_hover_text(XMRIG_MIDDLE),
};
2022-10-01 16:58:22 +00:00
});
// [Save/Reset]
ui.with_layout(Layout::right_to_left(Align::RIGHT), |ui| {
let width = match self.tab {
Tab::Gupax => (ui.available_width()/2.0)-(SPACE*3.0),
_ => (ui.available_width()/3.0)-(SPACE*3.0),
};
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() {
let og = self.og.lock().unwrap().clone();
self.state.gupax = og.gupax;
self.state.p2pool = og.p2pool;
self.state.xmrig = og.xmrig;
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() {
match State::save(&mut self.state, &self.state_path) {
Ok(_) => {
let mut og = self.og.lock().unwrap();
og.gupax = self.state.gupax.clone();
og.p2pool = self.state.p2pool.clone();
og.xmrig = self.state.xmrig.clone();
},
Err(e) => {
self.error_state.set(format!("State file: {}", e), ErrorFerris::Error, ErrorButtons::Okay);
},
};
match Node::save(&self.og_node_vec, &self.node_path) {
Ok(_) => self.og_node_vec = self.node_vec.clone(),
Err(e) => self.error_state.set(format!("Node list: {}", e), ErrorFerris::Error, ErrorButtons::Okay),
};
match Pool::save(&self.og_pool_vec, &self.pool_path) {
Ok(_) => self.og_pool_vec = self.pool_vec.clone(),
Err(e) => self.error_state.set(format!("Pool list: {}", e), ErrorFerris::Error, ErrorButtons::Okay),
};
2022-10-01 16:58:22 +00:00
}
});
// [Simple/Advanced] + [Start/Stop/Restart]
match self.tab {
Tab::Gupax => {
ui.group(|ui| {
let width = width / 2.0;
if ui.add_sized([width, height], SelectableLabel::new(!self.state.gupax.simple, "Advanced")).on_hover_text(GUPAX_ADVANCED).clicked() {
self.state.gupax.simple = false;
}
ui.separator();
if ui.add_sized([width, height], SelectableLabel::new(self.state.gupax.simple, "Simple")).on_hover_text(GUPAX_SIMPLE).clicked() {
self.state.gupax.simple = true;
}
});
},
Tab::P2pool => {
ui.group(|ui| {
let width = width / 1.5;
if ui.add_sized([width, height], SelectableLabel::new(!self.state.p2pool.simple, "Advanced")).on_hover_text(P2POOL_ADVANCED).clicked() {
self.state.p2pool.simple = false;
}
ui.separator();
if ui.add_sized([width, height], SelectableLabel::new(self.state.p2pool.simple, "Simple")).on_hover_text(P2POOL_SIMPLE).clicked() {
self.state.p2pool.simple = true;
}
});
ui.group(|ui| {
let width = (ui.available_width()/3.0)-5.0;
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
if self.p2pool.lock().unwrap().is_waiting() {
ui.add_enabled_ui(false, |ui| {
ui.add_sized([width, height], Button::new("")).on_hover_text("Restart P2Pool");
ui.add_sized([width, height], Button::new("")).on_hover_text("Stop P2Pool");
ui.add_sized([width, height], Button::new("")).on_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() {
Helper::restart_p2pool(&self.helper, &self.state.p2pool, &self.state.gupax.absolute_p2pool_path);
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
}
if ui.add_sized([width, height], Button::new("")).on_hover_text("Stop P2Pool").clicked() {
Helper::stop_p2pool(&self.helper);
}
ui.add_enabled_ui(false, |ui| {
ui.add_sized([width, height], Button::new("")).on_hover_text("Start P2Pool");
});
} else {
ui.add_enabled_ui(false, |ui| {
ui.add_sized([width, height], Button::new("")).on_hover_text("Restart P2Pool");
ui.add_sized([width, height], Button::new("")).on_hover_text("Stop P2Pool");
});
// Check if address is okay before allowing to start.
let mut text = String::new();
if !Regexes::addr_ok(&self.regex, &self.state.p2pool.address) {
ui.set_enabled(false);
text = P2POOL_ADDRESS.to_string();
} else if !Gupax::path_is_exe(&self.state.gupax.p2pool_path) {
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() {
Helper::start_p2pool(&self.helper, &self.state.p2pool, &self.state.gupax.absolute_p2pool_path);
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
}
}
});
},
Tab::Xmrig => {
ui.group(|ui| {
let width = width / 1.5;
if ui.add_sized([width, height], SelectableLabel::new(!self.state.xmrig.simple, "Advanced")).on_hover_text(XMRIG_ADVANCED).clicked() {
self.state.xmrig.simple = false;
}
ui.separator();
if ui.add_sized([width, height], SelectableLabel::new(self.state.xmrig.simple, "Simple")).on_hover_text(XMRIG_SIMPLE).clicked() {
self.state.xmrig.simple = true;
}
});
ui.group(|ui| {
let width = (ui.available_width()/3.0)-5.0;
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
if self.xmrig.lock().unwrap().is_waiting() {
ui.add_enabled_ui(false, |ui| {
ui.add_sized([width, height], Button::new("")).on_hover_text("Restart XMRig");
ui.add_sized([width, height], Button::new("")).on_hover_text("Stop XMRig");
ui.add_sized([width, height], Button::new("")).on_hover_text("Start XMRig");
});
} else if self.xmrig.lock().unwrap().is_alive() {
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
if ui.add_sized([width, height], Button::new("")).on_hover_text("Restart XMRig").clicked() {
self.sudo.lock().unwrap().signal = ProcessSignal::Restart;
#[cfg(target_family = "unix")]
self.error_state.ask_sudo(&self.sudo);
#[cfg(target_os = "windows")]
Helper::restart_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path, Arc::clone(&self.sudo));
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
}
if ui.add_sized([width, height], Button::new("")).on_hover_text("Stop XMRig").clicked() {
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
Helper::stop_xmrig(&self.helper);
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
}
ui.add_enabled_ui(false, |ui| {
ui.add_sized([width, height], Button::new("")).on_hover_text("Start XMRig");
});
} else {
ui.add_enabled_ui(false, |ui| {
ui.add_sized([width, height], Button::new("")).on_hover_text("Restart XMRig");
ui.add_sized([width, height], Button::new("")).on_hover_text("Stop XMRig");
});
let mut text = String::new();
if !Gupax::path_is_exe(&self.state.gupax.xmrig_path) {
ui.set_enabled(false);
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() {
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() {
self.sudo.lock().unwrap().signal = ProcessSignal::Start;
self.error_state.ask_sudo(&self.sudo);
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
}
}
});
},
_ => (),
}
});
});
});
// Middle panel, contents of the [Tab]
CentralPanel::default().show(ctx, |ui| {
// This sets the Ui dimensions after Top/Bottom are filled
self.width = ui.available_width();
self.height = ui.available_height();
ui.style_mut().override_text_style = Some(TextStyle::Body);
match self.tab {
Tab::About => {
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.add_space(ui.available_height()/1.8);
ui.hyperlink_to("Powered by egui", "https://github.com/emilk/egui");
2022-11-24 04:03:56 +00:00
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");
helper: p2pool - connect major [Helper] APIs to GUI thread Lots of stuff in this commit: 1. Implement [Start/Stop/Restart] and make it not possible for the GUI to interact with that UI if [Helper] is doing stuff. This prevents the obviously bad situation where [Helper] is in the middle of spawning P2Pool, but the user is still allowed to start it again, which would spawn another P2Pool. The main GUI matches on the state and disables the appropriate UI so the user can't do this. 2. Sync P2Pool's [Priv] and [Pub] output so that the GUI thread is only rendering it once a second. All of Gupax also refreshes at least once a second now as well. 3. Match the [ProcessState] with some colors in the GUI 4. GUI thread no longer directly starts/stops/restarts a process. It will call a function in [Helper] that acts as a proxy. 5. The tokio [async_spawn_p2pool_watchdog()] function that was a clone of the PTY version (but had async stuff) and all of the related functions like the async STDOUT/STDERR reader is now completely gone. It doesn't make sense to write the same code twice, both [Simple] and [Advanced] will have a PTY, only difference being the [Simple] UI won't have an input box. 6. P2Pool's exit code is now examined, either success or failure 7. Output was moved into it's own [Arc<Mutex>]. This allows for more efficient writing/reading since before I had to lock all of [Helper], which caused some noticable deadlocks in the GUI. 8. New [tab] field in [State<Gupax>], and GUI option to select the tab that Gupax will start on.
2022-12-06 03:33:35 +00:00
if cfg!(debug_assertions) { ui.label(format!("{}", self.now.elapsed().as_secs_f64())); }
});
}
Tab::Status => {
Status::show(self, self.width, self.height, ctx, ui);
}
Tab::Gupax => {
Gupax::show(&mut self.state.gupax, &self.og, &self.state_path, &self.update, &self.file_window, &mut self.error_state, &self.restart, self.width, self.height, frame, ctx, ui);
}
Tab::P2pool => {
P2pool::show(&mut self.state.p2pool, &mut self.node_vec, &self.og, &self.ping, &self.regex, &self.p2pool, &self.p2pool_api, &mut self.p2pool_console, self.width, self.height, ctx, ui);
}
Tab::Xmrig => {
helper: optimize [String] output buffers Instead of cloning the entirety of the process's output, this commit adds a sort of hierarchy of string buffers: We need: 1. The full output for stat parsing (max 1 month/56m bytes) 2. GUI output to be lightweight (max 1-2 hours/1m bytes) 3. GUI output buffered so it's on a refresh tick of 1 second ------------------------------------------------------------------ So this is the new buffer hierarchy, from low to high level: [Process] <-> [Watchdog] <-> [Helper] <-> [GUI] ------------------------------------------------------------------ Process: Writes to 2 buffers, a FULL (stores everything) and a BUF which is exactly the same, except this gets [mem:take]n later Stuff gets written immediately if there is output detected. Watchdog: [std::mem::take]'s [Process]'s BUF for itself. Both FULL and BUF output overflows are checked in this loop. This is done on a slightly faster tick (900ms). Helper: Appends watchdog's BUF to GUI's already existing [String] on a 1-second tick. GUI: Does nothing, only locks & reads. ------------------------------------------------------------------ This means Helper's buffer will be swapped out every second, meaning it'll almost always be empty. Process's FULL output will be the heaviest, but is only used for backend parsing. GUI will be in the middle. This system of buffers makes it so not every thread has to hold it's own FULL copy of the output, in particular the GUI thread was starting to lag a little due to loading so much output.
2022-12-08 17:29:38 +00:00
Xmrig::show(&mut self.state.xmrig, &mut self.pool_vec, &self.regex, &self.xmrig, &self.xmrig_api, &mut self.xmrig_console, self.width, self.height, ctx, ui);
}
}
2022-10-01 16:58:22 +00:00
});
}
}