diff --git a/Cargo.toml b/Cargo.toml index e5e7786e4..3b371aa8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ derive_more = "1.0.0" accesskit = "=0.16.1" egui = { version = "0.29.1" } egui_extras = "0.29.1" +rfd = "0.15.1" qrcode = "0.14.1" eframe = { version = "0.29.1", features = ["persistence"] } strum = { version = "0.26.1", features = ["derive"] } diff --git a/src/app.rs b/src/app.rs index 009bc2f98..d952724a5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -123,7 +123,7 @@ impl AppState { let password_info = settings .clone() - .map(|(_, _, password_info)| password_info) + .map(|(_, _, password_info, _, _)| password_info) .flatten(); let mainnet_app_context = @@ -150,10 +150,23 @@ impl AppState { let mut proof_log_screen = ProofLogScreen::new(&mainnet_app_context); let mut document_query_screen = DocumentQueryScreen::new(&mainnet_app_context); let mut withdraws_status_screen = WithdrawsStatusScreen::new(&mainnet_app_context); + + let (custom_dash_qt_path, overwrite_dash_conf) = match settings.clone() { + Some((.., db_custom_dash_qt_path, db_overwrite_dash_qt)) => { + (db_custom_dash_qt_path, db_overwrite_dash_qt) + } + _ => { + // Default values: Use system default path and overwrite conf + (None, true) + } + }; + let mut network_chooser_screen = NetworkChooserScreen::new( &mainnet_app_context, testnet_app_context.as_ref(), Network::Dash, + custom_dash_qt_path, + overwrite_dash_conf, ); let mut wallets_balances_screen = WalletsBalancesScreen::new(&mainnet_app_context); @@ -162,7 +175,7 @@ impl AppState { let mut chosen_network = Network::Dash; - if let Some((network, screen_type, password_info)) = settings { + if let Some((network, screen_type, password_info, _, _)) = settings { selected_main_screen = screen_type; chosen_network = network; if chosen_network == Network::Testnet && testnet_app_context.is_some() { diff --git a/src/backend_task/core/mod.rs b/src/backend_task/core/mod.rs index 8dad43414..a4c9bd368 100644 --- a/src/backend_task/core/mod.rs +++ b/src/backend_task/core/mod.rs @@ -12,13 +12,14 @@ use std::sync::{Arc, RwLock}; pub(crate) enum CoreTask { GetBestChainLock, RefreshWalletInfo(Arc>), - StartDashQT(Network), + StartDashQT(Network, Option, bool), } impl PartialEq for CoreTask { fn eq(&self, other: &Self) -> bool { match (self, other) { (CoreTask::GetBestChainLock, CoreTask::GetBestChainLock) => true, (CoreTask::RefreshWalletInfo(_), CoreTask::RefreshWalletInfo(_)) => true, + (CoreTask::StartDashQT(_, _, _), CoreTask::StartDashQT(_, _, _)) => true, _ => false, } } @@ -44,8 +45,8 @@ impl AppContext { }) .map_err(|e| e.to_string()), CoreTask::RefreshWalletInfo(wallet) => self.refresh_wallet_info(wallet), - CoreTask::StartDashQT(network) => self - .start_dash_qt(network) + CoreTask::StartDashQT(network, custom_dash_qt, overwrite_dash_conf) => self + .start_dash_qt(network, custom_dash_qt, overwrite_dash_conf) .map_err(|e| e.to_string()) .map(|_| BackendTaskSuccessResult::None), } diff --git a/src/backend_task/core/start_dash_qt.rs b/src/backend_task/core/start_dash_qt.rs index 9b4a8a246..49d626269 100644 --- a/src/backend_task/core/start_dash_qt.rs +++ b/src/backend_task/core/start_dash_qt.rs @@ -8,19 +8,26 @@ use std::{env, io}; impl AppContext { /// Function to start Dash QT based on the selected network - pub(super) fn start_dash_qt(&self, network: Network) -> io::Result<()> { - // Determine the path to Dash-Qt based on the operating system - let dash_qt_path: PathBuf = if cfg!(target_os = "macos") { - PathBuf::from("/Applications/Dash-Qt.app/Contents/MacOS/Dash-Qt") - } else if cfg!(target_os = "windows") { - // Retrieve the PROGRAMFILES environment variable and construct the path - let program_files = env::var("PROGRAMFILES") - .map(PathBuf::from) - .map_err(|e| io::Error::new(io::ErrorKind::NotFound, e))?; - - program_files.join("DashCore\\dash-qt.exe") - } else { - PathBuf::from("/usr/local/bin/dash-qt") // Linux path + pub(super) fn start_dash_qt( + &self, + network: Network, + custom_dash_qt: Option, + overwrite_dash_conf: bool, + ) -> io::Result<()> { + let dash_qt_path = match custom_dash_qt { + Some(ref custom_path) => PathBuf::from(custom_path), + None => { + if cfg!(target_os = "macos") { + PathBuf::from("/Applications/Dash-Qt.app/Contents/MacOS/Dash-Qt") + } else if cfg!(target_os = "windows") { + // Retrieve the PROGRAMFILES environment variable or default to "C:\\Program Files" + let program_files = env::var("PROGRAMFILES") + .unwrap_or_else(|_| "C:\\Program Files".to_string()); + PathBuf::from(program_files).join("DashCore\\dash-qt.exe") + } else { + PathBuf::from("/usr/local/bin/dash-qt") // Default Linux path + } + } }; // Ensure the Dash-Qt binary path exists @@ -43,16 +50,20 @@ impl AppContext { } }; - // Construct the full path to the config file - let current_dir = env::current_dir()?; - let config_path = current_dir.join(config_file); + let mut command = Command::new(&dash_qt_path); + command.stdout(Stdio::null()).stderr(Stdio::null()); // Suppress output + + if overwrite_dash_conf { + // Construct the full path to the config file + let current_dir = env::current_dir()?; + let config_path = current_dir.join(config_file); + command.arg(format!("-conf={}", config_path.display())); + } else if network == Network::Testnet { + command.arg("-testnet"); + } - // Start Dash-Qt with the appropriate config - Command::new(&dash_qt_path) - .arg(format!("-conf={}", config_path.display())) - .stdout(Stdio::null()) // Optional: Suppress output - .stderr(Stdio::null()) - .spawn()?; // Spawn the Dash-Qt process + // Spawn the Dash-Qt process + command.spawn()?; Ok(()) } diff --git a/src/components/core_zmq_listener.rs b/src/components/core_zmq_listener.rs index ff0de263d..c02d8f7df 100644 --- a/src/components/core_zmq_listener.rs +++ b/src/components/core_zmq_listener.rs @@ -232,7 +232,7 @@ impl CoreZMQListener { .recv(&mut addr_msg, 0) .expect("Failed to receive address message"); - let data = event_msg.as_ref(); + let data: &[u8] = event_msg.as_ref(); // Explicitly annotate the type if data.len() >= 6 { let event_number = u16::from_le_bytes([data[0], data[1]]); let endpoint = addr_msg.as_str().unwrap_or(""); diff --git a/src/context.rs b/src/context.rs index c9ac5474d..dcf76b2a1 100644 --- a/src/context.rs +++ b/src/context.rs @@ -221,7 +221,17 @@ impl AppContext { } /// Retrieves the current `RootScreenType` from the settings - pub fn get_settings(&self) -> Result)>> { + pub fn get_settings( + &self, + ) -> Result< + Option<( + Network, + RootScreenType, + Option, + Option, + bool, + )>, + > { self.db.get_settings() } diff --git a/src/database/initialization.rs b/src/database/initialization.rs index 967fdcfec..2a828c88d 100644 --- a/src/database/initialization.rs +++ b/src/database/initialization.rs @@ -4,7 +4,8 @@ use rusqlite::{params, Connection}; use std::fs; use std::path::Path; -pub const DEFAULT_DB_VERSION: u16 = 2; +pub const DEFAULT_DB_VERSION: u16 = 3; + pub const DEFAULT_NETWORK: &str = "dash"; impl Database { @@ -33,6 +34,9 @@ impl Database { fn apply_version_changes(&self, version: u16) -> rusqlite::Result<()> { match version { + 3 => { + self.add_custom_dash_qt_columns()?; + } 2 => { self.initialize_proof_log_table()?; } @@ -175,6 +179,8 @@ impl Database { main_password_nonce BLOB, network TEXT NOT NULL, start_root_screen INTEGER NOT NULL, + custom_dash_qt_path TEXT, + overwrite_dash_conf INTEGER, database_version INTEGER NOT NULL )", [], diff --git a/src/database/settings.rs b/src/database/settings.rs index edec45cf6..56425b95c 100644 --- a/src/database/settings.rs +++ b/src/database/settings.rs @@ -3,6 +3,7 @@ use crate::model::password_info::PasswordInfo; use crate::ui::RootScreenType; use dash_sdk::dpp::dashcore::Network; use rusqlite::{params, Result}; +use std::path::PathBuf; use std::str::FromStr; impl Database { @@ -44,6 +45,35 @@ impl Database { Ok(()) } + pub fn update_dash_core_execution_settings( + &self, + custom_dash_path: Option, + overwrite_dash_conf: bool, + ) -> Result<()> { + self.execute( + "UPDATE settings + SET custom_dash_qt_path = ?, + overwrite_dash_conf = ? + WHERE id = 1", + rusqlite::params![custom_dash_path, overwrite_dash_conf], + )?; + + Ok(()) + } + + pub fn add_custom_dash_qt_columns(&self) -> Result<()> { + self.execute( + "ALTER TABLE settings ADD COLUMN custom_dash_qt_path TEXT DEFAULT NULL;", + (), + )?; + self.execute( + "ALTER TABLE settings ADD COLUMN overwrite_dash_conf INTEGER DEFAULT NULL;", + (), + )?; + + Ok(()) + } + /// Updates the database version in the settings table. pub fn update_database_version(&self, new_version: u16) -> Result<()> { // Ensure the database version is updated @@ -58,11 +88,21 @@ impl Database { } /// Retrieves the settings from the database. - pub fn get_settings(&self) -> Result)>> { + pub fn get_settings( + &self, + ) -> Result< + Option<( + Network, + RootScreenType, + Option, + Option, + bool, + )>, + > { // Query the settings row let conn = self.conn.lock().unwrap(); let mut stmt = - conn.prepare("SELECT network, start_root_screen, password_check, main_password_salt, main_password_nonce FROM settings WHERE id = 1")?; + conn.prepare("SELECT network, start_root_screen, password_check, main_password_salt, main_password_nonce, custom_dash_qt_path, overwrite_dash_conf FROM settings WHERE id = 1")?; let result = stmt.query_row([], |row| { let network: String = row.get(0)?; @@ -70,6 +110,8 @@ impl Database { let password_check: Option> = row.get(2)?; let main_password_salt: Option> = row.get(3)?; let main_password_nonce: Option> = row.get(4)?; + let custom_dash_qt_path: Option = row.get(5)?; + let overwrite_dash_conf: Option = row.get(6)?; // Combine the password-related fields if all are present, otherwise set to None let password_data = match (password_check, main_password_salt, main_password_nonce) { @@ -89,7 +131,13 @@ impl Database { let root_screen_type = RootScreenType::from_int(start_root_screen) .ok_or_else(|| rusqlite::Error::InvalidQuery)?; - Ok((parsed_network, root_screen_type, password_data)) + Ok(( + parsed_network, + root_screen_type, + password_data, + custom_dash_qt_path, + overwrite_dash_conf.unwrap_or(true), + )) }); match result { diff --git a/src/ui/components/top_panel.rs b/src/ui/components/top_panel.rs index bf371054c..dbc7c6f3c 100644 --- a/src/ui/components/top_panel.rs +++ b/src/ui/components/top_panel.rs @@ -92,10 +92,28 @@ fn add_connection_indicator(ui: &mut Ui, app_context: &Arc) -> AppAc }; response.clone().on_hover_text(tooltip_text); + let settings = app_context + .db + .get_settings() + .expect("Failed to db get settings"); + let (custom_dash_qt_path, overwrite_dash_conf) = match settings { + Some((.., db_custom_dash_qt_path, db_overwrite_dash_qt)) => { + (db_custom_dash_qt_path, db_overwrite_dash_qt) + } + _ => { + // Default values: Use system default path and overwrite conf + (None, true) + } + }; + // Handle click to start DashQT if disconnected if response.clicked() && !connected { let network = app_context.network; - action |= AppAction::BackendTask(BackendTask::CoreTask(CoreTask::StartDashQT(network))); + action |= AppAction::BackendTask(BackendTask::CoreTask(CoreTask::StartDashQT( + network, + custom_dash_qt_path, + overwrite_dash_conf, + ))); } }); diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index 0e99c5c82..98e35a781 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -2,6 +2,7 @@ use crate::app::AppAction; use crate::backend_task::core::{CoreItem, CoreTask}; use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::context::AppContext; +use crate::model::password_info::PasswordInfo; use crate::ui::components::left_panel::add_left_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::wallet::add_new_wallet_screen::AddNewWalletScreen; @@ -21,6 +22,9 @@ pub struct NetworkChooserScreen { pub testnet_core_status_online: bool, status_checked: bool, pub recheck_time: Option, + custom_dash_qt_path: Option, + custom_dash_qt_error_message: Option, + overwrite_dash_conf: bool, } impl NetworkChooserScreen { @@ -28,6 +32,8 @@ impl NetworkChooserScreen { mainnet_app_context: &Arc, testnet_app_context: Option<&Arc>, current_network: Network, + custom_dash_qt_path: Option, + overwrite_dash_conf: bool, ) -> Self { Self { mainnet_app_context: mainnet_app_context.clone(), @@ -37,6 +43,9 @@ impl NetworkChooserScreen { testnet_core_status_online: false, status_checked: false, recheck_time: None, + custom_dash_qt_path, + custom_dash_qt_error_message: None, + overwrite_dash_conf, } } @@ -82,6 +91,77 @@ impl NetworkChooserScreen { // Render Testnet app_action |= self.render_network_row(ui, Network::Testnet, "Testnet"); }); + egui::CollapsingHeader::new("Show more advanced settings") + .default_open(false) // The grid is hidden by default + .show(ui, |ui| { + egui::Grid::new("advanced_settings") + .show(ui, |ui| { + ui.label("Custom Dash-QT path:"); + + if ui.button("Select file").clicked() { + if let Some(path) = rfd::FileDialog::new().pick_file() { + { + let file_name = path.file_name().and_then(|f| f.to_str()); + if let Some(file_name) = file_name { + self.custom_dash_qt_path = None; + self.custom_dash_qt_error_message = None; + let required_file_name = if cfg!(target_os = "windows") { + String::from("dash-qt.exe") + } else if cfg!(target_os = "macos") { + String::from("dash-qt") + } else { //linux + String::from("dash-qt") + }; + if file_name.ends_with(required_file_name.as_str()) { + self.custom_dash_qt_path = Some(path.display().to_string()); + self.custom_dash_qt_error_message = None; + self.current_app_context().db.update_dash_core_execution_settings(self.custom_dash_qt_path.clone(), self.overwrite_dash_conf).expect("Expected to save db settings"); + } else { + self.custom_dash_qt_error_message = Some(format!("Invalid file: Please select a valid '{}'.", required_file_name)); + } + } + } + } + } + + if let Some(ref file) = self.custom_dash_qt_path { + ui.label(format!("Selected: {}", file)); + } else if let Some(ref error) = self.custom_dash_qt_error_message { + ui.colored_label(egui::Color32::RED, error); + } else { + ui.label(""); + } + if self.custom_dash_qt_path.is_some() || self.custom_dash_qt_error_message.is_some() { + if ui.button("clear").clicked() { + self.custom_dash_qt_path = None; + self.custom_dash_qt_error_message = None; + } + } + ui.end_row(); + + if ui.checkbox(&mut self.overwrite_dash_conf, "Overwrite dash.conf").clicked() { + self.current_app_context().db.update_dash_core_execution_settings(self.custom_dash_qt_path.clone(), self.overwrite_dash_conf).expect("Expected to save db settings"); + } + if !self.overwrite_dash_conf { + ui.end_row(); + if self.current_network == Network::Dash { + ui.colored_label(egui::Color32::ORANGE, "The following lines must be included in the custom Mainnet dash.conf:"); + ui.end_row(); + ui.label("zmqpubrawtxlocksig=tcp://0.0.0.0:23708"); + ui.end_row(); + ui.label("zmqpubrawchainlock=tcp://0.0.0.0:23708"); + } + else { //Testnet + ui.colored_label(egui::Color32::ORANGE, "The following lines must be included in the custom Testnet dash.conf:"); + ui.end_row(); + ui.label("zmqpubrawtxlocksig=tcp://0.0.0.0:23709"); + ui.end_row(); + ui.label("zmqpubrawchainlock=tcp://0.0.0.0:23709"); + } + } + + }); + }); app_action } @@ -157,8 +237,11 @@ impl NetworkChooserScreen { // Add a button to start the network if ui.button("Start").clicked() { - app_action |= - AppAction::BackendTask(BackendTask::CoreTask(CoreTask::StartDashQT(network))); + app_action |= AppAction::BackendTask(BackendTask::CoreTask(CoreTask::StartDashQT( + network, + self.custom_dash_qt_path.clone(), + self.overwrite_dash_conf, + ))); // in 5 seconds self.recheck_time = Some( (SystemTime::now() @@ -200,6 +283,7 @@ impl ScreenLike for NetworkChooserScreen { } } fn ui(&mut self, ctx: &Context) -> AppAction { + //let _ = self.current_app_context().db.get_settings(); let mut action = add_top_panel( ctx, self.current_app_context(),