diff --git a/CHANGELOG.md b/CHANGELOG.md index 030d29c..176c2f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,35 @@ # v0.7.0 -## Updates -* - -## Fixes -* - +## Prototype Release +* Added `Simple` XMRig tab: + - Console + - Thread slider + - Pause on active slider (Windows/macOS only) +* Added `Advanced` XMRig tab: + - Includes all simple features + - Manual pool database, select/add/edit/delete a custom `Name/IP/Port/RigID` (max 1000 pools), saved at: + - Windows: `C:\Users\USER\AppData\Roaming\Gupax\pool.toml` + - macOS: `/Users/USER/Library/Application Support/Gupax/pool.toml` + - Linux: `/home/USER/.local/share/gupax/pool.toml` + - Overriding `config.json` option + - Manual Monero address option + - HTTP API IP/Port option + - TLS option + - Keepalive option +* Added `Simple` Gupax tab: + - Package updater + - `Auto-update` setting + - `Update-via-Tor` setting + - `Ask-before-quit` setting + - `Save-before-quit` setting +* Added `Advanced` Gupax tab: + - Includes all simple features + - P2Pool binary path selector + - XMRig binary path selector + - Gupax window width/height adjuster +* Default resolution change `1280x720, 16:9` -> `1280x800, 16:10` +* Added plowsof to community nodes: + - Plowsof1: `IP: node.monerodevs.org, RPC: 18089, ZMQ: 18084` + - Plowsof2: `IP: node2.monerodevs.org, RPC: 18089, ZMQ: 18084` --- diff --git a/README.md b/README.md index 4fa4faf..4f4a4a5 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ With Monero GUI managing the Monero node on one side and Gupax managing P2Pool/X | SupportXmrIr | node.supportxmr.ir | 18089 | | XmrVsBeast | p2pmd.xmrvsbeast.com | 18081 | -*Note: All have ZMQ port on 18083* +*Note: Plowsof1 & Plowsof2 have ZMQ port on 18084, the rest are 18083* ## Demo https://user-images.githubusercontent.com/101352116/194763334-d8e936c9-a71e-474e-ac65-3a339b96a9d2.mp4 diff --git a/src/README.md b/src/README.md index 93ff0f4..406550c 100644 --- a/src/README.md +++ b/src/README.md @@ -44,7 +44,7 @@ This is how Gupax works internally when starting up: - Kill processes, kill connections, exit ## Disk -Long-term state is saved onto the disk in the "OS data folder", using the [TOML](https://github.com/toml-lang/toml) format. If not found, default files will be created. Given a slightly corrupted state file, Gupax will attempt to merge it with a new default one. This will most likely happen if the internal data structure of `state.toml` is changed in the future (e.g removing an outdated setting). Merging silently in the background is a good non-interactive way to handle this. If Gupax can't read/write to disk at all, or if there are any other big issues, it will show an un-recoverable error window. +Long-term state is saved onto the disk in the "OS data folder", using the [TOML](https://github.com/toml-lang/toml) format. If not found, default files will be created. Given a slightly corrupted state file, Gupax will attempt to merge it with a new default one. This will most likely happen if the internal data structure of `state.toml` is changed in the future (e.g removing an outdated setting). Merging silently in the background is a good non-interactive way to handle this. The node/pool database cannot be merged, and if given a corrupted file, Gupax will show an un-recoverable error screen. If Gupax can't read/write to disk at all, or if there are any other big issues, it will show an un-recoverable error window. | OS | Data Folder | Example | |----------|----------------------------------------- |------------------------------------------------| @@ -55,6 +55,7 @@ Long-term state is saved onto the disk in the "OS data folder", using the [TOML] The current files saved to disk: * `state.toml` Gupax state/settings * `node.toml` The manual node database used for P2Pool advanced +* `pool.toml` The manual pool database used for XMRig advanced Arti (Tor) also needs to save cache and state. It uses the same file/folder conventions (.local/arti, .cache/arti). @@ -62,7 +63,7 @@ Arti (Tor) also needs to save cache and state. It uses the same file/folder conv Every frame, the max available `[width, height]` are calculated, and those are used as a baseline for the Top/Bottom bars, containing the tabs and status bar. After that, all available space is given to the middle ui elements. The scale is calculated every frame so that all elements can scale immediately as the user adjusts it; this doesn't take as much CPU as you might think since frames are only rendered on user interaction. Some elements are subtracted a fixed number because the `ui.seperator()`s add some fixed space which needs to be accounted for. ``` -Main [App] outer frame (default: [1280.0, 720.0]) +Main [App] outer frame (default: [1280.0, 800.0], 16:10 aspect ratio) ├─ TopPanel = height: 1/12th ├─ BottomPanel = height: 1/20th ├─ CentralPanel = height: the rest @@ -72,17 +73,19 @@ Main [App] outer frame (default: [1280.0, 720.0]) This is the internal naming scheme used by Gupax when updating/creating default folders/etc: Windows: - - Gupax: `Gupax.exe` - - P2Pool: `P2Pool\p2pool.exe` - - XMRig: `XMRig\xmrig.exe` +- Gupax: `Gupax.exe` +- P2Pool: `P2Pool\p2pool.exe` +- XMRig: `XMRig\xmrig.exe` + macOS: - - Gupax: `Gupax.app/.../Gupax` (Gupax is packaged as an `.app` on macOS) - - P2Pool: `p2pool/p2pool` - - XMRig: `xmrig/xmrig` +- Gupax: `Gupax.app/.../Gupax` (Gupax is packaged as an `.app` on macOS) +- P2Pool: `p2pool/p2pool` +- XMRig: `xmrig/xmrig` + Linux: - - Gupax: `gupax` - - P2Pool: `p2pool/p2pool` - - XMRig: `xmrig/xmrig` +- Gupax: `gupax` +- P2Pool: `p2pool/p2pool` +- XMRig: `xmrig/xmrig` These have to be packaged exactly with these names because the update code is case-sensitive. If an exact match is not found, it will error. @@ -102,6 +105,6 @@ Exceptions (there are always exceptions...): ``` For the Gupax data folder: - - Windows: `Gupax` - - macOS: `Gupax` - - Linux: `gupax` +- Windows: `Gupax` +- macOS: `Gupax` +- Linux: `gupax` diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 0000000..7e46dde --- /dev/null +++ b/src/command.rs @@ -0,0 +1,16 @@ +// 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 . diff --git a/src/constants.rs b/src/constants.rs index 211a2bb..4c976c3 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -20,6 +20,15 @@ pub const P2POOL_VERSION: &'static str = "v2.4"; pub const XMRIG_VERSION: &'static str = "v6.18.0"; pub const COMMIT: &'static str = include_str!("../.git/refs/heads/main"); +// App frame resolution, [16:10] aspect ratio, height = width * 1.6 +pub const APP_MIN_WIDTH: f32 = 768.0; +pub const APP_MIN_HEIGHT: f32 = 480.0; +pub const APP_MAX_WIDTH: f32 = 3456.0; +pub const APP_MAX_HEIGHT: f32 = 2160.0; +// Default, 1280x800 +pub const APP_DEFAULT_WIDTH: f32 = 1280.0; +pub const APP_DEFAULT_HEIGHT: f32 = 800.0; + // Use macOS shaped icon for macOS #[cfg(target_os = "macos")] pub const BYTES_ICON: &[u8] = include_bytes!("../images/icons/icon@2x.png"); @@ -36,6 +45,13 @@ pub const HORIZONTAL: &'static str = "------------------------------------------ // things actually line up. pub const SPACE: f32 = 10.0; +// Some colors +pub const RED: egui::Color32 = egui::Color32::from_rgb(230, 50, 50); +pub const GREEN: egui::Color32 = egui::Color32::from_rgb(100, 230, 100); +pub const YELLOW: egui::Color32 = egui::Color32::from_rgb(230, 230, 100); +pub const LIGHT_GRAY: egui::Color32 = egui::Color32::LIGHT_GRAY; +pub const BLACK: egui::Color32 = egui::Color32::BLACK; + // OS specific #[cfg(target_os = "windows")] pub const OS: &'static str = " Windows"; @@ -62,9 +78,26 @@ pub const GUPAX_UPDATE_VIA_TOR: &'static str = "Update through the Tor network. pub const GUPAX_UPDATE_VIA_TOR: &'static str = "WARNING: This option is unstable on macOS. Update through the Tor network. Tor is embedded within Gupax; a Tor system proxy is not required"; pub const GUPAX_ASK_BEFORE_QUIT: &'static str = "Ask before quitting Gupax"; pub const GUPAX_SAVE_BEFORE_QUIT: &'static str = "Automatically save any changed settings before quitting"; +pub const GUPAX_WIDTH: &'static str = "Set the width of the Gupax window"; +pub const GUPAX_HEIGHT: &'static str = "Set the height of the Gupax window"; +pub const GUPAX_LOCK_WIDTH: &'static str = "Automatically match the height against the width in a 16:10 ratio; aka HEIGHT = WIDTH / 1.6"; +pub const GUPAX_LOCK_HEIGHT: &'static str = "Automatically match the width against the height in a 16:10 ratio; aka WIDTH = HEIGHT * 1.6"; +pub const GUPAX_NO_LOCK: &'static str = "Allow individual selection of width and height"; +pub const GUPAX_SET: &'static str = "Set the width/height of the Gupax window to the current values"; +pub const GUPAX_SIMPLE: &'static str = +r#"Use simple Gupax settings: + - Update button + - Basic toggles"#; +pub const GUPAX_ADVANCED: &'static str = +r#"Use advanced Gupax settings: + - Update button + - Basic toggles + - P2Pool/XMRig binary path selector + - Gupax resolution sliders"#; pub const GUPAX_SELECT: &'static str = "Open a file explorer to select a file"; pub const GUPAX_PATH_P2POOL: &'static str = "The location of the P2Pool binary: Both absolute and relative paths are accepted; A red [X] will appear if there is no file found at the given path"; pub const GUPAX_PATH_XMRIG: &'static str = "The location of the XMRig binary: Both absolute and relative paths are accepted; A red [X] will appear if there is no file found at the given path"; + // P2Pool pub const P2POOL_MAIN: &'static str = "Use the P2Pool main-chain. This P2Pool finds shares faster, but has a higher difficulty. Suitable for miners with more than 50kH/s"; pub const P2POOL_MINI: &'static str = "Use the P2Pool mini-chain. This P2Pool finds shares slower, but has a lower difficulty. Suitable for miners with less than 50kH/s"; @@ -78,34 +111,54 @@ pub const P2POOL_PING: &'static str = "Ping the built-in community Monero nodes" pub const P2POOL_ADDRESS: &'static str = "You must use a primary Monero address to mine on P2Pool (starts with a 4). It is highly recommended to create a new wallet for P2Pool mining; wallet addresses are public on P2Pool!"; pub const P2POOL_COMMAND: &'static str = "Start P2Pool with these arguments and override all below settings; If the [--data-api] flag is not given, Gupax will append it to the arguments automatically so that the [Status] tab can work"; pub const P2POOL_SIMPLE: &'static str = -r#"Use simple settings: +r#"Use simple P2Pool settings: - Remote community Monero node - Default P2Pool settings + Mini"#; pub const P2POOL_ADVANCED: &'static str = -r#"Use advanced settings: +r#"Use advanced P2Pool settings: - Overriding command arguments - - Manual node selection + - Manual node list - P2Pool Main/Mini selection - Out/In peer setting - Log level setting"#; -pub const P2POOL_NAME: &'static str = "Add a unique name to identify this node; Only [A-Za-z0-9-_] and spaces allowed, if the name already exists, the current settings will be saved to the already existing entry; Max length = 30 characters"; +pub const P2POOL_NAME: &'static str = "Add a unique name to identify this node; Only [A-Za-z0-9-_] and spaces allowed; Max length = 30 characters"; pub const P2POOL_NODE_IP: &'static str = "Specify the Monero Node IP to connect to with P2Pool; It must be a valid IPv4 address or a valid domain name; Max length = 255 characters"; pub const P2POOL_RPC_PORT: &'static str = "Specify the RPC port of the Monero node; [1-65535]"; pub const P2POOL_ZMQ_PORT: &'static str = "Specify the ZMQ port of the Monero node; [1-65535]"; -pub const P2POOL_ADD: &'static str = "Add the current values to the list"; -pub const P2POOL_SAVE: &'static str = "Save the current values to the already existing entry"; -pub const P2POOL_DELETE: &'static str = "Delete the currently selected node"; -pub const P2POOL_CLEAR: &'static str = "Clear all current values"; + +// Node/Pool list +pub const LIST_ADD: &'static str = "Add the current values to the list"; +pub const LIST_SAVE: &'static str = "Save the current values to the already existing entry"; +pub const LIST_DELETE: &'static str = "Delete the currently selected entry"; +pub const LIST_CLEAR: &'static str = "Clear all current values"; // XMRig -pub const XMRIG_P2POOL: &'static str = "Mine to your own P2Pool instance (localhost:3333)"; -pub const XMRIG_MANUAL: &'static str = "Manually specify where to mine to"; +pub const XMRIG_SIMPLE: &'static str = +r#"Use simple XMRig settings: + - Mine to local P2Pool (localhost:3333) + - CPU thread slider + - HTTP API @ localhost:18088"#; +pub const XMRIG_ADVANCED: &'static str = +r#"Use advanced XMRig settings: + - Overriding config file + - Custom payout address + - CPU thread slider + - Manual pool list + - TLS setting + - Keepalive setting + - Custom HTTP API IP/Port"#; +pub const XMRIG_CONFIG: &'static str = "Start XMRig with this config file and override all below settings; If the [http-api] options are not set, the [Status] tab will not properly show XMRig stats. If they are set, Gupax will detect which automatically IP/Port you are using"; +pub const XMRIG_ADDRESS: &'static str = "Specify which Monero address to send payouts to; Must be a valid primary address (starts with 4)"; +pub const XMRIG_NAME: &'static str = "Add a unique name to identify this pool; Only [A-Za-z0-9-_] and spaces allowed; Max length = 30 characters"; +pub const XMRIG_IP: &'static str = "Specify the pool IP to connect to with XMRig; It must be a valid IPv4 address or a valid domain name; Max length = 255 characters"; +pub const XMRIG_PORT: &'static str = "Specify the port of the pool; [1-65535]"; +pub const XMRIG_RIG: &'static str = "Add a unique rig ID. This will be the name shown on the pool; Only [A-Za-z0-9-_] and spaces allowed; Max length = 30 characters"; +pub const XMRIG_PAUSE: &'static str = "THIS SETTING IS DISABLED IF SET TO [0]. Pause mining if user is active, resume after"; +pub const XMRIG_API_IP: &'static str = "Specify which IP to bind to for XMRig's HTTP API"; +pub const XMRIG_API_PORT: &'static str = "Specify which port to bind to for XMRig's HTTP API"; pub const XMRIG_TLS: &'static str = "Enable SSL/TLS connections (needs pool support)"; -pub const XMRIG_HUGEPAGES_JIT: &'static str = "Enable hugepages for RandomX JIT code. Note: 1GB hugepages is automatically enabled (only available on Linux)"; -pub const XMRIG_NICEHASH: &'static str = "Enable nicehash.com support"; pub const XMRIG_KEEPALIVE: &'static str = "Send keepalived packet to prevent timeout (needs pool support)"; pub const XMRIG_THREADS: &'static str = "Number of CPU threads to use for mining"; -pub const XMRIG_PRIORITY: &'static str = "Set process priority (0 idle, 2 normal to 5 highest)"; // CLI argument messages pub const ARG_HELP: &'static str = @@ -118,7 +171,8 @@ r#"USAGE: ./gupax [--flag] --no-startup Disable all auto-startup settings for this instance --reset-state Reset all Gupax state (your settings) --reset-nodes Reset the manual node list in the [P2Pool] tab - --reset-all Reset both the state and the manual node list + --reset-pools Reset the manual pool list in the [XMRig] tab + --reset-all Reset the state, the manual node list, and the manual pool list --ferris Print an extremely cute crab To view more detailed console debug information, start Gupax with diff --git a/src/disk.rs b/src/disk.rs index c1cffbb..61d8a8d 100644 --- a/src/disk.rs +++ b/src/disk.rs @@ -41,9 +41,32 @@ use std::{ use serde::{Serialize,Deserialize}; use figment::Figment; use figment::providers::{Format,Toml}; -use crate::constants::*; +use crate::{ + constants::*, + gupax::Ratio, +}; use log::*; +//---------------------------------------------------------------------------------------------------- Const +// State file +const ERROR: &'static str = "Disk error"; +const PATH_ERROR: &'static str = "PATH for state directory could not be not found"; +#[cfg(target_os = "windows")] +const DIRECTORY: &'static str = r#"Gupax\"#; +#[cfg(target_os = "macos")] +const DIRECTORY: &'static str = "Gupax/"; +#[cfg(target_os = "linux")] +const DIRECTORY: &'static str = "gupax/"; + +#[cfg(target_os = "windows")] +pub const DEFAULT_P2POOL_PATH: &'static str = r"P2Pool\p2pool.exe"; +#[cfg(target_family = "unix")] +pub const DEFAULT_P2POOL_PATH: &'static str = "p2pool/p2pool"; +#[cfg(target_os = "windows")] +pub const DEFAULT_XMRIG_PATH: &'static str = r"XMRig\xmrig.exe"; +#[cfg(target_family = "unix")] +pub const DEFAULT_XMRIG_PATH: &'static str = "xmrig/xmrig"; + //---------------------------------------------------------------------------------------------------- General functions for all [File]'s // get_file_path() | Return absolute path to OS data path + filename // read_to_string() | Convert the file at a given path into a [String] @@ -116,6 +139,7 @@ impl State { if max_threads == 1 { current_threads = 1; } else { current_threads = max_threads / 2; } Self { gupax: Gupax { + simple: true, auto_update: true, auto_node: true, ask_before_quit: true, @@ -128,6 +152,9 @@ impl State { xmrig_path: DEFAULT_XMRIG_PATH.to_string(), absolute_p2pool_path: into_absolute_path(DEFAULT_P2POOL_PATH.to_string()).unwrap(), absolute_xmrig_path: into_absolute_path(DEFAULT_XMRIG_PATH.to_string()).unwrap(), + selected_width: APP_DEFAULT_WIDTH as u16, + selected_height: APP_DEFAULT_HEIGHT as u16, + ratio: Ratio::Width, }, p2pool: P2pool { simple: true, @@ -152,15 +179,24 @@ impl State { }, xmrig: Xmrig { simple: true, - tls: false, - nicehash: false, - keepalive: false, - hugepages_jit: true, + pause: 0, + config: String::with_capacity(100), + address: String::with_capacity(95), + name: "Local P2Pool".to_string(), + rig: "Gupax".to_string(), + ip: "localhost".to_string(), + port: "3333".to_string(), + selected_index: 0, + selected_name: "Local P2Pool".to_string(), + selected_ip: "localhost".to_string(), + selected_rig: "Gupax".to_string(), + selected_port: "3333".to_string(), + api_ip: "localhost".to_string(), + api_port: "18088".to_string(), + tls: true, + keepalive: true, current_threads, max_threads, - priority: 2, - pool: "localhost:3333".to_string(), - address: String::with_capacity(95), }, version: Arc::new(Mutex::new(Version { gupax: GUPAX_VERSION.to_string(), @@ -381,6 +417,95 @@ impl Node { // } } +//---------------------------------------------------------------------------------------------------- [Pool] impl +impl Pool { + pub fn p2pool() -> Self { + Self { + rig: "Gupax".to_string(), + ip: "localhost".to_string(), + port: "3333".to_string(), + } + } + + pub fn new_vec() -> Vec<(String, Self)> { + let mut vec = Vec::new(); + vec.push(("Local P2Pool".to_string(), Self::p2pool())); + vec + } + + pub fn from_string_to_vec(string: &String) -> Result, TomlError> { + let pools: toml::map::Map = match toml::de::from_str(&string) { + Ok(map) => { + info!("Pool | Parse ... OK"); + map + } + Err(err) => { + error!("Pool | String parse ... FAIL ... {}", err); + return Err(TomlError::Deserialize(err)) + }, + }; + let size = pools.keys().len(); + let mut vec = Vec::with_capacity(size); + for (key, values) in pools.iter() { + let pool = Pool { + rig: values.get("rig").unwrap().as_str().unwrap().to_string(), + ip: values.get("ip").unwrap().as_str().unwrap().to_string(), + port: values.get("port").unwrap().as_str().unwrap().to_string(), + }; + vec.push((key.clone(), pool)); + } + Ok(vec) + } + + pub fn to_string(vec: &Vec<(String, Self)>) -> Result { + let mut toml = String::new(); + for (key, value) in vec.iter() { + write!( + toml, + "[\'{}\']\nrig = {:#?}\nip = {:#?}\nport = {:#?}\n\n", + key, + value.rig, + value.ip, + value.port, + )?; + } + Ok(toml) + } + + pub fn get(path: &PathBuf) -> Result, TomlError> { + // Read + let file = File::Pool; + let string = match read_to_string(file, &path) { + Ok(string) => string, + // Create + _ => { + Self::create_new(path)?; + read_to_string(file, &path)? + }, + }; + // Deserialize + Self::from_string_to_vec(&string) + } + + pub fn create_new(path: &PathBuf) -> Result, TomlError> { + info!("Pool | Creating new default..."); + let new = Self::new_vec(); + let string = Self::to_string(&Self::new_vec())?; + fs::write(&path, &string)?; + info!("Pool | Write ... OK"); + Ok(new) + } + + pub fn save(vec: &Vec<(String, Self)>, path: &PathBuf) -> Result<(), TomlError> { + info!("Pool | Saving to disk..."); + let string = Self::to_string(vec)?; + match fs::write(path, string) { + Ok(_) => { info!("TOML save ... OK"); Ok(()) }, + Err(err) => { error!("Couldn't overwrite TOML file"); Err(TomlError::Io(err)) }, + } + } +} + //---------------------------------------------------------------------------------------------------- Custom Error [TomlError] #[derive(Debug)] pub enum TomlError { @@ -418,31 +543,12 @@ impl From for TomlError { } } -//---------------------------------------------------------------------------------------------------- Const -// State file -const ERROR: &'static str = "Disk error"; -const PATH_ERROR: &'static str = "PATH for state directory could not be not found"; -#[cfg(target_os = "windows")] -const DIRECTORY: &'static str = r#"Gupax\"#; -#[cfg(target_os = "macos")] -const DIRECTORY: &'static str = "Gupax/"; -#[cfg(target_os = "linux")] -const DIRECTORY: &'static str = "gupax/"; - -#[cfg(target_os = "windows")] -pub const DEFAULT_P2POOL_PATH: &'static str = r"P2Pool\p2pool.exe"; -#[cfg(target_family = "unix")] -pub const DEFAULT_P2POOL_PATH: &'static str = "p2pool/p2pool"; -#[cfg(target_os = "windows")] -pub const DEFAULT_XMRIG_PATH: &'static str = r"XMRig\xmrig.exe"; -#[cfg(target_family = "unix")] -pub const DEFAULT_XMRIG_PATH: &'static str = "xmrig/xmrig"; - //---------------------------------------------------------------------------------------------------- [File] Enum (for matching which file) #[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)] pub enum File { - State, - Node, + State, // state.toml -> Gupax state + Node, // node.toml -> P2Pool manual node selector + Pool, // pool.toml -> XMRig manual pool selector } //---------------------------------------------------------------------------------------------------- [Node] Struct @@ -453,6 +559,14 @@ pub struct Node { pub zmq: String, } +//---------------------------------------------------------------------------------------------------- [Pool] Struct +#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)] +pub struct Pool { + pub rig: String, + pub ip: String, + pub port: String, +} + //---------------------------------------------------------------------------------------------------- [State] Struct #[derive(Clone,Debug,Deserialize,Serialize)] pub struct State { @@ -464,6 +578,7 @@ pub struct State { #[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)] pub struct Gupax { + pub simple: bool, pub auto_update: bool, pub auto_node: bool, pub ask_before_quit: bool, @@ -473,6 +588,9 @@ pub struct Gupax { pub xmrig_path: String, pub absolute_p2pool_path: PathBuf, pub absolute_xmrig_path: PathBuf, + pub selected_width: u16, + pub selected_height: u16, + pub ratio: Ratio, } #[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)] @@ -501,15 +619,24 @@ pub struct P2pool { #[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)] pub struct Xmrig { pub simple: bool, + pub pause: u8, + pub config: String, pub tls: bool, - pub nicehash: bool, pub keepalive: bool, - pub hugepages_jit: bool, pub max_threads: usize, pub current_threads: usize, - pub priority: u8, - pub pool: String, pub address: String, + pub api_ip: String, + pub api_port: String, + pub name: String, + pub rig: String, + pub ip: String, + pub port: String, + pub selected_index: usize, + pub selected_name: String, + pub selected_rig: String, + pub selected_ip: String, + pub selected_port: String, } #[derive(Clone,Debug,Deserialize,Serialize)] diff --git a/src/gupax.rs b/src/gupax.rs index f1e4943..a77dad3 100644 --- a/src/gupax.rs +++ b/src/gupax.rs @@ -18,9 +18,10 @@ use crate::State; use egui::{ TextStyle::Monospace, - RichText, - Label, - Color32, + Checkbox,ProgressBar,Spinner,Button,Label,Slider, + SelectableLabel, + RichText,Color32, + Vec2, }; use crate::constants::*; use crate::disk::{Gupax,Version}; @@ -31,6 +32,7 @@ use std::{ path::PathBuf, }; use log::*; +use serde::{Serialize,Deserialize}; //---------------------------------------------------------------------------------------------------- FileWindow // Struct for writing/reading the path state. @@ -62,9 +64,18 @@ pub enum FileType { Xmrig, } +//---------------------------------------------------------------------------------------------------- Ratio Lock +// Enum for the lock ratio in the advanced tab. +#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)] +pub enum Ratio { + Width, + Height, + None, +} + //---------------------------------------------------------------------------------------------------- Gupax impl Gupax { - pub fn show(&mut self, og: &Arc>, state_ver: &Arc>, update: &Arc>, file_window: &Arc>, state_path: &PathBuf, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) { + pub fn show(&mut self, og: &Arc>, state_ver: &Arc>, update: &Arc>, file_window: &Arc>, state_path: &PathBuf, width: f32, height: f32, frame: &mut eframe::Frame, ctx: &egui::Context, ui: &mut egui::Ui) { // Update button + Progress bar ui.group(|ui| { // These are in unnecessary [ui.vertical()]'s @@ -76,7 +87,7 @@ impl Gupax { let updating = *update.lock().unwrap().updating.lock().unwrap(); ui.vertical(|ui| { ui.set_enabled(!updating); - if ui.add_sized([width, height], egui::Button::new("Check for updates")).on_hover_text(GUPAX_UPDATE).clicked() { + if ui.add_sized([width, height], Button::new("Check for updates")).on_hover_text(GUPAX_UPDATE).clicked() { Update::spawn_thread(og, update, state_ver, state_path); } }); @@ -84,14 +95,14 @@ impl Gupax { ui.set_enabled(updating); let prog = *update.lock().unwrap().prog.lock().unwrap(); let msg = format!("{}\n{}{}", *update.lock().unwrap().msg.lock().unwrap(), prog, "%"); - ui.add_sized([width, height*1.4], egui::Label::new(RichText::text_style(RichText::new(msg), Monospace))); + ui.add_sized([width, height*1.4], Label::new(RichText::text_style(RichText::new(msg), Monospace))); let height = height/2.0; if updating { - ui.add_sized([width, height], egui::Spinner::new().size(height)); + ui.add_sized([width, height], Spinner::new().size(height)); } else { - ui.add_sized([width, height], egui::Label::new("...")); + ui.add_sized([width, height], Label::new("...")); } - ui.add_sized([width, height], egui::ProgressBar::new(update.lock().unwrap().prog.lock().unwrap().round() / 100.0)); + ui.add_sized([width, height], ProgressBar::new(update.lock().unwrap().prog.lock().unwrap().round() / 100.0)); }); }); @@ -104,33 +115,36 @@ impl Gupax { style.spacing.icon_width = width / 6.0; style.spacing.icon_spacing = 20.0; ctx.set_style(style); - ui.add_sized([width, height], egui::Checkbox::new(&mut self.auto_update, "Auto-update")).on_hover_text(GUPAX_AUTO_UPDATE); + ui.add_sized([width, height], Checkbox::new(&mut self.auto_update, "Auto-update")).on_hover_text(GUPAX_AUTO_UPDATE); ui.separator(); - ui.add_sized([width, height], egui::Checkbox::new(&mut self.update_via_tor, "Update via Tor")).on_hover_text(GUPAX_UPDATE_VIA_TOR); + ui.add_sized([width, height], Checkbox::new(&mut self.update_via_tor, "Update via Tor")).on_hover_text(GUPAX_UPDATE_VIA_TOR); ui.separator(); - ui.add_sized([width, height], egui::Checkbox::new(&mut self.ask_before_quit, "Ask before quit")).on_hover_text(GUPAX_ASK_BEFORE_QUIT); + ui.add_sized([width, height], Checkbox::new(&mut self.ask_before_quit, "Ask before quit")).on_hover_text(GUPAX_ASK_BEFORE_QUIT); ui.separator(); - ui.add_sized([width, height], egui::Checkbox::new(&mut self.save_before_quit, "Save before quit")).on_hover_text(GUPAX_SAVE_BEFORE_QUIT); + ui.add_sized([width, height], Checkbox::new(&mut self.save_before_quit, "Save before quit")).on_hover_text(GUPAX_SAVE_BEFORE_QUIT); }); }); - ui.add_space(SPACE); + if self.simple { return } + + // P2Pool/XMRig binary path selection + ui.add_space(SPACE); ui.style_mut().override_text_style = Some(Monospace); let height = height/20.0; let text_edit = (ui.available_width()/10.0)-SPACE; ui.horizontal(|ui| { if self.p2pool_path.is_empty() { - ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ➖").color(Color32::LIGHT_GRAY))); + ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ➖").color(LIGHT_GRAY))); } else { match crate::disk::into_absolute_path(self.p2pool_path.clone()) { Ok(path) => { if path.is_file() { - ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ✔").color(Color32::from_rgb(100, 230, 100)))) + ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ✔").color(GREEN))) } else { - ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ❌").color(Color32::from_rgb(230, 50, 50)))) + ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ❌").color(RED))) } }, - _ => ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ❌").color(Color32::from_rgb(230, 50, 50)))), + _ => ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ❌").color(RED))), }; } ui.spacing_mut().text_edit_width = ui.available_width() - SPACE; @@ -142,17 +156,17 @@ impl Gupax { }); ui.horizontal(|ui| { if self.xmrig_path.is_empty() { - ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ➖").color(Color32::LIGHT_GRAY))); + ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ➖").color(LIGHT_GRAY))); } else { match crate::disk::into_absolute_path(self.xmrig_path.clone()) { Ok(path) => { if path.is_file() { - ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ✔").color(Color32::from_rgb(100, 230, 100)))) + ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ✔").color(GREEN))) } else { - ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ❌").color(Color32::from_rgb(230, 50, 50)))) + ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ❌").color(RED))) } }, - _ => ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ❌").color(Color32::from_rgb(230, 50, 50)))), + _ => ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ❌").color(RED))), }; } ui.spacing_mut().text_edit_width = ui.available_width() - SPACE; @@ -166,6 +180,52 @@ impl Gupax { if guard.picked_p2pool { self.p2pool_path = guard.p2pool_path.clone(); guard.picked_p2pool = false; } if guard.picked_xmrig { self.xmrig_path = guard.xmrig_path.clone(); guard.picked_xmrig = false; } drop(guard); + + // Gupax App resolution sliders + ui.vertical(|ui| { + let width = width/10.0; + ui.spacing_mut().icon_width = width / 25.0; + ui.spacing_mut().slider_width = width*7.6; + match self.ratio { + Ratio::None => (), + Ratio::Width => { + let width = self.selected_width as f64; + let height = self.selected_height as f64; + let height = (width / 1.6).round(); + self.selected_height = height as u16; + }, + Ratio::Height => { + let width = self.selected_width as f64; + let height = self.selected_height as f64; + let width = (height * 1.6).round(); + self.selected_width = width as u16; + }, + } + ui.horizontal(|ui| { + ui.set_enabled(self.ratio != Ratio::Height); + ui.add_sized([width, height], Label::new(format!(" Width [{}-{}]:", APP_MIN_WIDTH as u16, APP_MAX_WIDTH as u16))); + ui.add_sized([width, height], Slider::new(&mut self.selected_width, APP_MIN_WIDTH as u16..=APP_MAX_WIDTH as u16)).on_hover_text(GUPAX_WIDTH); + }); + ui.horizontal(|ui| { + ui.set_enabled(self.ratio != Ratio::Width); + ui.add_sized([width, height], Label::new(format!("Height [{}-{}]:", APP_MIN_HEIGHT as u16, APP_MAX_HEIGHT as u16))); + ui.add_sized([width, height], Slider::new(&mut self.selected_height, APP_MIN_HEIGHT as u16..=APP_MAX_HEIGHT as u16)).on_hover_text(GUPAX_HEIGHT); + }); + }); + ui.style_mut().override_text_style = Some(egui::TextStyle::Button); + ui.group(|ui| { + let width = (width/4.0)-(SPACE*1.5); + let height = ui.available_height()/2.0; + ui.horizontal(|ui| { + if ui.add_sized([width, height], SelectableLabel::new(self.ratio == Ratio::Width, "Lock to width")).on_hover_text(GUPAX_LOCK_WIDTH).clicked() { self.ratio = Ratio::Width; } + ui.separator(); + if ui.add_sized([width, height], SelectableLabel::new(self.ratio == Ratio::Height, "Lock to height")).on_hover_text(GUPAX_LOCK_HEIGHT).clicked() { self.ratio = Ratio::Height; } + ui.separator(); + if ui.add_sized([width, height], SelectableLabel::new(self.ratio == Ratio::None, "No lock")).on_hover_text(GUPAX_NO_LOCK).clicked() { self.ratio = Ratio::None; } + if ui.add_sized([width, height], Button::new("Set")).on_hover_text(GUPAX_SET).clicked() { + frame.set_window_size(Vec2::new(self.selected_width as f32, self.selected_height as f32)); + } + })}); } fn spawn_file_window_thread(file_window: &Arc>, file_type: FileType) { diff --git a/src/main.rs b/src/main.rs index ad21d4f..197987d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,12 +20,17 @@ //---------------------------------------------------------------------------------------------------- Imports // egui/eframe -use egui::TextStyle::*; -use egui::color::Color32; -use egui::FontFamily::Proportional; -use egui::{FontId,Label,RichText,Stroke,Vec2}; -use egui::special_emojis::GITHUB; -use egui::{Key,Modifiers}; +use egui::{ + TextStyle::*, + color::Color32, + FontFamily::Proportional, + TextStyle, + Layout,Align, + FontId,Label,RichText,Stroke,Vec2,Button,SelectableLabel, + special_emojis::GITHUB, + Key,Modifiers, + CentralPanel,TopBottomPanel, +}; use egui_extras::RetainedImage; use eframe::{egui,NativeOptions}; @@ -75,6 +80,8 @@ pub struct App { ping: Arc>, // 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 // Error State // These values are essentially global variables that @@ -97,6 +104,7 @@ pub struct App { 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 name_version: String, // [Gupax vX.X.X] img: Images, // Custom Struct holding pre-compiled bytes of [Images] @@ -118,14 +126,16 @@ impl App { let mut app = Self { tab: Tab::default(), ping: Arc::new(Mutex::new(Ping::new())), - width: 1280.0, - height: 720.0, + width: APP_DEFAULT_WIDTH, + height: APP_DEFAULT_HEIGHT, og: Arc::new(Mutex::new(State::new())), state: State::new(), 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(), diff: false, error_state: ErrorState::new(), p2pool: false, @@ -134,11 +144,12 @@ impl App { now: Instant::now(), exe: String::new(), dir: String::new(), - resolution: Vec2::new(1280.0, 720.0), + 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(), @@ -162,11 +173,13 @@ impl App { Err(e) => { panic = format!("get_os_data_path(): {}", e); app.error_state.set(panic.clone(), ErrorFerris::Panic, ErrorButtons::Quit); PathBuf::new() }, }; - // Set [state.toml/node.toml] path + // 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 @@ -201,13 +214,29 @@ impl App { 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!("State file: {}", 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() }, }; - app.node_vec = app.og_node_vec.clone(); + // 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] @@ -233,6 +262,19 @@ impl App { 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(); @@ -421,11 +463,11 @@ fn init_logger(now: Instant) { info!("init_logger() ... OK"); } -fn init_options() -> NativeOptions { +fn init_options(initial_window_size: Option) -> NativeOptions { let mut options = eframe::NativeOptions::default(); - options.min_window_size = Option::from(Vec2::new(854.0, 480.0)); - options.max_window_size = Option::from(Vec2::new(3180.0, 2160.0)); - options.initial_window_size = Option::from(Vec2::new(1280.0, 720.0)); + 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; 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(); @@ -594,14 +636,18 @@ fn print_disk_file(path: &PathBuf) { fn main() { let now = Instant::now(); init_logger(now); - let options = init_options(); + let mut app = App::new(); + app.now = now; + init_auto(&app); + let initial_window_size = match app.state.gupax.simple { + true => Some(Vec2::new(app.state.gupax.selected_width as f32, app.state.gupax.selected_height as f32)), + false => Some(Vec2::new(APP_DEFAULT_WIDTH, APP_DEFAULT_HEIGHT)), + }; + 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), } - let mut app = App::new(); - app.now = now; - init_auto(&app); info!("Init ... DONE"); eframe::run_native(&app.name_version.clone(), options, Box::new(|cc| Box::new(App::cc(cc, app))),); } @@ -626,7 +672,7 @@ impl eframe::App for App { // This sets the top level Ui dimensions. // Used as a reference for other uis. - egui::CentralPanel::default().show(ctx, |ui| { self.width = ui.available_width(); self.height = ui.available_height(); }); + CentralPanel::default().show(ctx, |ui| { self.width = ui.available_width(); self.height = ui.available_height(); }); // This sets fonts globally depending on the width. init_text_styles(ctx, self.width); @@ -638,7 +684,7 @@ impl eframe::App for App { // If there's an error, display [ErrorState] on the whole screen until user responds if self.error_state.error { - egui::CentralPanel::default().show(ctx, |ui| { + CentralPanel::default().show(ctx, |ui| { ui.vertical_centered(|ui| { // Set width/height/font let width = self.width; @@ -690,22 +736,22 @@ impl eframe::App for App { match self.error_state.buttons { YesNo => { - if ui.add_sized([width, height/2.0], egui::Button::new("Yes")).clicked() { self.error_state = ErrorState::new(); } + if ui.add_sized([width, height/2.0], Button::new("Yes")).clicked() { self.error_state = ErrorState::new(); } // If [Esc] was pressed, assume [No] - if esc || ui.add_sized([width, height/2.0], egui::Button::new("No")).clicked() { exit(0); } + 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], egui::Button::new("Stay")).clicked() { + 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], egui::Button::new("Quit")).clicked() { exit(0); } + 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], egui::Button::new("Yes")).clicked() { + 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) { @@ -720,10 +766,10 @@ 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], egui::Button::new("No")).clicked() { self.error_state = ErrorState::new() } + if esc || ui.add_sized([width, height/2.0], Button::new("No")).clicked() { self.error_state = ErrorState::new() } }, ResetNode => { - if ui.add_sized([width, height/2.0], egui::Button::new("Yes")).clicked() { + 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) { @@ -738,10 +784,10 @@ 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], egui::Button::new("No")).clicked() { self.error_state = ErrorState::new() } + if esc || ui.add_sized([width, height/2.0], Button::new("No")).clicked() { self.error_state = ErrorState::new() } }, - Okay => if esc || ui.add_sized([width, height], egui::Button::new("Okay")).clicked() { self.error_state = ErrorState::new(); }, - Quit => if ui.add_sized([width, height], egui::Button::new("Quit")).clicked() { exit(1); }, + Okay => if esc || ui.add_sized([width, height], Button::new("Okay")).clicked() { self.error_state = ErrorState::new(); }, + Quit => if ui.add_sized([width, height], Button::new("Quit")).clicked() { exit(1); }, } })}); return @@ -760,7 +806,7 @@ impl eframe::App for App { drop(og); // Top: Tabs - egui::TopBottomPanel::top("top").show(ctx, |ui| { + TopBottomPanel::top("top").show(ctx, |ui| { let width = (self.width - (SPACE*10.0))/5.0; let height = self.height/12.0; ui.group(|ui| { @@ -771,22 +817,22 @@ impl eframe::App for App { 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], egui::SelectableLabel::new(self.tab == Tab::About, "About")).clicked() { self.tab = Tab::About; } + 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], egui::SelectableLabel::new(self.tab == Tab::Status, "Status")).clicked() { self.tab = Tab::Status; } + 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], egui::SelectableLabel::new(self.tab == Tab::Gupax, "Gupax")).clicked() { self.tab = Tab::Gupax; } + 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], egui::SelectableLabel::new(self.tab == Tab::P2pool, "P2Pool")).clicked() { self.tab = Tab::P2pool; } + 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], egui::SelectableLabel::new(self.tab == Tab::Xmrig, "XMRig")).clicked() { self.tab = Tab::Xmrig; } + if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Xmrig, "XMRig")).clicked() { self.tab = Tab::Xmrig; } }); ui.add_space(4.0); }); }); // Bottom: app info + state/process buttons - egui::TopBottomPanel::bottom("bottom").show(ctx, |ui| { + TopBottomPanel::bottom("bottom").show(ctx, |ui| { let height = self.height/20.0; ui.style_mut().override_text_style = Some(Name("Bottom".into())); ui.horizontal(|ui| { @@ -798,32 +844,36 @@ impl eframe::App for App { ui.add_sized([width, height], Label::new(self.os)); ui.separator(); if self.p2pool { - ui.add_sized([width, height], Label::new(RichText::new("P2Pool ⏺").color(Color32::from_rgb(100, 230, 100)))); + ui.add_sized([width, height], Label::new(RichText::new("P2Pool ⏺").color(GREEN))); } else { - ui.add_sized([width, height], Label::new(RichText::new("P2Pool ⏺").color(Color32::from_rgb(230, 50, 50)))); + ui.add_sized([width, height], Label::new(RichText::new("P2Pool ⏺").color(RED))); } ui.separator(); if self.xmrig { - ui.add_sized([width, height], Label::new(RichText::new("XMRig ⏺").color(Color32::from_rgb(100, 230, 100)))); + ui.add_sized([width, height], Label::new(RichText::new("XMRig ⏺").color(GREEN))); } else { - ui.add_sized([width, height], Label::new(RichText::new("XMRig ⏺").color(Color32::from_rgb(230, 50, 50)))); + ui.add_sized([width, height], Label::new(RichText::new("XMRig ⏺").color(RED))); } }); - ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| { - // [Start/Stop/Restart] + [Simple/Advanced] + [Save/Reset] - let width = (ui.available_width()/3.0)-(SPACE*3.0); + // [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], egui::Button::new("Reset")).on_hover_text("Reset changes").clicked() { + 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], egui::Button::new("Save")).on_hover_text("Save changes").clicked() { + 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(); @@ -839,66 +889,83 @@ impl eframe::App for App { 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), + }; } }); + // [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], egui::SelectableLabel::new(!self.state.p2pool.simple, "Advanced")).on_hover_text(P2POOL_ADVANCED).clicked() { + 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], egui::SelectableLabel::new(self.state.p2pool.simple, "Simple")).on_hover_text(P2POOL_SIMPLE).clicked() { + 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; if self.p2pool { - if ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart P2Pool").clicked() { self.p2pool = false; } - if ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop P2Pool").clicked() { self.p2pool = false; } + if ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart P2Pool").clicked() { self.p2pool = false; } + if ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop P2Pool").clicked() { self.p2pool = false; } ui.add_enabled_ui(false, |ui| { - ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start P2Pool"); + ui.add_sized([width, height], Button::new("⏺")).on_hover_text("Start P2Pool"); }); } else { ui.add_enabled_ui(false, |ui| { - ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart P2Pool"); - ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop P2Pool"); + ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart P2Pool"); + ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop P2Pool"); }); - if ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start P2Pool").clicked() { self.p2pool = true; } + if ui.add_sized([width, height], Button::new("⏺")).on_hover_text("Start P2Pool").clicked() { self.p2pool = true; } } }); - } + }, Tab::Xmrig => { ui.group(|ui| { let width = width / 1.5; - if ui.add_sized([width, height], egui::SelectableLabel::new(!self.state.xmrig.simple, "Advanced")).clicked() { + 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], egui::SelectableLabel::new(self.state.xmrig.simple, "Simple")).clicked() { + 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; if self.xmrig { - if ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart XMRig").clicked() { self.xmrig = false; } - if ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop XMRig").clicked() { self.xmrig = false; } + if ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart XMRig").clicked() { self.xmrig = false; } + if ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop XMRig").clicked() { self.xmrig = false; } ui.add_enabled_ui(false, |ui| { - ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start XMRig"); + ui.add_sized([width, height], Button::new("⏺")).on_hover_text("Start XMRig"); }); } else { ui.add_enabled_ui(false, |ui| { - ui.add_sized([width, height], egui::Button::new("⟲")).on_hover_text("Restart XMRig"); - ui.add_sized([width, height], egui::Button::new("⏹")).on_hover_text("Stop XMRig"); + ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart XMRig"); + ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop XMRig"); }); - if ui.add_sized([width, height], egui::Button::new("⏺")).on_hover_text("Start XMRig").clicked() { self.xmrig = true; } + if ui.add_sized([width, height], Button::new("⏺")).on_hover_text("Start XMRig").clicked() { self.xmrig = true; } } }); - } + }, _ => (), } }); @@ -906,11 +973,11 @@ impl eframe::App for App { }); // Middle panel, contents of the [Tab] - egui::CentralPanel::default().show(ctx, |ui| { + 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(egui::TextStyle::Body); + ui.style_mut().override_text_style = Some(TextStyle::Body); match self.tab { Tab::About => { ui.add_space(10.0); @@ -925,9 +992,9 @@ impl eframe::App for App { ui.hyperlink_to("[XMRig]", "https://www.github.com/xmrig/xmrig"); ui.label("miner for max hashrate"); - ui.add_space(ui.available_height()/2.0); + ui.add_space(ui.available_height()/1.8); ui.hyperlink_to("Powered by egui", "https://github.com/emilk/egui"); - ui.hyperlink_to(format!("{} {}", GITHUB, "Made by hinto-janaiyo"), "https://gupax.io"); + ui.hyperlink_to(format!("{}", "Made by hinto-janaiyo"), "https://gupax.io"); ui.label("egui is licensed under MIT & Apache-2.0"); ui.label("Gupax, P2Pool, and XMRig are licensed under GPLv3"); }); @@ -936,13 +1003,13 @@ impl eframe::App for App { Status::show(self, self.width, self.height, ctx, ui); } Tab::Gupax => { - Gupax::show(&mut self.state.gupax, &self.og, &self.state.version, &self.update, &self.file_window, &self.state_path, self.width, self.height, ctx, ui); + Gupax::show(&mut self.state.gupax, &self.og, &self.state.version, &self.update, &self.file_window, &self.state_path, self.width, self.height, frame, ctx, ui); } Tab::P2pool => { P2pool::show(&mut self.state.p2pool, &mut self.node_vec, &self.og, self.p2pool, &self.ping, &self.regex, self.width, self.height, ctx, ui); } Tab::Xmrig => { - Xmrig::show(&mut self.state.xmrig, self.width, self.height, ctx, ui); + Xmrig::show(&mut self.state.xmrig, &mut self.pool_vec, &self.regex, self.width, self.height, ctx, ui); } } }); diff --git a/src/node.rs b/src/node.rs index a676f30..8d26e85 100644 --- a/src/node.rs +++ b/src/node.rs @@ -15,7 +15,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::State; +use crate::{ + State, + constants::*, +}; use serde::{Serialize,Deserialize}; use std::time::{Instant,Duration}; use std::sync::{Arc,Mutex}; @@ -41,8 +44,8 @@ pub const FEATHER_2: &'static str = "selsta2.featherwallet.net:18081"; pub const MAJESTICBANK_IS: &'static str = "node.majesticbank.is:18089"; pub const MAJESTICBANK_SU: &'static str = "node.majesticbank.su:18089"; pub const MONERUJO: &'static str = "nodex.monerujo.io:18081"; -pub const PLOWSOF_1: &'static str = "node.monerodevs.org:18089"; -pub const PLOWSOF_2: &'static str = "node2.monerodevs.org:18089"; +pub const PLOWSOF_1: &'static str = "node.monerodevs.org:18089"; // ZMQ = 18084 +pub const PLOWSOF_2: &'static str = "node2.monerodevs.org:18089"; // ZMQ = 18084 pub const RINO: &'static str = "node.community.rino.io:18081"; pub const SETH: &'static str = "node.sethforprivacy.com:18089"; pub const SUPPORTXMR: &'static str = "node.supportxmr.com:18081"; @@ -307,13 +310,13 @@ impl Ping { }; let color; if ms < 300 { - color = Color32::from_rgb(100, 230, 100); // GREEN + color = GREEN; } else if ms < 1000 { - color = Color32::from_rgb(230, 230, 100); // YELLOW + color = YELLOW; } else if ms < 5000 { - color = Color32::from_rgb(230, 50, 50); // RED + color = RED; } else { - color = Color32::BLACK; + color = BLACK; } let mut ping = ping.lock().unwrap(); ping.msg = info; diff --git a/src/p2pool.rs b/src/p2pool.rs index 3007b9b..512ac10 100644 --- a/src/p2pool.rs +++ b/src/p2pool.rs @@ -22,7 +22,8 @@ use crate::{ node::* }; use egui::{ - TextEdit,SelectableLabel,ComboBox,Label,Button,Color32,RichText,Slider, + TextEdit,SelectableLabel,ComboBox,Label,Button, + Color32,RichText,Slider,Checkbox,ProgressBar,Spinner, TextStyle::*, }; use std::sync::{Arc,Mutex}; @@ -34,23 +35,23 @@ impl P2pool { let text_edit = height / 22.0; //---------------------------------------------------------------------------------------------------- Console ui.group(|ui| { - let height = height / SPACE; + let height = height / 10.0; let width = width - SPACE; ui.style_mut().override_text_style = Some(Monospace); - ui.add_sized([width, height*3.0], TextEdit::multiline(&mut "".to_string())); + ui.add_sized([width, height*3.5], TextEdit::multiline(&mut "".to_string())); ui.add_sized([width, text_edit], TextEdit::hint_text(TextEdit::singleline(&mut "".to_string()), r#"Type a command (e.g "help" or "status") and press Enter"#)); }); //---------------------------------------------------------------------------------------------------- Args - if ! self.simple { - ui.group(|ui| { ui.horizontal(|ui| { - let width = (width/10.0) - SPACE; - ui.style_mut().override_text_style = Some(Monospace); - ui.add_sized([width, text_edit], Label::new("Command arguments:")); - ui.add_sized([ui.available_width(), text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.arguments), r#"--wallet <...> --host <...>"#)).on_hover_text(P2POOL_COMMAND); - self.arguments.truncate(1024); - })}); - ui.set_enabled(self.arguments.is_empty()); + if self.simple == false { + ui.group(|ui| { ui.horizontal(|ui| { + let width = (width/10.0) - SPACE; + ui.style_mut().override_text_style = Some(Monospace); + ui.add_sized([width, text_edit], Label::new("Command arguments:")); + ui.add_sized([ui.available_width(), text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.arguments), r#"--wallet <...> --host <...>"#)).on_hover_text(P2POOL_COMMAND); + self.arguments.truncate(1024); + })}); + ui.set_enabled(self.arguments.is_empty()); } //---------------------------------------------------------------------------------------------------- Address @@ -154,11 +155,11 @@ impl P2pool { ui.add_sized([width, height], Label::new(msg)); ui.add_space(5.0); if pinging { - ui.add_sized([width, height], egui::Spinner::new().size(height)); + ui.add_sized([width, height], Spinner::new().size(height)); } else { - ui.add_sized([width, height], egui::Label::new("...")); + ui.add_sized([width, height], Label::new("...")); } - ui.add_sized([width, height], egui::ProgressBar::new(prog.round()/100.0)); + ui.add_sized([width, height], ProgressBar::new(prog.round()/100.0)); ui.add_space(5.0); }); }); @@ -168,13 +169,13 @@ impl P2pool { let width = (width/2.0)-(SPACE*1.75); // [Auto-node] + [Auto-select] let mut style = (*ctx.style()).clone(); - style.spacing.icon_width_inner = height/1.5; - style.spacing.icon_width = height; + style.spacing.icon_width_inner = width / 16.0; + style.spacing.icon_width = width / 6.0; style.spacing.icon_spacing = 20.0; ctx.set_style(style); - ui.add_sized([width, height], egui::Checkbox::new(&mut self.auto_select, "Auto-select")).on_hover_text(P2POOL_AUTO_SELECT); + ui.add_sized([width, height], Checkbox::new(&mut self.auto_select, "Auto-select")).on_hover_text(P2POOL_AUTO_SELECT); ui.separator(); - ui.add_sized([width, height], egui::Checkbox::new(&mut self.auto_node, "Auto-node")).on_hover_text(P2POOL_AUTO_NODE); + ui.add_sized([width, height], Checkbox::new(&mut self.auto_node, "Auto-node")).on_hover_text(P2POOL_AUTO_NODE); })}); //---------------------------------------------------------------------------------------------------- Advanced @@ -313,7 +314,7 @@ impl P2pool { } ui.horizontal(|ui| { let text; - if exists { text = P2POOL_SAVE } else { text = P2POOL_ADD } + if exists { text = LIST_SAVE } else { text = LIST_ADD } let text = format!("{}\n Currently selected node: {}. {}\n Current amount of nodes: {}/1000", text, self.selected_index+1, self.selected_name, node_vec_len); // If the node already exists, show [Save] and mutate the already existing node if exists { @@ -349,7 +350,7 @@ impl P2pool { // [Delete] ui.horizontal(|ui| { ui.set_enabled(node_vec_len > 1); - let text = format!("{}\n Currently selected node: {}. {}\n Current amount of nodes: {}/1000", P2POOL_DELETE, self.selected_index+1, self.selected_name, node_vec_len); + let text = format!("{}\n Currently selected node: {}. {}\n Current amount of nodes: {}/1000", LIST_DELETE, self.selected_index+1, self.selected_name, node_vec_len); if ui.add_sized([width, text_edit], Button::new("Delete")).on_hover_text(text).clicked() { let new_name; let new_node; @@ -379,7 +380,7 @@ impl P2pool { }); ui.horizontal(|ui| { ui.set_enabled(!self.name.is_empty() || !self.ip.is_empty() || !self.rpc.is_empty() || !self.zmq.is_empty()); - if ui.add_sized([width, text_edit], Button::new("Clear")).on_hover_text(P2POOL_CLEAR).clicked() { + if ui.add_sized([width, text_edit], Button::new("Clear")).on_hover_text(LIST_CLEAR).clicked() { self.name.clear(); self.ip.clear(); self.rpc.clear(); diff --git a/src/xmrig.rs b/src/xmrig.rs index 00be524..f70ad45 100644 --- a/src/xmrig.rs +++ b/src/xmrig.rs @@ -15,67 +15,372 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::constants::*; -use crate::disk::Xmrig; +use crate::{ + Regexes, + constants::*, + disk::*, + node::* +}; +use egui::{ + TextEdit,SelectableLabel,ComboBox,Label,Button,RichText,Slider,Checkbox, + TextStyle::*, +}; +use std::sync::{Arc,Mutex}; +use regex::Regex; +use log::*; impl Xmrig { - pub fn show(&mut self, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) { - let height = ui.available_height() / 10.0; - let width = ui.available_width() - 10.0; + pub fn show(&mut self, pool_vec: &mut Vec<(String, Pool)>, regex: &Regexes, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) { + let text_edit = height / 22.0; + //---------------------------------------------------------------------------------------------------- Console + ui.group(|ui| { + let height = height / 10.0; + let width = width - SPACE; + ui.style_mut().override_text_style = Some(Monospace); + ui.add_sized([width, height*3.5], TextEdit::multiline(&mut "".to_string())); + ui.add_sized([width, text_edit], TextEdit::hint_text(TextEdit::singleline(&mut "".to_string()), r#"Type a command (e.g "help" or "status") and press Enter"#)); + }); + + //---------------------------------------------------------------------------------------------------- Config + if self.simple == false { + ui.group(|ui| { ui.horizontal(|ui| { + let width = (width/10.0) - SPACE; + ui.style_mut().override_text_style = Some(Monospace); + ui.add_sized([width, text_edit], Label::new("Config file:")); + ui.add_sized([ui.available_width(), text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.config), r#"...config.json"#)).on_hover_text(XMRIG_CONFIG); + self.config.truncate(1024); + })}); + ui.set_enabled(self.config.is_empty()); + //---------------------------------------------------------------------------------------------------- Address ui.group(|ui| { - ui.add_sized([width, height*4.0], egui::TextEdit::multiline(&mut "".to_owned())); - ui.add_sized([width, 30.0], egui::TextEdit::singleline(&mut "".to_owned())); + let width = width - SPACE; + ui.spacing_mut().text_edit_width = (width)-(SPACE*3.0); + ui.style_mut().override_text_style = Some(Monospace); + let text; + let color; + let len = format!("{:02}", self.address.len()); + if self.address.is_empty() { + text = format!("Monero Address [{}/95] ➖", len); + color = LIGHT_GRAY; + } else if self.address.len() == 95 && Regex::is_match(®ex.address, &self.address) && ! self.address.contains("0") && ! self.address.contains("O") && ! self.address.contains("l") { + text = format!("Monero Address [{}/95] ✔", len); + color = GREEN; + } else { + text = format!("Monero Address [{}/95] ❌", len); + color = RED; + } + ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color))); + ui.add_sized([width, text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.address), "4...")).on_hover_text(XMRIG_ADDRESS); + self.address.truncate(95); }); + } - let mut style = (*ctx.style()).clone(); - let height = ui.available_height()/1.2; - let width = width - 15.0; + //---------------------------------------------------------------------------------------------------- Threads + if self.simple { ui.add_space(SPACE); } + ui.vertical(|ui| { + let width = width/10.0; + ui.spacing_mut().icon_width = width / 25.0; ui.horizontal(|ui| { - ui.group(|ui| { ui.vertical(|ui| { - ui.group(|ui| { ui.horizontal(|ui| { - if ui.add_sized([width/2.0, height/6.0], egui::SelectableLabel::new(self.simple == false, "P2Pool Mode")).on_hover_text(XMRIG_P2POOL).clicked() { self.simple = false; }; - if ui.add_sized([width/2.0, height/6.0], egui::SelectableLabel::new(self.simple == true, "Manual Mode")).on_hover_text(XMRIG_MANUAL).clicked() { self.simple = true; }; - })}); - ui.group(|ui| { ui.horizontal(|ui| { - let width = width - 58.0; - ui.add_sized([width/4.0, height/6.0], egui::Checkbox::new(&mut self.tls, "TLS Connection")).on_hover_text(XMRIG_TLS); - ui.separator(); - ui.add_sized([width/4.0, height/6.0], egui::Checkbox::new(&mut self.hugepages_jit, "Hugepages JIT")).on_hover_text(XMRIG_HUGEPAGES_JIT); - ui.separator(); - ui.add_sized([width/4.0, height/6.0], egui::Checkbox::new(&mut self.nicehash, "Nicehash")).on_hover_text(XMRIG_NICEHASH); - ui.separator(); - ui.add_sized([width/4.0, height/6.0], egui::Checkbox::new(&mut self.keepalive, "Keepalive")).on_hover_text(XMRIG_KEEPALIVE); - })}); - })}); + ui.spacing_mut().slider_width = width*8.35; + ui.add_sized([width, text_edit], Label::new(format!("Threads [1-{}]:", self.max_threads))); + ui.add_sized([width, text_edit], Slider::new(&mut self.current_threads, 1..=self.max_threads)).on_hover_text(XMRIG_THREADS); + }); + #[cfg(not(target_os = "linux"))] // Pause on active isn't supported on Linux + ui.horizontal(|ui| { + ui.spacing_mut().slider_width = width*7.7; + ui.add_sized([width, text_edit], Label::new(format!("Pause on active [0-255]:"))); + ui.add_sized([width, text_edit], Slider::new(&mut self.pause, 0..=255)).on_hover_text(format!("{} [{}] seconds.", XMRIG_PAUSE, self.pause)); + }); + }); + + //---------------------------------------------------------------------------------------------------- Simple + let height = ui.available_height(); + if self.simple { +// ui.group(|ui| + +// }); + } else { + let height = height / 10.0; + let width = ui.available_width() - 10.0; + let mut incorrect_input = false; // This will disable [Add/Delete] on bad input + // [Pool IP/Port] + ui.horizontal(|ui| { + ui.group(|ui| { + let width = width/10.0; + ui.vertical(|ui| { + ui.style_mut().override_text_style = Some(Monospace); + ui.spacing_mut().text_edit_width = width*3.32; + ui.horizontal(|ui| { + let text; + let color; + let len = format!("{:02}", self.name.len()); + if self.name.is_empty() { + text = format!("Name [ {}/30 ]➖", len); + color = LIGHT_GRAY; + incorrect_input = true; + } else if Regex::is_match(®ex.name, &self.name) { + text = format!("Name [ {}/30 ]✔", len); + color = GREEN; + } else { + text = format!("Name [ {}/30 ]❌", len); + color = RED; + incorrect_input = true; + } + ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color))); + ui.text_edit_singleline(&mut self.name).on_hover_text(XMRIG_NAME); + self.name.truncate(30); + }); + ui.horizontal(|ui| { + let text; + let color; + let len = format!("{:03}", self.ip.len()); + if self.ip.is_empty() { + text = format!(" IP [{}/255]➖", len); + color = LIGHT_GRAY; + incorrect_input = true; + } else if self.ip == "localhost" || Regex::is_match(®ex.ipv4, &self.ip) || Regex::is_match(®ex.domain, &self.ip) { + text = format!(" IP [{}/255]✔", len); + color = GREEN; + } else { + text = format!(" IP [{}/255]❌", len); + color = RED; + incorrect_input = true; + } + ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color))); + ui.text_edit_singleline(&mut self.ip).on_hover_text(XMRIG_IP); + self.ip.truncate(255); + }); + ui.horizontal(|ui| { + let text; + let color; + let len = self.port.len(); + if self.port.is_empty() { + text = format!("Port [ {}/5 ]➖", len); + color = LIGHT_GRAY; + incorrect_input = true; + } else if Regex::is_match(®ex.port, &self.port) { + text = format!("Port [ {}/5 ]✔", len); + color = GREEN; + } else { + text = format!("Port [ {}/5 ]❌", len); + color = RED; + incorrect_input = true; + } + ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color))); + ui.text_edit_singleline(&mut self.port).on_hover_text(XMRIG_PORT); + self.port.truncate(5); + }); + ui.horizontal(|ui| { + let text; + let color; + let len = format!("{:02}", self.rig.len()); + if self.rig.is_empty() { + text = format!(" Rig [ {}/30 ]➖", len); + color = LIGHT_GRAY; + } else if Regex::is_match(®ex.name, &self.name) { + text = format!(" Rig [ {}/30 ]✔", len); + color = GREEN; + } else { + text = format!(" Rig [ {}/30 ]❌", len); + color = RED; + incorrect_input = true; + } + ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color))); + ui.text_edit_singleline(&mut self.rig).on_hover_text(XMRIG_RIG); + self.rig.truncate(30); + }); }); -// ui.group(|ui| { - style.spacing.slider_width = ui.available_width()/1.25; - ctx.set_style(style); - ui.horizontal(|ui| { - ui.add_sized([width/8.0, height/8.0], egui::Label::new(format!("Threads [1-{}]:", self.max_threads))); - ui.add_sized([width, height/8.0], egui::Slider::new(&mut self.current_threads, 1..=self.max_threads)).on_hover_text(XMRIG_THREADS); + ui.vertical(|ui| { + let width = ui.available_width(); + ui.add_space(1.0); + // [Manual node selection] + ui.spacing_mut().slider_width = width - 8.0; + ui.spacing_mut().icon_width = width / 25.0; + // [Ping List] + let text = RichText::new(format!("{}. {}", self.selected_index+1, self.selected_name)); + ComboBox::from_id_source("manual_pool").selected_text(RichText::text_style(text, Monospace)).show_ui(ui, |ui| { + let mut n = 0; + for (name, pool) in pool_vec.iter() { + let text = RichText::text_style(RichText::new(format!("{}. {}\n RIG: {}\n IP: {}\n PORT: {}\n", n+1, name, pool.rig, pool.ip, pool.port)), Monospace); + if ui.add(SelectableLabel::new(self.selected_name == *name, text)).clicked() { + self.selected_index = n; + let pool = pool.clone(); + self.selected_name = name.clone(); + self.selected_rig = pool.rig.clone(); + self.selected_ip = pool.ip.clone(); + self.selected_port = pool.port.clone(); + self.name = name.clone(); + self.rig = pool.rig; + self.ip = pool.ip; + self.port = pool.port; + } + n += 1; + } }); + // [Add/Save] + let pool_vec_len = pool_vec.len(); + let mut exists = false; + let mut save_diff = true; + let mut existing_index = 0; + for (name, pool) in pool_vec.iter() { + if *name == self.name { + exists = true; + if self.rig == pool.rig && self.ip == pool.ip && self.port == pool.port { + save_diff = false; + } + break + } + existing_index += 1; + } + ui.horizontal(|ui| { + let text; + if exists { text = LIST_SAVE } else { text = LIST_ADD } + let text = format!("{}\n Currently selected pool: {}. {}\n Current amount of pools: {}/1000", text, self.selected_index+1, self.selected_name, pool_vec_len); + // If the pool already exists, show [Save] and mutate the already existing pool + if exists { + ui.set_enabled(!incorrect_input && save_diff); + if ui.add_sized([width, text_edit], Button::new("Save")).on_hover_text(text).clicked() { + let pool = Pool { + rig: self.rig.clone(), + ip: self.ip.clone(), + port: self.port.clone(), + }; + pool_vec[existing_index].1 = pool; + info!("Node | S | [index: {}, name: \"{}\", rig: \"{}\", ip: \"{}\", pool: {}]", existing_index+1, self.name, self.rig, self.ip, self.port); + } + // Else, add to the list + } else { + ui.set_enabled(!incorrect_input && pool_vec_len < 1000); + if ui.add_sized([width, text_edit], Button::new("Add")).on_hover_text(text).clicked() { + let pool = Pool { + rig: self.rig.clone(), + ip: self.ip.clone(), + port: self.port.clone(), + }; + pool_vec.push((self.name.clone(), pool)); + self.selected_index = pool_vec_len; + self.selected_name = self.name.clone(); + self.selected_rig = self.rig.clone(); + self.selected_ip = self.ip.clone(); + self.selected_port = self.port.clone(); + info!("Node | A | [index: {}, name: \"{}\", rig: \"{}\", ip: \"{}\", port: {}]", pool_vec_len, self.name, self.rig, self.ip, self.port); + } + } + }); + // [Delete] + ui.horizontal(|ui| { + ui.set_enabled(pool_vec_len > 1); + let text = format!("{}\n Currently selected pool: {}. {}\n Current amount of pools: {}/1000", LIST_DELETE, self.selected_index+1, self.selected_name, pool_vec_len); + if ui.add_sized([width, text_edit], Button::new("Delete")).on_hover_text(text).clicked() { + let new_name; + let new_pool; + match self.selected_index { + 0 => { + new_name = pool_vec[1].0.clone(); + new_pool = pool_vec[1].1.clone(); + pool_vec.remove(0); + } + _ => { + pool_vec.remove(self.selected_index); + self.selected_index = self.selected_index-1; + new_name = pool_vec[self.selected_index].0.clone(); + new_pool = pool_vec[self.selected_index].1.clone(); + } + }; + self.selected_name = new_name.clone(); + self.selected_rig = new_pool.rig.clone(); + self.selected_ip = new_pool.ip.clone(); + self.selected_port = new_pool.port.clone(); + self.name = new_name; + self.rig = new_pool.rig; + self.ip = new_pool.ip; + self.port = new_pool.port; + info!("Node | D | [index: {}, name: \"{}\", rig: \"{}\", ip: \"{}\", port: {}]", self.selected_index, self.selected_name, self.selected_rig, self.selected_ip, self.selected_port); + } + }); + ui.horizontal(|ui| { + ui.set_enabled(!self.name.is_empty() || !self.ip.is_empty() || !self.port.is_empty()); + if ui.add_sized([width, text_edit], Button::new("Clear")).on_hover_text(LIST_CLEAR).clicked() { + self.name.clear(); + self.rig.clear(); + self.ip.clear(); + self.port.clear(); + } + }); + }); + }); + }); + ui.add_space(5.0); + // [HTTP API IP/Port] + [TLS + Keepalive] + ui.group(|ui| { ui.horizontal(|ui| { + ui.vertical(|ui| { + let width = width/10.0; + ui.style_mut().override_text_style = Some(Monospace); + ui.spacing_mut().text_edit_width = width*2.39; + // HTTP API ui.horizontal(|ui| { - ui.add_sized([width/8.0, height/8.0], egui::Label::new("CPU Priority [0-5]:")); - ui.add_sized([width, height/8.0], egui::Slider::new(&mut self.priority, 0..=5)).on_hover_text(XMRIG_PRIORITY); + let text; + let color; + let len = format!("{:03}", self.api_ip.len()); + if self.api_ip.is_empty() { + text = format!("HTTP API IP [{}/255]➖", len); + color = LIGHT_GRAY; + incorrect_input = true; + } else if self.api_ip == "localhost" || Regex::is_match(®ex.ipv4, &self.api_ip) || Regex::is_match(®ex.domain, &self.api_ip) { + text = format!("HTTP API IP [{}/255]✔", len); + color = GREEN; + } else { + text = format!("HTTP API IP [{}/255]❌", len); + color = RED; + incorrect_input = true; + } + ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color))); + ui.text_edit_singleline(&mut self.api_ip).on_hover_text(XMRIG_API_IP); + self.api_ip.truncate(255); }); -// }); + ui.horizontal(|ui| { + let text; + let color; + let len = self.port.len(); + if self.api_port.is_empty() { + text = format!("HTTP API Port [ {}/5 ]➖", len); + color = LIGHT_GRAY; + incorrect_input = true; + } else if Regex::is_match(®ex.port, &self.api_port) { + text = format!("HTTP API Port [ {}/5 ]✔", len); + color = GREEN; + } else { + text = format!(" Port [ {}/5 ]❌", len); + color = RED; + incorrect_input = true; + } + ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color))); + ui.text_edit_singleline(&mut self.api_port).on_hover_text(XMRIG_API_PORT); + self.api_port.truncate(5); + }); + }); -// ui.group(|ui| { - if self.simple == false { ui.set_enabled(false); } - let width = width/4.0; + ui.separator(); + + ui.vertical(|ui| { + // TLS/Keepalive ui.horizontal(|ui| { - ui.add_sized([width/8.0, height/8.0], egui::Label::new("Pool IP:")); - ui.spacing_mut().text_edit_width = ui.available_width() - 35.0; - ui.text_edit_singleline(&mut self.pool); + let width = (ui.available_width()/2.0)-11.0; + let height = text_edit*2.0; + let mut style = (*ctx.style()).clone(); + style.spacing.icon_width_inner = width / 8.0; + style.spacing.icon_width = width / 6.0; + style.spacing.icon_spacing = 20.0; + ctx.set_style(style); + ui.add_sized([width, height], Checkbox::new(&mut self.tls, "TLS Connection")).on_hover_text(XMRIG_TLS); + ui.separator(); + ui.add_sized([width, height], Checkbox::new(&mut self.keepalive, "Keepalive")).on_hover_text(XMRIG_KEEPALIVE); }); - ui.horizontal(|ui| { - ui.add_sized([width/8.0, height/8.0], egui::Label::new("Address:")); - ui.spacing_mut().text_edit_width = ui.available_width() - 35.0; - ui.text_edit_singleline(&mut self.address); - }); -// }); + }); + }); + }); + } } } diff --git a/utils/prepare.sh b/utils/prepare.sh index 58fca59..656adf1 100755 --- a/utils/prepare.sh +++ b/utils/prepare.sh @@ -36,5 +36,5 @@ cat CHANGELOG.md >> CHANGELOG.md.new mv -f CHANGELOG.md.new CHANGELOG.md # commit -git add . +git add CHANGELOG.md README.md git commit -m "prepare $1"