From 3a4dcf1a5243ad5e16f51e43aa35c661d1149920 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Sat, 25 Nov 2023 09:14:54 +0200 Subject: [PATCH] refactor: cleanup path handling - Avoids unnecessary string allocation when tracing paths - Replaces `mut path & path.push()` with `path.join()` - Avoids unncessary cloning of paths where applicable - Use `dunce` crate to remove `UNC` prefix - Improve performance of resolving `~` by avoiding unnecessary string allocations - Resolve `~`, `$Env:USERPROFILE` and `$HOME` consistenly between different code paths - Use `PathBuf` instead of `String` for paths in CLI args I may have missed a couple of places but I think I covered 90% of path handling in the codebase --- Cargo.lock | 9 + Cargo.toml | 3 + komorebi-core/Cargo.toml | 4 +- komorebi-core/src/custom_layout.rs | 24 +- komorebi-core/src/lib.rs | 35 +++ komorebi/Cargo.toml | 4 +- komorebi/src/main.rs | 36 +-- komorebi/src/monitor.rs | 5 +- komorebi/src/process_command.rs | 56 ++-- komorebi/src/process_movement.rs | 2 +- komorebi/src/static_config.rs | 13 +- komorebi/src/window.rs | 8 +- komorebi/src/window_manager.rs | 137 ++++---- komorebi/src/windows_api.rs | 2 +- komorebi/src/winevent_listener.rs | 2 +- komorebi/src/workspace.rs | 4 +- komorebic/Cargo.toml | 5 +- komorebic/src/main.rs | 486 ++++++++--------------------- 18 files changed, 305 insertions(+), 530 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0056fc156..1379856fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -328,6 +328,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + [[package]] name = "dyn-clone" version = "1.0.16" @@ -824,6 +830,8 @@ version = "0.1.19" dependencies = [ "clap", "color-eyre", + "dirs", + "dunce", "schemars", "serde", "serde_json", @@ -840,6 +848,7 @@ dependencies = [ "color-eyre", "derive-ahk", "dirs", + "dunce", "fs-tail", "heck", "komorebi-core", diff --git a/Cargo.toml b/Cargo.toml index 02f305140..e9558ae0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,9 @@ members = [ [workspace.dependencies] windows-interface = { version = "0.52" } windows-implement = { version = "0.52" } +dunce = "1" +dirs = "5" +color-eyre = "0.6" [workspace.dependencies.windows] version = "0.52" diff --git a/komorebi-core/Cargo.toml b/komorebi-core/Cargo.toml index 5d69254d0..6d21223e5 100644 --- a/komorebi-core/Cargo.toml +++ b/komorebi-core/Cargo.toml @@ -7,10 +7,12 @@ edition = "2021" [dependencies] clap = { version = "4", features = ["derive"] } -color-eyre = "0.6" serde = { version = "1", features = ["derive"] } serde_json = "1" serde_yaml = "0.9" strum = { version = "0.25", features = ["derive"] } schemars = "0.8" +color-eyre = { workspace = true } windows = { workspace = true } +dunce = { workspace = true } +dirs = { workspace = true } diff --git a/komorebi-core/src/custom_layout.rs b/komorebi-core/src/custom_layout.rs index 26b5991da..2edae1772 100644 --- a/komorebi-core/src/custom_layout.rs +++ b/komorebi-core/src/custom_layout.rs @@ -3,9 +3,10 @@ use std::fs::File; use std::io::BufReader; use std::ops::Deref; use std::ops::DerefMut; -use std::path::PathBuf; +use std::path::Path; use color_eyre::eyre::anyhow; +use color_eyre::eyre::bail; use color_eyre::Result; use schemars::JsonSchema; use serde::Deserialize; @@ -31,23 +32,20 @@ impl DerefMut for CustomLayout { } impl CustomLayout { - pub fn from_path_buf(path: PathBuf) -> Result { - let invalid_filetype = anyhow!("custom layouts must be json or yaml files"); + pub fn from_path>(path: P) -> Result { + let path = path.as_ref(); let layout: Self = match path.extension() { - Some(extension) => { - if extension == "yaml" || extension == "yml" { - serde_yaml::from_reader(BufReader::new(File::open(path)?))? - } else if extension == "json" { - serde_json::from_reader(BufReader::new(File::open(path)?))? - } else { - return Err(invalid_filetype); - } + Some(extension) if extension == "yaml" || extension == "yml" => { + serde_json::from_reader(BufReader::new(File::open(path)?))? + } + Some(extension) if extension == "json" => { + serde_json::from_reader(BufReader::new(File::open(path)?))? } - None => return Err(invalid_filetype), + _ => return Err(anyhow!("custom layouts must be json or yaml files")), }; if !layout.is_valid() { - return Err(anyhow!("the layout file provided was invalid")); + bail!("the layout file provided was invalid"); } Ok(layout) diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 9f03c921d..3a95612d5 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -1,10 +1,12 @@ #![warn(clippy::all, clippy::nursery, clippy::pedantic)] #![allow(clippy::missing_errors_doc, clippy::use_self)] +use std::path::Path; use std::path::PathBuf; use std::str::FromStr; use clap::ValueEnum; +use color_eyre::eyre::anyhow; use color_eyre::Result; use schemars::JsonSchema; use serde::Deserialize; @@ -298,3 +300,36 @@ impl Sizing { } } } + +pub fn resolve_home_path>(path: P) -> Result { + let mut resolved_path = PathBuf::new(); + let mut resolved = false; + for c in path.as_ref().components() { + match c { + std::path::Component::Normal(c) + if (c == "~" || c == "$Env:USERPROFILE" || c == "$HOME") && !resolved => + { + let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?; + + resolved_path.extend(home.components()); + resolved = true; + } + + _ => resolved_path.push(c), + } + } + + let parent = resolved_path + .parent() + .ok_or_else(|| anyhow!("cannot parse parent directory"))?; + + Ok(if parent.is_dir() { + let file = resolved_path + .components() + .last() + .ok_or_else(|| anyhow!("cannot parse filename"))?; + dunce::canonicalize(parent)?.join(file) + } else { + resolved_path + }) +} diff --git a/komorebi/Cargo.toml b/komorebi/Cargo.toml index d1bcd21b0..3a7f71735 100644 --- a/komorebi/Cargo.toml +++ b/komorebi/Cargo.toml @@ -15,11 +15,9 @@ komorebi-core = { path = "../komorebi-core" } bitflags = "2" clap = { version = "4", features = ["derive"] } -color-eyre = "0.6" crossbeam-channel = "0.5" crossbeam-utils = "0.8" ctrlc = "3" -dirs = "5" getset = "0.1" hotwatch = "0.4" lazy_static = "1" @@ -45,6 +43,8 @@ winreg = "0.52" windows-interface = { workspace = true } windows-implement = { workspace = true } windows = { workspace = true } +color-eyre = { workspace = true } +dirs = { workspace = true } [features] deadlock_detection = [] diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index 5f402c694..f328792ab 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -22,7 +22,6 @@ use std::sync::Arc; use std::time::Duration; use clap::Parser; -use color_eyre::eyre::anyhow; use color_eyre::Result; use crossbeam_channel::Receiver; use crossbeam_channel::Sender; @@ -252,7 +251,7 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> { std::env::set_var("RUST_LOG", "info"); } - let appender = tracing_appender::rolling::never(DATA_DIR.clone(), "komorebi.log"); + let appender = tracing_appender::rolling::never(&*DATA_DIR, "komorebi.log"); let color_appender = tracing_appender::rolling::never(std::env::temp_dir(), "komorebi.log"); let (non_blocking, guard) = tracing_appender::non_blocking(appender); let (color_non_blocking, color_guard) = tracing_appender::non_blocking(color_appender); @@ -305,13 +304,8 @@ fn setup() -> Result<(WorkerGuard, WorkerGuard)> { } pub fn load_configuration() -> Result<()> { - let home = HOME_DIR.clone(); - - let mut config_pwsh = home.clone(); - config_pwsh.push("komorebi.ps1"); - - let mut config_ahk = home; - config_ahk.push("komorebi.ahk"); + let config_pwsh = HOME_DIR.join("komorebi.ps1"); + let config_ahk = HOME_DIR.join("komorebi.ahk"); if config_pwsh.exists() { let powershell_exe = if which("pwsh.exe").is_ok() { @@ -320,25 +314,13 @@ pub fn load_configuration() -> Result<()> { "powershell.exe" }; - tracing::info!( - "loading configuration file: {}", - config_pwsh - .as_os_str() - .to_str() - .ok_or_else(|| anyhow!("cannot convert path to string"))? - ); + tracing::info!("loading configuration file: {}", config_pwsh.display()); Command::new(powershell_exe) .arg(config_pwsh.as_os_str()) .output()?; } else if config_ahk.exists() && which(&*AHK_EXE).is_ok() { - tracing::info!( - "loading configuration file: {}", - config_ahk - .as_os_str() - .to_str() - .ok_or_else(|| anyhow!("cannot convert path to string"))? - ); + tracing::info!("loading configuration file: {}", config_ahk.display()); Command::new(&*AHK_EXE) .arg(config_ahk.as_os_str()) @@ -410,7 +392,7 @@ pub fn notify_subscribers(notification: &str) -> Result<()> { let mut subscriptions = SUBSCRIPTION_PIPES.lock(); for (subscriber, pipe) in &mut *subscriptions { match writeln!(pipe, "{notification}") { - Ok(_) => { + Ok(()) => { tracing::debug!("pushed notification to subscriber: {}", subscriber); } Err(error) => { @@ -528,7 +510,7 @@ fn main() -> Result<()> { let wm = if let Some(config) = &opts.config { tracing::info!( "creating window manager from static configuration file: {}", - config.as_os_str().to_str().unwrap() + config.display() ); Arc::new(Mutex::new(StaticConfig::preload( @@ -557,9 +539,7 @@ fn main() -> Result<()> { } if opts.config.is_none() { - std::thread::spawn(|| { - load_configuration().expect("could not load configuration"); - }); + std::thread::spawn(|| load_configuration().expect("could not load configuration")); if opts.await_configuration { let backoff = Backoff::new(); diff --git a/komorebi/src/monitor.rs b/komorebi/src/monitor.rs index a649013e8..b29d6914d 100644 --- a/komorebi/src/monitor.rs +++ b/komorebi/src/monitor.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::collections::VecDeque; use color_eyre::eyre::anyhow; +use color_eyre::eyre::bail; use color_eyre::Result; use getset::CopyGetters; use getset::Getters; @@ -120,9 +121,7 @@ impl Monitor { .ok_or_else(|| anyhow!("there is no workspace"))?; if workspace.maximized_window().is_some() { - return Err(anyhow!( - "cannot move native maximized window to another monitor or workspace" - )); + bail!("cannot move native maximized window to another monitor or workspace"); } let container = workspace diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 1cec23140..1013f9be3 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -472,10 +472,10 @@ impl WindowManager { SocketMessage::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?, SocketMessage::CycleLayout(direction) => self.cycle_layout(direction)?, SocketMessage::ChangeLayoutCustom(ref path) => { - self.change_workspace_custom_layout(path.clone())?; + self.change_workspace_custom_layout(path)?; } SocketMessage::WorkspaceLayoutCustom(monitor_idx, workspace_idx, ref path) => { - self.set_workspace_layout_custom(monitor_idx, workspace_idx, path.clone())?; + self.set_workspace_layout_custom(monitor_idx, workspace_idx, path)?; } SocketMessage::WorkspaceTiling(monitor_idx, workspace_idx, tile) => { self.set_workspace_tiling(monitor_idx, workspace_idx, tile)?; @@ -506,7 +506,7 @@ impl WindowManager { monitor_idx, workspace_idx, at_container_count, - path.clone(), + path, )?; } SocketMessage::ClearWorkspaceLayoutRules(monitor_idx, workspace_idx) => { @@ -516,7 +516,7 @@ impl WindowManager { if let Some((monitor_idx, workspace_idx)) = self.monitor_workspace_index_by_name(workspace) { - self.set_workspace_layout_custom(monitor_idx, workspace_idx, path.clone())?; + self.set_workspace_layout_custom(monitor_idx, workspace_idx, path)?; } } SocketMessage::NamedWorkspaceTiling(ref workspace, tile) => { @@ -557,7 +557,7 @@ impl WindowManager { monitor_idx, workspace_idx, at_container_count, - path.clone(), + path, )?; } } @@ -691,10 +691,7 @@ impl WindowManager { Err(error) => error.to_string(), }; - let mut socket = DATA_DIR.clone(); - socket.push("komorebic.sock"); - let socket = socket.as_path(); - + let socket = DATA_DIR.join("komorebic.sock"); let mut stream = UnixStream::connect(socket)?; stream.write_all(state.as_bytes())?; } @@ -714,10 +711,7 @@ impl WindowManager { } .to_string(); - let mut socket = DATA_DIR.clone(); - socket.push("komorebic.sock"); - let socket = socket.as_path(); - + let socket = DATA_DIR.join("komorebic.sock"); let mut stream = UnixStream::connect(socket)?; stream.write_all(response.as_bytes())?; } @@ -1034,8 +1028,7 @@ impl WindowManager { let workspace = self.focused_workspace()?; let resize = workspace.resize_dimensions(); - let mut quicksave_json = std::env::temp_dir(); - quicksave_json.push("komorebi.quicksave.json"); + let quicksave_json = std::env::temp_dir().join("komorebi.quicksave.json"); let file = OpenOptions::new() .write(true) @@ -1048,15 +1041,10 @@ impl WindowManager { SocketMessage::QuickLoad => { let workspace = self.focused_workspace_mut()?; - let mut quicksave_json = std::env::temp_dir(); - quicksave_json.push("komorebi.quicksave.json"); + let quicksave_json = std::env::temp_dir().join("komorebi.quicksave.json"); - let file = File::open(&quicksave_json).map_err(|_| { - anyhow!( - "no quicksave found at {}", - quicksave_json.display().to_string() - ) - })?; + let file = File::open(&quicksave_json) + .map_err(|_| anyhow!("no quicksave found at {}", quicksave_json.display()))?; let resize: Vec> = serde_json::from_reader(file)?; @@ -1071,15 +1059,15 @@ impl WindowManager { .write(true) .truncate(true) .create(true) - .open(path.clone())?; + .open(path)?; serde_json::to_writer_pretty(&file, &resize)?; } SocketMessage::Load(ref path) => { let workspace = self.focused_workspace_mut()?; - let file = File::open(path) - .map_err(|_| anyhow!("no file found at {}", path.display().to_string()))?; + let file = + File::open(path).map_err(|_| anyhow!("no file found at {}", path.display()))?; let resize: Vec> = serde_json::from_reader(file)?; @@ -1191,9 +1179,7 @@ impl WindowManager { SocketMessage::NotificationSchema => { let notification = schema_for!(Notification); let schema = serde_json::to_string_pretty(¬ification)?; - let mut socket = DATA_DIR.clone(); - socket.push("komorebic.sock"); - let socket = socket.as_path(); + let socket = DATA_DIR.join("komorebic.sock"); let mut stream = UnixStream::connect(socket)?; stream.write_all(schema.as_bytes())?; @@ -1201,9 +1187,7 @@ impl WindowManager { SocketMessage::SocketSchema => { let socket_message = schema_for!(SocketMessage); let schema = serde_json::to_string_pretty(&socket_message)?; - let mut socket = DATA_DIR.clone(); - socket.push("komorebic.sock"); - let socket = socket.as_path(); + let socket = DATA_DIR.join("komorebic.sock"); let mut stream = UnixStream::connect(socket)?; stream.write_all(schema.as_bytes())?; @@ -1211,18 +1195,14 @@ impl WindowManager { SocketMessage::StaticConfigSchema => { let socket_message = schema_for!(StaticConfig); let schema = serde_json::to_string_pretty(&socket_message)?; - let mut socket = DATA_DIR.clone(); - socket.push("komorebic.sock"); - let socket = socket.as_path(); + let socket = DATA_DIR.join("komorebic.sock"); let mut stream = UnixStream::connect(socket)?; stream.write_all(schema.as_bytes())?; } SocketMessage::GenerateStaticConfig => { let config = serde_json::to_string_pretty(&StaticConfig::from(&*self))?; - let mut socket = DATA_DIR.clone(); - socket.push("komorebic.sock"); - let socket = socket.as_path(); + let socket = DATA_DIR.join("komorebic.sock"); let mut stream = UnixStream::connect(socket)?; stream.write_all(config.as_bytes())?; diff --git a/komorebi/src/process_movement.rs b/komorebi/src/process_movement.rs index c1082ef11..f5f8d75de 100644 --- a/komorebi/src/process_movement.rs +++ b/komorebi/src/process_movement.rs @@ -31,7 +31,7 @@ pub fn listen_for_movements(wm: Arc>) { Event::MouseMoveRelative { .. } => { if !ignore_movement { match wm.lock().raise_window_at_cursor_pos() { - Ok(_) => {} + Ok(()) => {} Err(error) => tracing::error!("{}", error), } } diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index bc5af8d58..c37a2f318 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -29,13 +29,13 @@ use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS; use crate::WORKSPACE_RULES; use color_eyre::Result; use crossbeam_channel::Receiver; -use dirs::home_dir; use hotwatch::notify::DebouncedEvent; use hotwatch::Hotwatch; use komorebi_core::config_generation::ApplicationConfigurationGenerator; use komorebi_core::config_generation::ApplicationOptions; use komorebi_core::config_generation::IdWithIdentifier; use komorebi_core::config_generation::MatchingStrategy; +use komorebi_core::resolve_home_path; use komorebi_core::ApplicationIdentifier; use komorebi_core::DefaultLayout; use komorebi_core::FocusFollowsMouseImplementation; @@ -616,13 +616,8 @@ impl StaticConfig { } if let Some(path) = &self.app_specific_configuration_path { - let stringified = path.to_string_lossy(); - let stringified = stringified.replace( - "$Env:USERPROFILE", - &home_dir().expect("no home dir").to_string_lossy(), - ); - - let content = std::fs::read_to_string(stringified)?; + let path = resolve_home_path(path)?; + let content = std::fs::read_to_string(path)?; let asc = ApplicationConfigurationGenerator::load(&content)?; for mut entry in asc { @@ -762,7 +757,7 @@ impl StaticConfig { let socket = DATA_DIR.join("komorebi.sock"); match std::fs::remove_file(&socket) { - Ok(_) => {} + Ok(()) => {} Err(error) => match error.kind() { // Doing this because ::exists() doesn't work reliably on Windows via IntelliJ ErrorKind::NotFound => {} diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 601ff6e86..b991baac5 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -241,7 +241,7 @@ impl Window { // Raise Window to foreground match WindowsApi::set_foreground_window(self.hwnd()) { - Ok(_) => {} + Ok(()) => {} Err(error) => { tracing::error!( "could not set as foreground window, but continuing execution of raise(): {}", @@ -252,7 +252,7 @@ impl Window { // This isn't really needed when the above command works as expected via AHK match WindowsApi::set_focus(self.hwnd()) { - Ok(_) => {} + Ok(()) => {} Err(error) => { tracing::error!( "could not set focus, but continuing execution of raise(): {}", @@ -302,7 +302,7 @@ impl Window { } match WindowsApi::set_foreground_window(self.hwnd()) { - Ok(_) => { + Ok(()) => { foregrounded = true; } Err(error) => { @@ -334,7 +334,7 @@ impl Window { // This isn't really needed when the above command works as expected via AHK match WindowsApi::set_focus(self.hwnd()) { - Ok(_) => {} + Ok(()) => {} Err(error) => { tracing::error!( "could not set focus, but continuing execution of focus(): {}", diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 25cac399c..49130f38e 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -3,11 +3,13 @@ use std::collections::HashSet; use std::collections::VecDeque; use std::io::ErrorKind; use std::num::NonZeroUsize; +use std::path::Path; use std::path::PathBuf; use std::sync::atomic::Ordering; use std::sync::Arc; use color_eyre::eyre::anyhow; +use color_eyre::eyre::bail; use color_eyre::Result; use crossbeam_channel::Receiver; use hotwatch::notify::DebouncedEvent; @@ -166,7 +168,7 @@ impl WindowManager { let socket = DATA_DIR.join("komorebi.sock"); match std::fs::remove_file(&socket) { - Ok(_) => {} + Ok(()) => {} Err(error) => match error.kind() { // Doing this because ::exists() doesn't work reliably on Windows via IntelliJ ErrorKind::NotFound => {} @@ -247,13 +249,8 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn watch_configuration(&mut self, enable: bool) -> Result<()> { - let home = HOME_DIR.clone(); - - let mut config_pwsh = home.clone(); - config_pwsh.push("komorebi.ps1"); - - let mut config_ahk = home; - config_ahk.push("komorebi.ahk"); + let config_pwsh = HOME_DIR.join("komorebi.ps1"); + let config_ahk = HOME_DIR.join("komorebi.ahk"); if config_pwsh.exists() { self.configure_watcher(enable, config_pwsh)?; @@ -265,50 +262,39 @@ impl WindowManager { } fn configure_watcher(&mut self, enable: bool, config: PathBuf) -> Result<()> { - if config.exists() { - if enable { - tracing::info!( - "watching configuration for changes: {}", - config - .as_os_str() - .to_str() - .ok_or_else(|| anyhow!("cannot convert path to string"))? - ); - // Always make absolutely sure that there isn't an already existing watch, because - // hotwatch allows multiple watches to be registered for the same path - match self.hotwatch.unwatch(config.clone()) { - Ok(_) => {} - Err(error) => match error { - hotwatch::Error::Notify(error) => match error { - hotwatch::notify::Error::WatchNotFound => {} - error => return Err(error.into()), - }, - error @ hotwatch::Error::Io(_) => return Err(error.into()), + if enable { + tracing::info!("watching configuration for changes: {}", config.display()); + // Always make absolutely sure that there isn't an already existing watch, because + // hotwatch allows multiple watches to be registered for the same path + match self.hotwatch.unwatch(&config) { + Ok(()) => {} + Err(error) => match error { + hotwatch::Error::Notify(error) => match error { + hotwatch::notify::Error::WatchNotFound => {} + error => return Err(error.into()), }, + error @ hotwatch::Error::Io(_) => return Err(error.into()), + }, + } + + self.hotwatch.watch(config, |event| match event { + // Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends + // a NoticeRemove, presumably because of the use of swap files? + DebouncedEvent::NoticeWrite(_) | DebouncedEvent::NoticeRemove(_) => { + std::thread::spawn(|| { + load_configuration().expect("could not load configuration"); + }); } + _ => {} + })?; + } else { + tracing::info!( + "no longer watching configuration for changes: {}", + config.display() + ); - self.hotwatch.watch(config, |event| match event { - // Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends - // a NoticeRemove, presumably because of the use of swap files? - DebouncedEvent::NoticeWrite(_) | DebouncedEvent::NoticeRemove(_) => { - std::thread::spawn(|| { - load_configuration().expect("could not load configuration"); - }); - } - _ => {} - })?; - } else { - tracing::info!( - "no longer watching configuration for changes: {}", - config - .as_os_str() - .to_str() - .ok_or_else(|| anyhow!("cannot convert path to string"))? - ); - - self.hotwatch.unwatch(config)?; - }; - } + self.hotwatch.unwatch(config)?; + }; Ok(()) } @@ -868,7 +854,7 @@ impl WindowManager { // attach to the thread of the desktop window always seems to result in "Access is // denied (os error 5)" match WindowsApi::set_foreground_window(desktop_window.hwnd()) { - Ok(_) => {} + Ok(()) => {} Err(error) => { tracing::warn!("{} {}:{}", error, file!(), line!()); } @@ -1002,9 +988,7 @@ impl WindowManager { let workspace = self.focused_workspace()?; let focused_hwnd = WindowsApi::foreground_window()?; if !workspace.contains_managed_window(focused_hwnd) { - return Err(anyhow!( - "ignoring commands while active window is not managed by komorebi" - )); + bail!("ignoring commands while active window is not managed by komorebi"); } } @@ -1120,9 +1104,7 @@ impl WindowManager { .ok_or_else(|| anyhow!("there is no workspace"))?; if workspace.maximized_window().is_some() { - return Err(anyhow!( - "cannot move native maximized window to another monitor or workspace" - )); + bail!("cannot move native maximized window to another monitor or workspace"); } let container = workspace @@ -1232,9 +1214,7 @@ impl WindowManager { // removing this messes up the monitor / container / window index somewhere // and results in the wrong window getting moved across the monitor boundary if workspace.is_focused_window_monocle_or_maximized()? { - return Err(anyhow!( - "ignoring command while active window is in monocle mode or maximized" - )); + bail!("ignoring command while active window is in monocle mode or maximized"); } tracing::info!("moving container"); @@ -1396,9 +1376,7 @@ impl WindowManager { let workspace = self.focused_workspace_mut()?; if workspace.is_focused_window_monocle_or_maximized()? { - return Err(anyhow!( - "ignoring command while active window is in monocle mode or maximized" - )); + bail!("ignoring command while active window is in monocle mode or maximized"); } tracing::info!("moving container"); @@ -1425,7 +1403,7 @@ impl WindowManager { .ok_or_else(|| anyhow!("there must be at least one window in a container"))?; if len.get() == 1 { - return Err(anyhow!("there is only one window in this container")); + bail!("there is only one window in this container"); } let current_idx = container.focused_window_idx(); @@ -1510,7 +1488,7 @@ impl WindowManager { tracing::info!("removing window"); if self.focused_container()?.windows().len() == 1 { - return Err(anyhow!("a container must have at least one window")); + bail!("a container must have at least one window"); } let workspace = self.focused_workspace_mut()?; @@ -1729,10 +1707,13 @@ impl WindowManager { } #[tracing::instrument(skip(self))] - pub fn change_workspace_custom_layout(&mut self, path: PathBuf) -> Result<()> { + pub fn change_workspace_custom_layout

(&mut self, path: P) -> Result<()> + where + P: AsRef + std::fmt::Debug, + { tracing::info!("changing layout"); - let layout = CustomLayout::from_path_buf(path)?; + let layout = CustomLayout::from_path(path)?; let workspace = self.focused_workspace_mut()?; match workspace.layout() { @@ -1854,13 +1835,16 @@ impl WindowManager { } #[tracing::instrument(skip(self))] - pub fn add_workspace_layout_custom_rule( + pub fn add_workspace_layout_custom_rule

( &mut self, monitor_idx: usize, workspace_idx: usize, at_container_count: usize, - path: PathBuf, - ) -> Result<()> { + path: P, + ) -> Result<()> + where + P: AsRef + std::fmt::Debug, + { tracing::info!("setting workspace layout"); let invisible_borders = self.invisible_borders; @@ -1885,7 +1869,7 @@ impl WindowManager { .get_mut(workspace_idx) .ok_or_else(|| anyhow!("there is no monitor"))?; - let layout = CustomLayout::from_path_buf(path)?; + let layout = CustomLayout::from_path(path)?; let rules: &mut Vec<(usize, Layout)> = workspace.layout_rules_mut(); rules.retain(|pair| pair.0 != at_container_count); @@ -1986,14 +1970,17 @@ impl WindowManager { } #[tracing::instrument(skip(self))] - pub fn set_workspace_layout_custom( + pub fn set_workspace_layout_custom

( &mut self, monitor_idx: usize, workspace_idx: usize, - path: PathBuf, - ) -> Result<()> { + path: P, + ) -> Result<()> + where + P: AsRef + std::fmt::Debug, + { tracing::info!("setting workspace layout"); - let layout = CustomLayout::from_path_buf(path)?; + let layout = CustomLayout::from_path(path)?; let invisible_borders = self.invisible_borders; let offset = self.work_area_offset; let focused_monitor_idx = self.focused_monitor_idx(); @@ -2164,7 +2151,7 @@ impl WindowManager { if self.monitors().get(idx).is_some() { self.monitors.focus(idx); } else { - return Err(anyhow!("this is not a valid monitor index")); + bail!("this is not a valid monitor index"); } Ok(()) diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index e2b129c0e..4ac889d68 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -368,7 +368,7 @@ impl WindowsApi { pub fn close_window(hwnd: HWND) -> Result<()> { match Self::post_message(hwnd, WM_CLOSE, WPARAM(0), LPARAM(0)) { - Ok(_) => Ok(()), + Ok(()) => Ok(()), Err(_) => Err(anyhow!("could not close window")), } } diff --git a/komorebi/src/winevent_listener.rs b/komorebi/src/winevent_listener.rs index 25c89b600..896dcf1f6 100644 --- a/komorebi/src/winevent_listener.rs +++ b/komorebi/src/winevent_listener.rs @@ -61,7 +61,7 @@ impl WinEventListener { MessageLoop::start(10, |_msg| { if let Ok(event) = WINEVENT_CALLBACK_CHANNEL.lock().1.try_recv() { match outgoing.send(event) { - Ok(_) => {} + Ok(()) => {} Err(error) => { tracing::error!("{}", error); } diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index 6c4e68d46..eddfd5bc0 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -108,7 +108,7 @@ impl Workspace { } if let Some(pathbuf) = &config.custom_layout { - let layout = CustomLayout::from_path_buf(pathbuf.clone())?; + let layout = CustomLayout::from_path(pathbuf)?; self.layout = Layout::Custom(layout); self.tile = true; } @@ -129,7 +129,7 @@ impl Workspace { if let Some(layout_rules) = &config.custom_layout_rules { let rules = self.layout_rules_mut(); for (count, pathbuf) in layout_rules { - let rule = CustomLayout::from_path_buf(pathbuf.clone())?; + let rule = CustomLayout::from_path(pathbuf)?; rules.push((*count, Layout::Custom(rule))); } } diff --git a/komorebic/Cargo.toml b/komorebic/Cargo.toml index ea0342760..369ef26ad 100644 --- a/komorebic/Cargo.toml +++ b/komorebic/Cargo.toml @@ -15,8 +15,6 @@ derive-ahk = { path = "../derive-ahk" } komorebi-core = { path = "../komorebi-core" } clap = { version = "4", features = ["derive", "wrap_help"] } -color-eyre = "0.6" -dirs = "5" fs-tail = "0.1" heck = "0.4" lazy_static = "1" @@ -30,3 +28,6 @@ sysinfo = "0.29" uds_windows = "1" which = "5" windows = { workspace = true } +color-eyre = { workspace = true } +dirs = { workspace = true } +dunce = { workspace = true } \ No newline at end of file diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index eb46aa6ae..dc1f0affe 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -16,9 +16,11 @@ use std::time::Duration; use clap::Parser; use clap::ValueEnum; use color_eyre::eyre::anyhow; +use color_eyre::eyre::bail; use color_eyre::Result; use fs_tail::TailedFile; use heck::ToKebabCase; +use komorebi_core::resolve_home_path; use lazy_static::lazy_static; use paste::paste; use sysinfo::SystemExt; @@ -259,7 +261,7 @@ pub struct WorkspaceCustomLayout { workspace: usize, /// JSON or YAML file from which the custom layout definition should be loaded - path: String, + path: PathBuf, } #[derive(Parser, AhkFunction)] @@ -268,7 +270,7 @@ pub struct NamedWorkspaceCustomLayout { workspace: String, /// JSON or YAML file from which the custom layout definition should be loaded - path: String, + path: PathBuf, } #[derive(Parser, AhkFunction)] @@ -310,7 +312,7 @@ pub struct WorkspaceCustomLayoutRule { at_container_count: usize, /// JSON or YAML file from which the custom layout definition should be loaded - path: String, + path: PathBuf, } #[derive(Parser, AhkFunction)] @@ -322,7 +324,7 @@ pub struct NamedWorkspaceCustomLayoutRule { at_container_count: usize, /// JSON or YAML file from which the custom layout definition should be loaded - path: String, + path: PathBuf, } #[derive(Parser, AhkFunction)] @@ -658,19 +660,19 @@ struct Stop { #[derive(Parser, AhkFunction)] struct SaveResize { /// File to which the resize layout dimensions should be saved - path: String, + path: PathBuf, } #[derive(Parser, AhkFunction)] struct LoadResize { /// File from which the resize layout dimensions should be loaded - path: String, + path: PathBuf, } #[derive(Parser, AhkFunction)] struct LoadCustomLayout { /// JSON or YAML file from which the custom layout definition should be loaded - path: String, + path: PathBuf, } #[derive(Parser, AhkFunction)] @@ -688,23 +690,23 @@ struct Unsubscribe { #[derive(Parser, AhkFunction)] struct AhkAppSpecificConfiguration { /// YAML file from which the application-specific configurations should be loaded - path: String, + path: PathBuf, /// Optional YAML file of overrides to apply over the first file - override_path: Option, + override_path: Option, } #[derive(Parser, AhkFunction)] struct PwshAppSpecificConfiguration { /// YAML file from which the application-specific configurations should be loaded - path: String, + path: PathBuf, /// Optional YAML file of overrides to apply over the first file - override_path: Option, + override_path: Option, } #[derive(Parser, AhkFunction)] struct FormatAppSpecificConfiguration { /// YAML file from which the application-specific configurations should be loaded - path: String, + path: PathBuf, } #[derive(Parser, AhkFunction)] @@ -717,7 +719,7 @@ struct AltFocusHack { struct EnableAutostart { /// Path to a static configuration JSON file #[clap(action, short, long)] - config: String, + config: PathBuf, /// Enable komorebi's custom focus-follows-mouse implementation #[clap(action, short, long = "ffm")] ffm: bool, @@ -1106,15 +1108,48 @@ pub fn send_message(bytes: &[u8]) -> Result<()> { Ok(stream.write_all(bytes)?) } +fn with_komorebic_socket Result<()>>(f: F) -> Result<()> { + let socket = DATA_DIR.join("komorebic.sock"); + + match std::fs::remove_file(&socket) { + Ok(()) => {} + Err(error) => match error.kind() { + // Doing this because ::exists() doesn't work reliably on Windows via IntelliJ + ErrorKind::NotFound => {} + _ => { + return Err(error.into()); + } + }, + }; + + f()?; + + let listener = UnixListener::bind(socket)?; + match listener.accept() { + Ok(incoming) => { + let stream = BufReader::new(incoming.0); + for line in stream.lines() { + println!("{}", line?); + } + + Ok(()) + } + Err(error) => { + panic!("{}", error); + } + } +} + fn startup_dir() -> Result { - let home_dir = dirs::home_dir().expect("unable to obtain user's home folder"); - let app_data = home_dir.join("AppData"); - let roaming = app_data.join("Roaming"); - let microsoft = roaming.join("Microsoft"); - let windows = microsoft.join("Windows"); - let start_menu = windows.join("Start Menu"); - let programs = start_menu.join("Programs"); - let startup = programs.join("Startup"); + let startup = dirs::home_dir() + .expect("unable to obtain user's home folder") + .join("AppData") + .join("Roaming") + .join("Microsoft") + .join("Windows") + .join("Start Menu") + .join("Programs") + .join("Startup"); if !startup.is_dir() { std::fs::create_dir_all(&startup)?; @@ -1132,33 +1167,25 @@ fn main() -> Result<()> { let version = env!("CARGO_PKG_VERSION"); let home_dir = dirs::home_dir().expect("could not find home dir"); - let mut config_dir = home_dir; - config_dir.push(".config"); - std::fs::create_dir_all(".config")?; + let config_dir = home_dir.join(".config"); + std::fs::create_dir_all(&config_dir)?; let komorebi_json = reqwest::blocking::get( format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/v{version}/komorebi.example.json") )?.text()?; - let mut komorebi_json_file_path = HOME_DIR.clone(); - komorebi_json_file_path.push("komorebi.json"); - std::fs::write(komorebi_json_file_path, komorebi_json)?; + std::fs::write(HOME_DIR.join("komorebi.json"), komorebi_json)?; let applications_yaml = reqwest::blocking::get( "https://raw.githubusercontent.com/LGUG2Z/komorebi-application-specific-configuration/master/applications.yaml" )? .text()?; - let mut komorebi_json_file_path = HOME_DIR.clone(); - komorebi_json_file_path.push("applications.yaml"); - std::fs::write(komorebi_json_file_path, applications_yaml)?; + std::fs::write(HOME_DIR.join("applications.yaml"), applications_yaml)?; let whkdrc = reqwest::blocking::get(format!( "https://raw.githubusercontent.com/LGUG2Z/komorebi/v{version}/whkdrc.sample" ))? .text()?; - let mut whkdrc_file_path = config_dir.clone(); - whkdrc_file_path.push("whkdrc"); - - std::fs::write(whkdrc_file_path, whkdrc)?; + std::fs::write(config_dir.join("whkdrc"), whkdrc)?; println!("Example ~/komorebi.json, ~/.config/whkdrc and latest ~/applications.yaml files downloaded"); println!( @@ -1166,15 +1193,20 @@ fn main() -> Result<()> { ); } SubCommand::EnableAutostart(args) => { - let mut current_exe = std::env::current_exe().expect("unable to get exec path"); - current_exe.pop(); + let mut current_exe_dir = std::env::current_exe().expect("unable to get exec path"); + current_exe_dir.pop(); - let komorebic_exe = current_exe.join("komorebic.exe"); + let komorebic_exe = current_exe_dir.join("komorebic.exe"); + let komorebic_exe = dunce::simplified(&komorebic_exe); let startup_dir = startup_dir()?; let shortcut_file = startup_dir.join("komorebi.lnk"); + let shortcut_file = dunce::simplified(&shortcut_file); - let mut arguments = format!("start --config {}", args.config); + let mut arguments = format!( + "start --config {}", + dunce::canonicalize(args.config)?.display() + ); if args.ffm { arguments.push_str(" --ffm"); @@ -1187,12 +1219,12 @@ fn main() -> Result<()> { } Command::new("powershell") - .arg("-c") - .arg("$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut($env:SHORTCUT_PATH); $Shortcut.TargetPath = $env:TARGET_PATH; $Shortcut.Arguments = $env:TARGET_ARGS; $Shortcut.Save()") - .env("SHORTCUT_PATH", shortcut_file.to_string_lossy().to_string()) - .env("TARGET_PATH", komorebic_exe.to_string_lossy().to_string()) - .env("TARGET_ARGS", arguments) - .output()?; + .arg("-c") + .arg("$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut($env:SHORTCUT_PATH); $Shortcut.TargetPath = $env:TARGET_PATH; $Shortcut.Arguments = $env:TARGET_ARGS; $Shortcut.Save()") + .env("SHORTCUT_PATH", shortcut_file.as_os_str()) + .env("TARGET_PATH", komorebic_exe.as_os_str()) + .env("TARGET_ARGS", arguments) + .output()?; } SubCommand::DisableAutostart => { let startup_dir = startup_dir()?; @@ -1203,10 +1235,9 @@ fn main() -> Result<()> { } } SubCommand::Check => { - let home = HOME_DIR.clone(); - let home_lossy_string = home.to_string_lossy(); + let home_display = HOME_DIR.display(); if HAS_CUSTOM_CONFIG_HOME.load(Ordering::SeqCst) { - println!("KOMOREBI_CONFIG_HOME detected: {home_lossy_string}\n"); + println!("KOMOREBI_CONFIG_HOME detected: {home_display}\n"); } else { println!( "No KOMOREBI_CONFIG_HOME detected, defaulting to {}\n", @@ -1216,20 +1247,15 @@ fn main() -> Result<()> { ); } - println!("Looking for configuration files in {home_lossy_string}\n"); - - let mut static_config = home.clone(); - static_config.push("komorebi.json"); - - let mut config_pwsh = home.clone(); - config_pwsh.push("komorebi.ps1"); + println!("Looking for configuration files in {home_display}\n"); - let mut config_ahk = home.clone(); - config_ahk.push("komorebi.ahk"); - - let mut config_whkd = dirs::home_dir().expect("no home dir found"); - config_whkd.push(".config"); - config_whkd.push("whkdrc"); + let static_config = HOME_DIR.join("komorebi.json"); + let config_pwsh = HOME_DIR.join("komorebi.ps1"); + let config_ahk = HOME_DIR.join("komorebi.ahk"); + let config_whkd = dirs::home_dir() + .expect("no home dir found") + .join(".config") + .join("whkdrc"); if static_config.exists() { println!("Found komorebi.json; this file can be passed to the start command with the --config flag\n"); @@ -1248,13 +1274,12 @@ fn main() -> Result<()> { } else if config_ahk.exists() { println!("Found komorebi.ahk; this file will be autoloaded by komorebi\n"); } else { - println!("No komorebi configuration found in {home_lossy_string}\n"); + println!("No komorebi configuration found in {home_display}\n"); println!("If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration\n"); } } SubCommand::AhkLibrary => { - let mut library = HOME_DIR.clone(); - library.push("komorebic.lib.ahk"); + let library = HOME_DIR.join("komorebic.lib.ahk"); let mut file = OpenOptions::new() .write(true) .create(true) @@ -1271,9 +1296,7 @@ fn main() -> Result<()> { println!( "\nAHKv1 helper library for komorebic written to {}", - library.to_str().ok_or_else(|| anyhow!( - "could not find the path to the generated ahk lib file" - ))? + library.to_string_lossy() ); println!("\nYou can convert this file to AHKv2 syntax using https://github.com/mmikeww/AHK-v2-script-converter"); @@ -1285,8 +1308,7 @@ fn main() -> Result<()> { println!("\n#Include komorebic.lib.ahk"); } SubCommand::Log => { - let mut color_log = std::env::temp_dir(); - color_log.push("komorebi.log"); + let color_log = std::env::temp_dir().join("komorebi.log"); let file = TailedFile::new(File::open(color_log)?); let locked = file.lock(); #[allow(clippy::significant_drop_in_scrutinee)] @@ -1486,7 +1508,7 @@ fn main() -> Result<()> { &SocketMessage::WorkspaceLayoutCustom( arg.monitor, arg.workspace, - resolve_windows_path(&arg.path)?, + resolve_home_path(arg.path)?, ) .as_bytes()?, )?; @@ -1495,7 +1517,7 @@ fn main() -> Result<()> { send_message( &SocketMessage::NamedWorkspaceLayoutCustom( arg.workspace, - resolve_windows_path(&arg.path)?, + resolve_home_path(arg.path)?, ) .as_bytes()?, )?; @@ -1527,7 +1549,7 @@ fn main() -> Result<()> { arg.monitor, arg.workspace, arg.at_container_count, - resolve_windows_path(&arg.path)?, + resolve_home_path(arg.path)?, ) .as_bytes()?, )?; @@ -1537,7 +1559,7 @@ fn main() -> Result<()> { &SocketMessage::NamedWorkspaceLayoutCustomRule( arg.workspace, arg.at_container_count, - resolve_windows_path(&arg.path)?, + resolve_home_path(arg.path)?, ) .as_bytes()?, )?; @@ -1573,11 +1595,11 @@ fn main() -> Result<()> { } if arg.whkd && which("whkd").is_err() { - return Err(anyhow!("could not find whkd, please make sure it is installed before using the --whkd flag")); + bail!("could not find whkd, please make sure it is installed before using the --whkd flag"); } if arg.ahk && which(&ahk).is_err() { - return Err(anyhow!("could not find autohotkey, please make sure it is installed before using the --ahk flag")); + bail!("could not find autohotkey, please make sure it is installed before using the --ahk flag"); } let mut buf: PathBuf; @@ -1594,7 +1616,7 @@ fn main() -> Result<()> { buf.pop(); // %USERPROFILE%\scoop\shims buf.pop(); // %USERPROFILE%\scoop buf.push("apps\\komorebi\\current\\komorebi.exe"); //%USERPROFILE%\scoop\komorebi\current\komorebi.exe - Option::from(buf.to_str().ok_or_else(|| { + Some(buf.to_str().ok_or_else(|| { anyhow!("cannot create a string from the scoop komorebi path") })?) } @@ -1605,17 +1627,13 @@ fn main() -> Result<()> { let mut flags = vec![]; if let Some(config) = arg.config { - let path = resolve_windows_path(config.as_os_str().to_str().unwrap())?; + let path = resolve_home_path(config)?; if !path.is_file() { - return Err(anyhow!("could not find file: {}", path.to_string_lossy())); + bail!("could not find file: {}", path.display()); } - flags.push(format!( - "'--config=\"{}\"'", - path.as_os_str() - .to_string_lossy() - .trim_start_matches(r"\\?\"), - )); + // we don't need to replace UNC prefix here as `resolve_home_path` already did + flags.push(format!("'--config=\"{}\"'", path.display())); } if arg.ffm { @@ -1670,12 +1688,12 @@ fn main() -> Result<()> { } if arg.whkd { - let script = r#" + let script = r" if (!(Get-Process whkd -ErrorAction SilentlyContinue)) { Start-Process whkd -WindowStyle hidden } - "#; + "; match powershell_script::run(script) { Ok(_) => { println!("{script}"); @@ -1687,15 +1705,14 @@ if (!(Get-Process whkd -ErrorAction SilentlyContinue)) } if arg.ahk { - let home = HOME_DIR.clone(); - let mut config_ahk = home; - config_ahk.push("komorebi.ahk"); + let config_ahk = HOME_DIR.join("komorebi.ahk"); + let config_ahk = dunce::simplified(&config_ahk); let script = format!( r#" Start-Process {ahk} {config} -WindowStyle hidden "#, - config = config_ahk.as_os_str().to_string_lossy() + config = config_ahk.display() ); match powershell_script::run(&script) { @@ -1710,9 +1727,9 @@ if (!(Get-Process whkd -ErrorAction SilentlyContinue)) } SubCommand::Stop(arg) => { if arg.whkd { - let script = r#" + let script = r" Stop-Process -Name:whkd -ErrorAction SilentlyContinue - "#; + "; match powershell_script::run(script) { Ok(_) => { println!("{script}"); @@ -1777,7 +1794,7 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue } SubCommand::LoadCustomLayout(arg) => { send_message( - &SocketMessage::ChangeLayoutCustom(resolve_windows_path(&arg.path)?).as_bytes()?, + &SocketMessage::ChangeLayoutCustom(resolve_home_path(arg.path)?).as_bytes()?, )?; } SubCommand::FlipLayout(arg) => { @@ -1843,74 +1860,12 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue )?; } SubCommand::State => { - let home = DATA_DIR.clone(); - let mut socket = home; - socket.push("komorebic.sock"); - let socket = socket.as_path(); - - match std::fs::remove_file(socket) { - Ok(_) => {} - Err(error) => match error.kind() { - // Doing this because ::exists() doesn't work reliably on Windows via IntelliJ - ErrorKind::NotFound => {} - _ => { - return Err(error.into()); - } - }, - }; - - let listener = UnixListener::bind(socket)?; - - send_message(&SocketMessage::State.as_bytes()?)?; - - match listener.accept() { - Ok(incoming) => { - let stream = BufReader::new(incoming.0); - for line in stream.lines() { - println!("{}", line?); - } - - return Ok(()); - } - Err(error) => { - panic!("{}", error); - } - } + with_komorebic_socket(|| send_message(&SocketMessage::State.as_bytes()?))?; } SubCommand::Query(arg) => { - let home = DATA_DIR.clone(); - let mut socket = home; - socket.push("komorebic.sock"); - let socket = socket.as_path(); - - match std::fs::remove_file(socket) { - Ok(_) => {} - Err(error) => match error.kind() { - // Doing this because ::exists() doesn't work reliably on Windows via IntelliJ - ErrorKind::NotFound => {} - _ => { - return Err(error.into()); - } - }, - }; - - let listener = UnixListener::bind(socket)?; - - send_message(&SocketMessage::Query(arg.state_query).as_bytes()?)?; - - match listener.accept() { - Ok(incoming) => { - let stream = BufReader::new(incoming.0); - for line in stream.lines() { - println!("{}", line?); - } - - return Ok(()); - } - Err(error) => { - panic!("{}", error); - } - } + with_komorebic_socket(|| { + send_message(&SocketMessage::Query(arg.state_query).as_bytes()?) + })?; } SubCommand::RestoreWindows => { let hwnd_json = DATA_DIR.join("komorebi.hwnd.json"); @@ -1974,9 +1929,7 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue match target.identifier { ApplicationIdentifier::Exe => {} _ => { - return Err(anyhow!( - "this command requires applications to be identified by their exe" - )); + bail!("this command requires applications to be identified by their exe"); } } @@ -1998,10 +1951,10 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue send_message(&SocketMessage::QuickLoad.as_bytes()?)?; } SubCommand::SaveResize(arg) => { - send_message(&SocketMessage::Save(resolve_windows_path(&arg.path)?).as_bytes()?)?; + send_message(&SocketMessage::Save(resolve_home_path(arg.path)?).as_bytes()?)?; } SubCommand::LoadResize(arg) => { - send_message(&SocketMessage::Load(resolve_windows_path(&arg.path)?).as_bytes()?)?; + send_message(&SocketMessage::Load(resolve_home_path(arg.path)?).as_bytes()?)?; } SubCommand::Subscribe(arg) => { send_message(&SocketMessage::AddSubscriber(arg.named_pipe).as_bytes()?)?; @@ -2054,10 +2007,9 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue )?; } SubCommand::AhkAppSpecificConfiguration(arg) => { - let content = std::fs::read_to_string(resolve_windows_path(&arg.path)?)?; + let content = std::fs::read_to_string(resolve_home_path(arg.path)?)?; let lines = if let Some(override_path) = arg.override_path { - let override_content = - std::fs::read_to_string(resolve_windows_path(&override_path)?)?; + let override_content = std::fs::read_to_string(resolve_home_path(override_path)?)?; ApplicationConfigurationGenerator::generate_ahk( &content, @@ -2067,28 +2019,24 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue ApplicationConfigurationGenerator::generate_ahk(&content, None)? }; - let mut generated_config = HOME_DIR.clone(); - generated_config.push("komorebi.generated.ahk"); + let generated_config = HOME_DIR.join("komorebi.generated.ahk"); let mut file = OpenOptions::new() .write(true) .create(true) .truncate(true) - .open(generated_config.clone())?; + .open(&generated_config)?; file.write_all(lines.join("\n").as_bytes())?; println!( "\nApplication-specific generated configuration written to {}", - generated_config.to_str().ok_or_else(|| anyhow!( - "could not find the path to the generated configuration file" - ))? + generated_config.display() ); } SubCommand::PwshAppSpecificConfiguration(arg) => { - let content = std::fs::read_to_string(resolve_windows_path(&arg.path)?)?; + let content = std::fs::read_to_string(resolve_home_path(arg.path)?)?; let lines = if let Some(override_path) = arg.override_path { - let override_content = - std::fs::read_to_string(resolve_windows_path(&override_path)?)?; + let override_content = std::fs::read_to_string(resolve_home_path(override_path)?)?; ApplicationConfigurationGenerator::generate_pwsh( &content, @@ -2098,25 +2046,22 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue ApplicationConfigurationGenerator::generate_pwsh(&content, None)? }; - let mut generated_config = HOME_DIR.clone(); - generated_config.push("komorebi.generated.ps1"); + let generated_config = HOME_DIR.join("komorebi.generated.ps1"); let mut file = OpenOptions::new() .write(true) .create(true) .truncate(true) - .open(generated_config.clone())?; + .open(&generated_config)?; file.write_all(lines.join("\n").as_bytes())?; println!( "\nApplication-specific generated configuration written to {}", - generated_config.to_str().ok_or_else(|| anyhow!( - "could not find the path to the generated configuration file" - ))? + generated_config.display() ); } SubCommand::FormatAppSpecificConfiguration(arg) => { - let file_path = resolve_windows_path(&arg.path)?; + let file_path = resolve_home_path(arg.path)?; let content = std::fs::read_to_string(&file_path)?; let formatted_content = ApplicationConfigurationGenerator::format(&content)?; @@ -2134,8 +2079,7 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue let content = reqwest::blocking::get("https://raw.githubusercontent.com/LGUG2Z/komorebi-application-specific-configuration/master/applications.yaml")? .text()?; - let mut output_file = HOME_DIR.clone(); - output_file.push("applications.yaml"); + let output_file = HOME_DIR.join("applications.yaml"); let mut file = OpenOptions::new() .write(true) @@ -2145,189 +2089,31 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue file.write_all(content.as_bytes())?; - let output_path = output_file.to_str().unwrap().to_string(); - let output_path = output_path.replace('\\', "/"); - println!("Latest version of applications.yaml from https://github.com/LGUG2Z/komorebi-application-specific-configuration downloaded\n"); println!( - "You can add this to your komorebi.json static configuration file like this: \n\n\"app_specific_configuration_path\": \"{output_path}\"", + "You can add this to your komorebi.json static configuration file like this: \n\n\"app_specific_configuration_path\": \"{}\"", + output_file.display() ); } SubCommand::NotificationSchema => { - let home = DATA_DIR.clone(); - let mut socket = home; - socket.push("komorebic.sock"); - let socket = socket.as_path(); - - match std::fs::remove_file(socket) { - Ok(_) => {} - Err(error) => match error.kind() { - // Doing this because ::exists() doesn't work reliably on Windows via IntelliJ - ErrorKind::NotFound => {} - _ => { - return Err(error.into()); - } - }, - }; - - send_message(&SocketMessage::NotificationSchema.as_bytes()?)?; - - let listener = UnixListener::bind(socket)?; - match listener.accept() { - Ok(incoming) => { - let stream = BufReader::new(incoming.0); - for line in stream.lines() { - println!("{}", line?); - } - - return Ok(()); - } - Err(error) => { - panic!("{}", error); - } - } + with_komorebic_socket(|| send_message(&SocketMessage::NotificationSchema.as_bytes()?))?; } SubCommand::SocketSchema => { - let home = DATA_DIR.clone(); - let mut socket = home; - socket.push("komorebic.sock"); - let socket = socket.as_path(); - - match std::fs::remove_file(socket) { - Ok(_) => {} - Err(error) => match error.kind() { - // Doing this because ::exists() doesn't work reliably on Windows via IntelliJ - ErrorKind::NotFound => {} - _ => { - return Err(error.into()); - } - }, - }; - - send_message(&SocketMessage::SocketSchema.as_bytes()?)?; - - let listener = UnixListener::bind(socket)?; - match listener.accept() { - Ok(incoming) => { - let stream = BufReader::new(incoming.0); - for line in stream.lines() { - println!("{}", line?); - } - - return Ok(()); - } - Err(error) => { - panic!("{}", error); - } - } + with_komorebic_socket(|| send_message(&SocketMessage::SocketSchema.as_bytes()?))?; } SubCommand::StaticConfigSchema => { - let home = DATA_DIR.clone(); - let mut socket = home; - socket.push("komorebic.sock"); - let socket = socket.as_path(); - - match std::fs::remove_file(socket) { - Ok(_) => {} - Err(error) => match error.kind() { - // Doing this because ::exists() doesn't work reliably on Windows via IntelliJ - ErrorKind::NotFound => {} - _ => { - return Err(error.into()); - } - }, - }; - - send_message(&SocketMessage::StaticConfigSchema.as_bytes()?)?; - - let listener = UnixListener::bind(socket)?; - match listener.accept() { - Ok(incoming) => { - let stream = BufReader::new(incoming.0); - for line in stream.lines() { - println!("{}", line?); - } - - return Ok(()); - } - Err(error) => { - panic!("{}", error); - } - } + with_komorebic_socket(|| send_message(&SocketMessage::StaticConfigSchema.as_bytes()?))?; } SubCommand::GenerateStaticConfig => { - let home = DATA_DIR.clone(); - let mut socket = home; - socket.push("komorebic.sock"); - let socket = socket.as_path(); - - match std::fs::remove_file(socket) { - Ok(_) => {} - Err(error) => match error.kind() { - // Doing this because ::exists() doesn't work reliably on Windows via IntelliJ - ErrorKind::NotFound => {} - _ => { - return Err(error.into()); - } - }, - }; - - send_message(&SocketMessage::GenerateStaticConfig.as_bytes()?)?; - - let listener = UnixListener::bind(socket)?; - match listener.accept() { - Ok(incoming) => { - let stream = BufReader::new(incoming.0); - for line in stream.lines() { - println!("{}", line?); - } - - return Ok(()); - } - Err(error) => { - panic!("{}", error); - } - } + with_komorebic_socket(|| { + send_message(&SocketMessage::GenerateStaticConfig.as_bytes()?) + })?; } } Ok(()) } -fn resolve_windows_path(raw_path: &str) -> Result { - let path = if raw_path.starts_with('~') { - raw_path.replacen( - '~', - &dirs::home_dir() - .ok_or_else(|| anyhow!("there is no home directory"))? - .display() - .to_string(), - 1, - ) - } else { - raw_path.to_string() - }; - - let full_path = PathBuf::from(path); - - let parent = full_path - .parent() - .ok_or_else(|| anyhow!("cannot parse directory"))?; - - Ok(if parent.is_dir() { - let file = full_path - .components() - .last() - .ok_or_else(|| anyhow!("cannot parse filename"))?; - - let mut canonicalized = std::fs::canonicalize(parent)?; - canonicalized.push(file); - canonicalized - } else { - full_path - }) -} - fn show_window(hwnd: HWND, command: SHOW_WINDOW_CMD) { // BOOL is returned but does not signify whether or not the operation was succesful // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow