diff --git a/Cargo.lock b/Cargo.lock index a6ff414..8963d5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -427,6 +427,7 @@ dependencies = [ "espanso-info", "espanso-inject", "espanso-ipc", + "espanso-kvs", "espanso-mac-utils", "espanso-match", "espanso-migrate", @@ -549,6 +550,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "espanso-kvs" +version = "0.1.0" +dependencies = [ + "anyhow", + "log", + "serde", + "serde_json", + "tempdir", + "thiserror", +] + [[package]] name = "espanso-mac-utils" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index f3a1639..426c234 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,5 @@ members = [ "espanso-modulo", "espanso-migrate", "espanso-mac-utils", + "espanso-kvs", ] \ No newline at end of file diff --git a/Makefile.toml b/Makefile.toml index 32e55db..bb19e44 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -34,8 +34,8 @@ dependencies=["create-bundle"] [tasks.test] command = "cargo" -args = ["test", "--workspace", "--exclude", "espanso-modulo", "--no-default-features"] +args = ["test", "--workspace", "--exclude", "espanso-modulo", "--exclude", "espanso-ipc", "--no-default-features"] [tasks.test-output] command = "cargo" -args = ["test", "--workspace", "--exclude", "espanso-modulo", "--no-default-features", "--", "--nocapture"] \ No newline at end of file +args = ["test", "--workspace", "--exclude", "espanso-modulo", "--exclude", "espanso-ipc", "--no-default-features", "--", "--nocapture"] \ No newline at end of file diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml index a12194c..daf7be1 100644 --- a/espanso/Cargo.toml +++ b/espanso/Cargo.toml @@ -32,6 +32,7 @@ espanso-path = { path = "../espanso-path" } espanso-ipc = { path = "../espanso-ipc" } espanso-modulo = { path = "../espanso-modulo", optional = true } espanso-migrate = { path = "../espanso-migrate" } +espanso-kvs = { path = "../espanso-kvs" } maplit = "1.0.2" simplelog = "0.9.0" log = "0.4.14" diff --git a/espanso/src/cli/launcher/mod.rs b/espanso/src/cli/launcher/mod.rs index 540efc3..882c342 100644 --- a/espanso/src/cli/launcher/mod.rs +++ b/espanso/src/cli/launcher/mod.rs @@ -17,9 +17,8 @@ * along with espanso. If not, see . */ -use espanso_modulo::wizard::{MigrationResult, WizardHandlers, WizardOptions}; - use self::util::MigrationError; +use crate::preferences::Preferences; use super::{CliModule, CliModuleArgs}; @@ -41,10 +40,19 @@ pub fn new() -> CliModule { #[cfg(feature = "modulo")] fn launcher_main(args: CliModuleArgs) -> i32 { + use espanso_modulo::wizard::{MigrationResult, WizardHandlers, WizardOptions}; + + // TODO: should we create a non-gui wizard? We can also use it for the non-modulo versions of espanso + let paths = args.paths.expect("missing paths in launcher main"); let icon_paths = crate::icon::load_icon_paths(&paths.runtime).expect("unable to load icon paths"); - // TODO: should move wizard to "init" subcommand? + let preferences = + crate::preferences::get_default(&paths.runtime).expect("unable to initialize preferences"); + + let is_welcome_page_enabled = !preferences.has_completed_wizard(); + + let is_move_bundle_page_enabled = false; // TODO let is_legacy_version_page_enabled = util::is_legacy_version_running(&paths.runtime); let runtime_dir_clone = paths.runtime.clone(); @@ -55,26 +63,20 @@ fn launcher_main(args: CliModuleArgs) -> i32 { let paths_clone = paths.clone(); let backup_and_migrate_handler = Box::new(move || match util::migrate_configuration(&paths_clone) { - Ok(_) => { - MigrationResult::Success - } - Err(error) => { - match error.downcast_ref::() { - Some(MigrationError::DirtyError) => { - MigrationResult::DirtyFailure - } - Some(MigrationError::CleanError) => { - MigrationResult::CleanFailure - } - _ => { - MigrationResult::UnknownFailure - } - } - } + Ok(_) => MigrationResult::Success, + Err(error) => match error.downcast_ref::() { + Some(MigrationError::DirtyError) => MigrationResult::DirtyFailure, + Some(MigrationError::CleanError) => MigrationResult::CleanFailure, + _ => MigrationResult::UnknownFailure, + }, }); + // TODO: enable "Add to PATH" page only when NOT in portable mode + // TODO: if the user clicks on "Continue" after unchecking the "ADD to PATH" + // checkbox, we should remember (with the kvs) the choice and avoid asking again. let is_add_path_page_enabled = if cfg!(target_os = "macos") { // TODO: add actual check + // TODO: consider also Windows case true } else { false @@ -87,39 +89,46 @@ fn launcher_main(args: CliModuleArgs) -> i32 { false }; - // TODO: show welcome page only the first time (we need a persistent key-value store) // TODO: show a "espanso is now running page at the end" (it should be triggered everytime // espanso is started, unless the user unchecks "show this message at startup") // This page could also be used when the user starts espanso, but an instance is already running. - espanso_modulo::wizard::show(WizardOptions { - version: crate::VERSION.to_string(), - is_welcome_page_enabled: true, // TODO - is_move_bundle_page_enabled: false, // TODO - is_legacy_version_page_enabled, - is_migrate_page_enabled, - is_add_path_page_enabled, - is_accessibility_page_enabled, - window_icon_path: icon_paths - .wizard_icon - .map(|path| path.to_string_lossy().to_string()), - welcome_image_path: icon_paths - .logo_no_background - .map(|path| path.to_string_lossy().to_string()), - accessibility_image_1_path: None, // TODO - accessibility_image_2_path: None, // TODO - handlers: WizardHandlers { - is_legacy_version_running: Some(is_legacy_version_running_handler), - backup_and_migrate: Some(backup_and_migrate_handler), - add_to_path: None, // TODO - enable_accessibility: None, // TODO - is_accessibility_enabled: None, // TODO - }, - }); + // Only show the wizard if a panel should be displayed + if is_welcome_page_enabled + || is_move_bundle_page_enabled + || is_legacy_version_page_enabled + || is_migrate_page_enabled + || is_add_path_page_enabled + || is_accessibility_page_enabled + { + espanso_modulo::wizard::show(WizardOptions { + version: crate::VERSION.to_string(), + is_welcome_page_enabled, + is_move_bundle_page_enabled, + is_legacy_version_page_enabled, + is_migrate_page_enabled, + is_add_path_page_enabled, + is_accessibility_page_enabled, + window_icon_path: icon_paths + .wizard_icon + .map(|path| path.to_string_lossy().to_string()), + welcome_image_path: icon_paths + .logo_no_background + .map(|path| path.to_string_lossy().to_string()), + accessibility_image_1_path: None, // TODO + accessibility_image_2_path: None, // TODO + handlers: WizardHandlers { + is_legacy_version_running: Some(is_legacy_version_running_handler), + backup_and_migrate: Some(backup_and_migrate_handler), + add_to_path: None, // TODO + enable_accessibility: None, // TODO + is_accessibility_enabled: None, // TODO + }, + }); - // TODO: enable "Add to PATH" page only when NOT in portable mode - // TODO: if the user clicks on "Continue" after unchecking the "ADD to PATH" - // checkbox, we should remember (with the kvs) the choice and avoid asking again. + // TODO: check the wizard return status? + preferences.set_completed_wizard(true); + } 0 } @@ -127,4 +136,6 @@ fn launcher_main(args: CliModuleArgs) -> i32 { #[cfg(not(feature = "modulo"))] fn launcher_main(_: CliModuleArgs) -> i32 { // TODO: handle what happens here + + 0 } diff --git a/espanso/src/main.rs b/espanso/src/main.rs index a36204c..efd9ac0 100644 --- a/espanso/src/main.rs +++ b/espanso/src/main.rs @@ -43,6 +43,7 @@ mod icon; mod ipc; mod lock; mod logging; +mod preferences; mod util; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -289,6 +290,10 @@ fn main() { // TODO: explain that the register and unregister commands are only meaningful // when using the system daemon manager on macOS and Linux + // TODO: set the LSEnvironment variable as described here: https://stackoverflow.com/questions/12203377/combined-gui-and-command-line-os-x-app?rq=1 + // to detect if the executable was launched inside an AppBundle, and if so, launch the "launcher" handler + // This should only apply when on macOS. + let matches = clap_instance.clone().get_matches(); let log_level = match matches.occurrences_of("v") { 0 | 1 => LevelFilter::Info, diff --git a/espanso/src/preferences/default.rs b/espanso/src/preferences/default.rs new file mode 100644 index 0000000..f58da01 --- /dev/null +++ b/espanso/src/preferences/default.rs @@ -0,0 +1,64 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso 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. + * + * espanso 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 espanso. If not, see . + */ + +use espanso_kvs::KVS; +use serde::{de::DeserializeOwned, Serialize}; +use std::path::Path; + +use anyhow::Result; + +use super::Preferences; + +const HAS_COMPLETED_WIZARD_KEY: &str = "has_completed_wizard"; + +#[derive(Clone)] +pub struct DefaultPreferences { + kvs: KVSType, +} + +impl DefaultPreferences { + pub fn new(runtime_dir: &Path, kvs: KVSType) -> Result { + Ok(Self { kvs }) + } + + fn get(&self, key: &str) -> Option { + let value = self + .kvs + .get(key) + .expect(&format!("unable to read preference for key {}", key)); + value + } + + fn set(&self, key: &str, value: T) { + self + .kvs + .set(key, value) + .expect(&format!("unable to write preference for key {}", key)) + } +} + +impl Preferences for DefaultPreferences { + fn has_completed_wizard(&self) -> bool { + self.get(HAS_COMPLETED_WIZARD_KEY).unwrap_or(false) + } + + fn set_completed_wizard(&self, value: bool) { + self.set(HAS_COMPLETED_WIZARD_KEY, value); + } +} diff --git a/espanso/src/preferences/mod.rs b/espanso/src/preferences/mod.rs new file mode 100644 index 0000000..a83c57f --- /dev/null +++ b/espanso/src/preferences/mod.rs @@ -0,0 +1,33 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso 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. + * + * espanso 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 espanso. If not, see . + */ + +use std::path::Path; +use anyhow::Result; + +mod default; + +pub trait Preferences: Send + Sync { + fn has_completed_wizard(&self) -> bool; + fn set_completed_wizard(&self, value: bool); +} + +pub fn get_default(runtime_dir: &Path) -> Result { + let kvs = espanso_kvs::get_persistent(runtime_dir)?; + default::DefaultPreferences::new(runtime_dir, kvs) +} \ No newline at end of file