From 2ee69e3870b9311351b7a18873f528bf2496b648 Mon Sep 17 00:00:00 2001 From: Miguel Piedrafita Date: Sun, 6 Aug 2023 07:03:50 +0100 Subject: [PATCH] Rework window management & config system --- Cargo.lock | 55 +++++++++++++++++-------- Cargo.toml | 6 ++- Tauri.toml | 1 + src/commands.rs | 2 +- src/config.rs | 54 ++++++++++++++++++++---- src/main.rs | 36 +++++++++------- src/shortcuts.rs | 62 ++++++++++++++++++++++++++++ src/tray.rs | 50 +++++++++++------------ src/window.rs | 104 +++++++++++++++++++++++++++-------------------- 9 files changed, 260 insertions(+), 110 deletions(-) create mode 100644 src/shortcuts.rs diff --git a/Cargo.lock b/Cargo.lock index 49f9e39..caa3509 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -522,7 +522,7 @@ name = "commit" version = "0.0.0" dependencies = [ "anyhow", - "confy", + "directories", "git2", "priority-queue", "rayon", @@ -531,6 +531,9 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-autostart", + "tauri-plugin-spotlight", + "thiserror", + "toml 0.7.6", "walkdir", "window-vibrancy", ] @@ -544,18 +547,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "confy" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37668cb35145dcfaa1931a5f37fde375eeae8068b4c0d2f289da28a270b2d2c" -dependencies = [ - "directories", - "serde", - "thiserror", - "toml 0.5.11", -] - [[package]] name = "convert_case" version = "0.4.0" @@ -803,11 +794,11 @@ dependencies = [ [[package]] name = "directories" -version = "4.0.1" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" dependencies = [ - "dirs-sys", + "dirs-sys 0.4.1", ] [[package]] @@ -816,7 +807,7 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "dirs-sys", + "dirs-sys 0.3.7", ] [[package]] @@ -840,6 +831,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -2195,6 +2198,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-stream" version = "0.2.0" @@ -3360,6 +3369,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "tauri-plugin-spotlight" +version = "0.1.2" +source = "git+https://github.com/zzzze/tauri-plugin-spotlight#fe00f26b6053058be113142df4032bab3120ac0e" +dependencies = [ + "cocoa 0.24.1", + "objc", + "serde", + "serde_json", + "tauri", +] + [[package]] name = "tauri-runtime" version = "0.14.0" diff --git a/Cargo.toml b/Cargo.toml index 6d73129..58c15ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ authors = ["Miguel Piedrafita "] description = "Open-source Git client for minimalists" [dependencies] -confy = "0.5.1" rayon = "1.7.0" walkdir = "2.3.3" anyhow = "1.0.72" @@ -17,7 +16,12 @@ priority-queue = "1.3.2" window-vibrancy = "0.4.0" serde = { version = "1.0", features = ["derive"] } git2 = { version = "0.17.2", default-features = false } +tauri-plugin-spotlight = { git = "https://github.com/zzzze/tauri-plugin-spotlight" } tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } +directories = "5.0.1" +toml = "0.7.6" +thiserror = "1.0.44" + [dependencies.tauri] version = "1.4" diff --git a/Tauri.toml b/Tauri.toml index 7fcb589..86c87ef 100644 --- a/Tauri.toml +++ b/Tauri.toml @@ -37,6 +37,7 @@ iconPath = "icons/menubar.png" [tauri.bundle] active = true targets = ["app", "dmg"] +category = "DeveloperTool" identifier = "build.miguel.commit" icon = [ "icons/32x32.png", diff --git a/src/commands.rs b/src/commands.rs index 3df99ac..c2b17e8 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -24,7 +24,7 @@ fn commit( ) .map_err(|e| e.to_string())?; - window::toggle(&window).unwrap(); + window::hide(&window).unwrap(); Notification::new(&app.config().tauri.bundle.identifier) .title("Commit") .body("Commit successful!") diff --git a/src/config.rs b/src/config.rs index 2cc54ec..ff5360a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,34 +1,61 @@ -use std::{path::PathBuf, process::Command, sync::RwLock}; +use std::{fs, io, path::PathBuf, process::Command, sync::RwLock}; use anyhow::Result; +use directories::ProjectDirs; + +use crate::shortcuts; pub type GetConfig = RwLock; +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Io(#[from] io::Error), + #[error(transparent)] + Encoding(#[from] toml::ser::Error), + #[error(transparent)] + Decoding(#[from] toml::de::Error), +} + #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Config { pub autostart: bool, + pub shortcut: String, pub should_push: bool, pub repo_paths: Vec, } impl Config { - pub fn load() -> Result { - let config: Self = confy::load("commit", Some("Settings"))?; + pub fn load() -> Result { + let config_path = Self::config_path(); + if config_path.exists() { + let config = toml::from_str(&fs::read_to_string(config_path)?)?; + return Ok(config); + } + + let config = Self::default(); config.save()?; + Ok(config) } - pub fn save(&self) -> Result<(), confy::ConfyError> { - confy::store("commit", Some("Settings"), self) + pub fn save(&self) -> Result<(), Error> { + let path = Self::config_path(); + fs::create_dir_all(path.parent().unwrap())?; + fs::write(path, toml::to_string_pretty(self)?)?; + + Ok(()) } pub fn manage(self) -> RwLock { RwLock::new(self) } - pub fn config_path() -> Result { - confy::get_configuration_file_path("commit", Some("Settings")) + pub fn config_path() -> PathBuf { + let project_dirs = ProjectDirs::from("", "Miguel Piedrafita", "Commit").unwrap(); + + project_dirs.config_dir().join("Settings.toml") } } @@ -38,12 +65,23 @@ impl Default for Config { autostart: false, should_push: true, repo_paths: vec![], + shortcut: shortcuts::DEFAULT_SHORTCUT.to_string(), } } } pub fn edit() -> Result<()> { - Command::new("open").arg(Config::config_path()?).status()?; + Command::new("open").arg(Config::config_path()).status()?; Ok(()) } + +pub trait ConfigExt { + fn user_config(&self) -> tauri::State<'_, GetConfig>; +} + +impl> ConfigExt for T { + fn user_config(&self) -> tauri::State<'_, GetConfig> { + self.state::() + } +} diff --git a/src/main.rs b/src/main.rs index 3578cb3..ea98971 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,15 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use anyhow::anyhow; -use config::Config; +use config::{Config, ConfigExt}; use std::error::Error; -use tauri::{generate_context, ActivationPolicy, Builder as Tauri, GlobalShortcutManager, Manager}; +use tauri::{generate_context, ActivationPolicy, Builder as Tauri, Manager}; use tauri_plugin_autostart::{self as tauri_autostart, MacosLauncher}; +use tauri_plugin_spotlight::WindowConfig; mod commands; mod config; +mod shortcuts; mod tray; mod utils; mod window; @@ -23,6 +25,16 @@ fn main() { .invoke_handler(commands::handler()) .on_system_tray_event(tray::handle) .plugin(tauri_autostart::init(MacosLauncher::LaunchAgent, None)) + .plugin(tauri_plugin_spotlight::init(Some( + tauri_plugin_spotlight::PluginConfig { + windows: Some(vec![WindowConfig { + macos_window_level: Some(20), + label: String::from(window::NAME), + shortcut: String::from(shortcuts::DEFAULT_SHORTCUT), + }]), + global_close_shortcut: None, + }, + ))) .build(generate_context!()) .expect("error while running tauri application"); @@ -33,25 +45,21 @@ fn setup_tauri(app: &mut tauri::App) -> Result<(), Box<(dyn Error + 'static)>> { app.set_activation_policy(ActivationPolicy::Accessory); let window = app - .get_window("main") + .get_window(window::NAME) .ok_or_else(|| anyhow!("Window not found"))?; window::make_transparent(&window).map_err(|_| { anyhow!("Unsupported platform! 'apply_vibrancy' is only supported on macOS") })?; - let mut shortcuts = app.global_shortcut_manager(); + let config = app.user_config(); + let config = config + .read() + .map_err(|_| anyhow!("Failed to read config"))?; - let window_handle = window.clone(); - shortcuts.register("CmdOrControl+Alt+Shift+C", move || { - window::toggle(&window_handle).unwrap() - })?; - - shortcuts.register("CmdOrControl+,", move || { - if window.is_focused().unwrap() { - config::edit().unwrap(); - } - })?; + if config.shortcut != shortcuts::DEFAULT_SHORTCUT { + shortcuts::update_default(window, shortcuts::DEFAULT_SHORTCUT, &config.shortcut)?; + } Ok(()) } diff --git a/src/shortcuts.rs b/src/shortcuts.rs new file mode 100644 index 0000000..c8b53b0 --- /dev/null +++ b/src/shortcuts.rs @@ -0,0 +1,62 @@ +use tauri::{AppHandle, GlobalShortcutManager, Manager, Window}; + +use crate::{config, window}; + +pub const DEFAULT_SHORTCUT: &str = "Cmd+Alt+Shift+C"; + +pub fn update_default( + window: Window, + old_shortcut: &str, + new_shortcut: &str, +) -> Result<(), tauri::Error> { + let app = window.app_handle(); + let mut shortcuts = app.global_shortcut_manager(); + + shortcuts.unregister(old_shortcut)?; + shortcuts.register(new_shortcut, move || { + if window.is_visible().unwrap() { + window::hide(&window).unwrap(); + } else { + window::show(&window).unwrap(); + } + })?; + + Ok(()) +} + +pub fn register_settings(app: &AppHandle) -> Result<(), anyhow::Error> { + let mut shortcuts = app.global_shortcut_manager(); + + shortcuts.register("Cmd+,", move || { + config::edit().unwrap(); + })?; + + Ok(()) +} + +pub fn unregister_settings(app: &AppHandle) -> Result<(), anyhow::Error> { + let mut shortcuts = app.global_shortcut_manager(); + + shortcuts.unregister("Cmd+,")?; + + Ok(()) +} + +pub fn register_escape(window: Window) -> Result<(), tauri::Error> { + let app = window.app_handle(); + let mut shortcuts = app.global_shortcut_manager(); + + shortcuts.register("Escape", move || { + window::hide(&window).unwrap(); + })?; + + Ok(()) +} + +pub fn unregister_escape(app: &AppHandle) -> Result<(), tauri::Error> { + let mut shortcuts = app.global_shortcut_manager(); + + shortcuts.unregister("Escape")?; + + Ok(()) +} diff --git a/src/tray.rs b/src/tray.rs index b2cb156..4dc3cd1 100644 --- a/src/tray.rs +++ b/src/tray.rs @@ -12,29 +12,6 @@ pub enum TrayMenu { DevTools, } -impl From for String { - fn from(value: TrayMenu) -> Self { - match value { - TrayMenu::Quit => "quit".to_string(), - TrayMenu::Settings => "settings".to_string(), - #[cfg(debug_assertions)] - TrayMenu::DevTools => "devtools".to_string(), - } - } -} - -impl From for TrayMenu { - fn from(value: String) -> Self { - match value.as_str() { - "quit" => TrayMenu::Quit, - "settings" => TrayMenu::Settings, - #[cfg(debug_assertions)] - "devtools" => TrayMenu::DevTools, - _ => unreachable!(), - } - } -} - pub fn build() -> SystemTray { let tray_menu = SystemTrayMenu::new() .add_item(CustomMenuItem::new(TrayMenu::Settings, "Settings...").accelerator("Cmd+,")) @@ -58,14 +35,37 @@ pub fn build() -> SystemTray { pub fn handle(app: &AppHandle, event: SystemTrayEvent) { match event { SystemTrayEvent::LeftClick { .. } => { - window::toggle(&app.get_window("main").unwrap()).unwrap() + window::show(&app.get_window(window::NAME).unwrap()).unwrap() }, SystemTrayEvent::MenuItemClick { id, .. } => match id.into() { TrayMenu::Quit => std::process::exit(0), TrayMenu::Settings => config::edit().unwrap(), #[cfg(debug_assertions)] - TrayMenu::DevTools => app.get_window("main").unwrap().open_devtools(), + TrayMenu::DevTools => app.get_window(window::NAME).unwrap().open_devtools(), }, _ => {}, }; } + +impl From for String { + fn from(value: TrayMenu) -> Self { + match value { + TrayMenu::Quit => "quit".to_string(), + TrayMenu::Settings => "settings".to_string(), + #[cfg(debug_assertions)] + TrayMenu::DevTools => "devtools".to_string(), + } + } +} + +impl From for TrayMenu { + fn from(value: String) -> Self { + match value.as_str() { + "quit" => TrayMenu::Quit, + "settings" => TrayMenu::Settings, + #[cfg(debug_assertions)] + "devtools" => TrayMenu::DevTools, + _ => unreachable!(), + } + } +} diff --git a/src/window.rs b/src/window.rs index 2c6ec4f..65c426d 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,26 +1,52 @@ use anyhow::anyhow; use git2::Repository; use tauri::{AppHandle, GlobalWindowEvent, Manager, RunEvent, Window, WindowEvent}; +use tauri_plugin_spotlight::ManagerExt; use window_vibrancy::{apply_vibrancy, NSVisualEffectMaterial, NSVisualEffectState}; -use crate::{config::GetConfig, utils}; +use crate::{config::GetConfig, shortcuts, utils}; + +pub const NAME: &str = "main"; pub fn handler(event: GlobalWindowEvent) { match event.event() { + WindowEvent::Focused(true) => { + on_window(event.window().clone()); + shortcuts::register_escape(event.window().clone()).unwrap(); + shortcuts::register_settings(&event.window().app_handle()).unwrap(); + }, WindowEvent::Focused(is_focused) => { if !is_focused { #[cfg(not(debug_assertions))] toggle(event.window()).unwrap(); + + event.window().emit("reset", true).unwrap(); + shortcuts::unregister_escape(&event.window().app_handle()).unwrap(); + shortcuts::unregister_settings(&event.window().app_handle()).unwrap(); } }, WindowEvent::CloseRequested { api, .. } => { - toggle(event.window()).unwrap(); api.prevent_close(); + hide(event.window()).unwrap(); }, _ => {}, } } +pub fn show(window: &Window) -> Result<(), tauri_plugin_spotlight::Error> { + let app = window.app_handle(); + let spotlight = app.spotlight(); + + spotlight.show(window) +} + +pub fn hide(window: &Window) -> Result<(), tauri_plugin_spotlight::Error> { + let app = window.app_handle(); + let spotlight = app.spotlight(); + + spotlight.hide(window) +} + pub fn make_transparent(window: &Window) -> Result<(), window_vibrancy::Error> { apply_vibrancy( window, @@ -30,52 +56,42 @@ pub fn make_transparent(window: &Window) -> Result<(), window_vibrancy::Error> { ) } -pub fn toggle(window: &Window) -> anyhow::Result<()> { - if window.is_visible()? { - window.emit("reset", true)?; - window.hide()?; - } else { - let window_handle = window.clone(); - tauri::async_runtime::spawn(async move { - let app = window_handle.app_handle(); - let config = app.state::(); - let config = config.read().unwrap(); - let Some(repo_path) = utils::find_latest_repo(&config.repo_paths)? else { - window_handle.emit("current_dir", Option::::None)?; - - return Err(anyhow!("No repo found")); - }; - - window_handle.emit("current_dir", &repo_path)?; - - let repo = Repository::open(&repo_path)?; - - window_handle.emit("current_branch", utils::get_branch_name(&repo))?; - - window_handle.emit( - "current_repo", - utils::get_repo_name(&repo).or_else(|| { - repo_path - .file_name() - .and_then(|s| s.to_str()) - .map(ToString::to_string) - }), - )?; - - window_handle.emit("current_diff", utils::get_diff(&repo))?; - - Ok(()) - }); - - window.show()?; - window.set_focus()?; - } +pub fn on_window(window: Window) { + tauri::async_runtime::spawn(async move { + let app = window.app_handle(); + let config = app.state::(); + let config = config.read().unwrap(); + let Some(repo_path) = utils::find_latest_repo(&config.repo_paths)? else { + window.emit("current_dir", Option::::None)?; + + return Err(anyhow!("No repo found")); + }; + + window.emit("current_dir", &repo_path)?; + + let repo = Repository::open(&repo_path)?; + + window.emit("current_branch", utils::get_branch_name(&repo))?; + + window.emit( + "current_repo", + utils::get_repo_name(&repo).or_else(|| { + repo_path + .file_name() + .and_then(|s| s.to_str()) + .map(ToString::to_string) + }), + )?; + + window.emit("current_diff", utils::get_diff(&repo))?; - Ok(()) + Ok(()) + }); } -pub fn prevent_exit(_: &AppHandle, event: RunEvent) { +pub fn prevent_exit(app: &AppHandle, event: RunEvent) { if let tauri::RunEvent::ExitRequested { api, .. } = event { api.prevent_exit(); + hide(&app.get_window(NAME).unwrap()).unwrap(); } }