//! ## SetupActivity //! //! `setup_activity` is the module which implements the Setup activity, which is the activity to //! work on termscp configuration // Locals use std::env; use tuirealm::ratatui::style::Color; use tuirealm::{State, StateValue}; use super::{Id, IdSsh, IdTheme, SetupActivity, ViewLayout}; // Ext use crate::config::themes::Theme; impl SetupActivity { /// On , if there are changes in the configuration, the quit dialog must be shown, otherwise /// we can exit without any problem pub(super) fn action_on_esc(&mut self) { if self.config_changed() { self.mount_quit(); } else { self.exit_reason = Some(super::ExitReason::Quit); } } /// Save all configurations. If current tab can load values, they will be loaded, otherwise they'll just be saved. /// Once all the configuration has been changed, set config_changed to false pub(super) fn action_save_all(&mut self) -> Result<(), String> { self.action_save_config()?; self.action_save_theme()?; // Set config changed to false self.set_config_changed(false); Ok(()) } /// Save configuration fn action_save_config(&mut self) -> Result<(), String> { // Collect input values if in setup form if self.layout == ViewLayout::SetupForm { self.collect_input_values(); } self.save_config() } /// Save configuration fn action_save_theme(&mut self) -> Result<(), String> { // Collect input values if in theme form if self.layout == ViewLayout::Theme { self.collect_styles() .map_err(|e| format!("'{e:?}' has an invalid color"))?; } // save theme self.save_theme() } /// Change view tab and load input values in order not to lose them pub(super) fn action_change_tab(&mut self, new_tab: ViewLayout) -> Result<(), String> { // load values for current tab first match self.layout { ViewLayout::SetupForm => self.collect_input_values(), ViewLayout::Theme => self .collect_styles() .map_err(|e| format!("'{e:?}' has an invalid color"))?, _ => {} } // Update view self.init(new_tab); Ok(()) } /// Reset configuration input fields pub(super) fn action_reset_config(&mut self) -> Result<(), String> { match self.reset_config_changes() { Err(err) => Err(err), Ok(_) => { self.load_input_values(); Ok(()) } } } /// Reset configuration input fields pub(super) fn action_reset_theme(&mut self) -> Result<(), String> { match self.reset_theme_changes() { Err(err) => Err(err), Ok(_) => { self.load_styles(); Ok(()) } } } /// delete of a ssh key pub(super) fn action_delete_ssh_key(&mut self) { // Get key // get index let idx: Option = match self.app.state(&Id::Ssh(IdSsh::SshKeys)) { Ok(State::One(StateValue::Usize(idx))) => Some(idx), _ => None, }; // get ssh key and delete it if let Some(Err(err)) = idx .and_then(|i| self.config().iter_ssh_keys().nth(i).cloned()) .and_then(|key| self.config().get_ssh_key(&key)) .map(|(host, username, _)| self.delete_ssh_key(host.as_str(), username.as_str())) { // Report error self.mount_error(err.as_str()); } } /// Create a new ssh key pub(super) fn action_new_ssh_key(&mut self) -> Result<(), String> { // get parameters let host: String = match self.app.state(&Id::Ssh(IdSsh::SshHost)) { Ok(State::One(StateValue::String(host))) => host, _ => String::new(), }; let username: String = match self.app.state(&Id::Ssh(IdSsh::SshUsername)) { Ok(State::One(StateValue::String(user))) => user, _ => String::new(), }; // Prepare text editor unsafe { env::set_var("EDITOR", self.config().get_text_editor()); } let placeholder: String = format!("# Type private SSH key for {username}@{host}\n"); // Put input mode back to normal if let Err(err) = self.context_mut().terminal().disable_raw_mode() { error!("Could not disable raw mode: {}", err); } // Leave alternate mode if let Err(err) = self.context_mut().terminal().leave_alternate_screen() { error!("Could not leave alternate screen: {}", err); } // Lock ports assert!(self.app.lock_ports().is_ok()); // Write key to file let res = match edit::edit(placeholder.as_bytes()) { Ok(rsa_key) => { // Remove placeholder from `rsa_key` let rsa_key: String = rsa_key.as_str().replace(placeholder.as_str(), ""); if rsa_key.is_empty() { // Report error: empty key Err("SSH key is empty!".to_string()) } else { self.add_ssh_key(host.as_str(), username.as_str(), rsa_key.as_str()) .map_err(|e| format!("Could not create new private key: {e}")) } } Err(err) => { // Report error Err(format!("Could not write private key to file: {err}")) } }; // Restore terminal if let Some(ctx) = self.context.as_mut() { // Enter alternate mode if let Err(err) = ctx.terminal().enter_alternate_screen() { error!("Could not enter alternate screen: {}", err); } // Re-enable raw mode if let Err(err) = ctx.terminal().enable_raw_mode() { error!("Failed to enter raw mode: {}", err); } if let Err(err) = ctx.terminal().clear_screen() { error!("Could not clear screen screen: {}", err); } // Unlock ports assert!(self.app.unlock_ports().is_ok()); } res } /// Given a component and a color, save the color into the theme pub(super) fn action_save_color(&mut self, component: IdTheme, color: Color) { let theme: &mut Theme = self.theme_mut(); match component { IdTheme::AuthAddress => { theme.auth_address = color; } IdTheme::AuthBookmarks => { theme.auth_bookmarks = color; } IdTheme::AuthPassword => { theme.auth_password = color; } IdTheme::AuthPort => { theme.auth_port = color; } IdTheme::AuthProtocol => { theme.auth_protocol = color; } IdTheme::AuthRecentHosts => { theme.auth_recents = color; } IdTheme::AuthUsername => { theme.auth_username = color; } IdTheme::MiscError => { theme.misc_error_dialog = color; } IdTheme::MiscInfo => { theme.misc_info_dialog = color; } IdTheme::MiscInput => { theme.misc_input_dialog = color; } IdTheme::MiscKeys => { theme.misc_keys = color; } IdTheme::MiscQuit => { theme.misc_quit_dialog = color; } IdTheme::MiscSave => { theme.misc_save_dialog = color; } IdTheme::MiscWarn => { theme.misc_warn_dialog = color; } IdTheme::ExplorerLocalBg => { theme.transfer_local_explorer_background = color; } IdTheme::ExplorerLocalFg => { theme.transfer_local_explorer_foreground = color; } IdTheme::ExplorerLocalHg => { theme.transfer_local_explorer_highlighted = color; } IdTheme::ExplorerRemoteBg => { theme.transfer_remote_explorer_background = color; } IdTheme::ExplorerRemoteFg => { theme.transfer_remote_explorer_foreground = color; } IdTheme::ExplorerRemoteHg => { theme.transfer_remote_explorer_highlighted = color; } IdTheme::LogBg => { theme.transfer_log_background = color; } IdTheme::LogWindow => { theme.transfer_log_window = color; } IdTheme::ProgBarFull => { theme.transfer_progress_bar_full = color; } IdTheme::ProgBarPartial => { theme.transfer_progress_bar_partial = color; } IdTheme::StatusHidden => { theme.transfer_status_hidden = color; } IdTheme::StatusSorting => { theme.transfer_status_sorting = color; } IdTheme::StatusSync => { theme.transfer_status_sync_browsing = color; } _ => {} } } /// Collect values from input and put them into the theme. /// If a component has an invalid color, returns Err(component_id) fn collect_styles(&mut self) -> Result<(), Id> { // auth let auth_address = self .get_color(&Id::Theme(IdTheme::AuthAddress)) .map_err(|_| Id::Theme(IdTheme::AuthAddress))?; let auth_bookmarks = self .get_color(&Id::Theme(IdTheme::AuthBookmarks)) .map_err(|_| Id::Theme(IdTheme::AuthBookmarks))?; let auth_password = self .get_color(&Id::Theme(IdTheme::AuthPassword)) .map_err(|_| Id::Theme(IdTheme::AuthPassword))?; let auth_port = self .get_color(&Id::Theme(IdTheme::AuthPort)) .map_err(|_| Id::Theme(IdTheme::AuthPort))?; let auth_protocol = self .get_color(&Id::Theme(IdTheme::AuthProtocol)) .map_err(|_| Id::Theme(IdTheme::AuthProtocol))?; let auth_recents = self .get_color(&Id::Theme(IdTheme::AuthRecentHosts)) .map_err(|_| Id::Theme(IdTheme::AuthRecentHosts))?; let auth_username = self .get_color(&Id::Theme(IdTheme::AuthUsername)) .map_err(|_| Id::Theme(IdTheme::AuthUsername))?; // misc let misc_error_dialog = self .get_color(&Id::Theme(IdTheme::MiscError)) .map_err(|_| Id::Theme(IdTheme::MiscError))?; let misc_info_dialog = self .get_color(&Id::Theme(IdTheme::MiscInfo)) .map_err(|_| Id::Theme(IdTheme::MiscInfo))?; let misc_input_dialog = self .get_color(&Id::Theme(IdTheme::MiscInput)) .map_err(|_| Id::Theme(IdTheme::MiscInput))?; let misc_keys = self .get_color(&Id::Theme(IdTheme::MiscKeys)) .map_err(|_| Id::Theme(IdTheme::MiscKeys))?; let misc_quit_dialog = self .get_color(&Id::Theme(IdTheme::MiscQuit)) .map_err(|_| Id::Theme(IdTheme::MiscQuit))?; let misc_save_dialog = self .get_color(&Id::Theme(IdTheme::MiscSave)) .map_err(|_| Id::Theme(IdTheme::MiscSave))?; let misc_warn_dialog = self .get_color(&Id::Theme(IdTheme::MiscWarn)) .map_err(|_| Id::Theme(IdTheme::MiscWarn))?; // transfer let transfer_local_explorer_background = self .get_color(&Id::Theme(IdTheme::ExplorerLocalBg)) .map_err(|_| Id::Theme(IdTheme::ExplorerLocalBg))?; let transfer_local_explorer_foreground = self .get_color(&Id::Theme(IdTheme::ExplorerLocalFg)) .map_err(|_| Id::Theme(IdTheme::ExplorerLocalFg))?; let transfer_local_explorer_highlighted = self .get_color(&Id::Theme(IdTheme::ExplorerLocalHg)) .map_err(|_| Id::Theme(IdTheme::ExplorerLocalHg))?; let transfer_remote_explorer_background = self .get_color(&Id::Theme(IdTheme::ExplorerRemoteBg)) .map_err(|_| Id::Theme(IdTheme::ExplorerRemoteBg))?; let transfer_remote_explorer_foreground = self .get_color(&Id::Theme(IdTheme::ExplorerRemoteFg)) .map_err(|_| Id::Theme(IdTheme::ExplorerRemoteFg))?; let transfer_remote_explorer_highlighted = self .get_color(&Id::Theme(IdTheme::ExplorerRemoteHg)) .map_err(|_| Id::Theme(IdTheme::ExplorerRemoteHg))?; let transfer_log_background = self .get_color(&Id::Theme(IdTheme::LogBg)) .map_err(|_| Id::Theme(IdTheme::LogBg))?; let transfer_log_window = self .get_color(&Id::Theme(IdTheme::LogWindow)) .map_err(|_| Id::Theme(IdTheme::LogWindow))?; let transfer_progress_bar_full = self .get_color(&Id::Theme(IdTheme::ProgBarFull)) .map_err(|_| Id::Theme(IdTheme::ProgBarFull))?; let transfer_progress_bar_partial = self .get_color(&Id::Theme(IdTheme::ProgBarPartial)) .map_err(|_| Id::Theme(IdTheme::ProgBarPartial))?; let transfer_status_hidden = self .get_color(&Id::Theme(IdTheme::StatusHidden)) .map_err(|_| Id::Theme(IdTheme::StatusHidden))?; let transfer_status_sorting = self .get_color(&Id::Theme(IdTheme::StatusSorting)) .map_err(|_| Id::Theme(IdTheme::StatusSorting))?; let transfer_status_sync_browsing = self .get_color(&Id::Theme(IdTheme::StatusSync)) .map_err(|_| Id::Theme(IdTheme::StatusSync))?; // Update theme let theme = self.theme_mut(); theme.auth_address = auth_address; theme.auth_bookmarks = auth_bookmarks; theme.auth_password = auth_password; theme.auth_port = auth_port; theme.auth_protocol = auth_protocol; theme.auth_recents = auth_recents; theme.auth_username = auth_username; theme.misc_error_dialog = misc_error_dialog; theme.misc_info_dialog = misc_info_dialog; theme.misc_input_dialog = misc_input_dialog; theme.misc_keys = misc_keys; theme.misc_quit_dialog = misc_quit_dialog; theme.misc_save_dialog = misc_save_dialog; theme.misc_warn_dialog = misc_warn_dialog; theme.transfer_local_explorer_background = transfer_local_explorer_background; theme.transfer_local_explorer_foreground = transfer_local_explorer_foreground; theme.transfer_local_explorer_highlighted = transfer_local_explorer_highlighted; theme.transfer_remote_explorer_background = transfer_remote_explorer_background; theme.transfer_remote_explorer_foreground = transfer_remote_explorer_foreground; theme.transfer_remote_explorer_highlighted = transfer_remote_explorer_highlighted; theme.transfer_log_background = transfer_log_background; theme.transfer_log_window = transfer_log_window; theme.transfer_progress_bar_full = transfer_progress_bar_full; theme.transfer_progress_bar_partial = transfer_progress_bar_partial; theme.transfer_status_hidden = transfer_status_hidden; theme.transfer_status_sorting = transfer_status_sorting; theme.transfer_status_sync_browsing = transfer_status_sync_browsing; Ok(()) } /// Get color from component fn get_color(&self, component: &Id) -> Result { match self.app.state(component) { Ok(State::One(StateValue::String(color))) => { match crate::utils::parser::parse_color(color.as_str()) { Some(c) => Ok(c), None => Err(()), } } _ => Err(()), } } }