diff --git a/Cargo.lock b/Cargo.lock index c921acfc..cda89368 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -509,6 +509,7 @@ version = "0.1.3" dependencies = [ "bindings", "bitflags", + "clap", "color-eyre", "crossbeam-channel", "crossbeam-utils", diff --git a/README.md b/README.md index dbacb2d8..8fa652b7 100644 --- a/README.md +++ b/README.md @@ -171,12 +171,17 @@ desktop, the task bar, and the system tray as windows and switches focus to them implementation, which only considers windows managed by `komorebi` as valid targets to switch focus to when moving the mouse. -When calling any of the `komorebic` commands related to focus-follows-mouse functionality, the `komorebi` -implementation will be chosen as the default implementation. You can optionally specify the `windows` implementation by +To enable the `komorebi` implementation you must start the process with the `--ffm` flag to explicitly enable the feature. +This is because the mouse tracking required for this feature significantly increases the CPU usage of the process (on my +machine, it jumps from <1% to ~4~), and this CPU increase persists regardless of whether focus-follows-mouse is enabled +or disabled at any given time via `komorebic`'s configuration commands. + +When calling any of the `komorebic` commands related to focus-follows-mouse functionality, the `windows` +implementation will be chosen as the default implementation. You can optionally specify the `komorebi` implementation by passing it as an argument to the `--implementation` flag: ```powershell -komorebic.exe toggle-focus-follows-mouse --implementation windows +komorebic.exe toggle-focus-follows-mouse --implementation komorebi ``` ## Configuration with `komorebic` diff --git a/komorebi/Cargo.toml b/komorebi/Cargo.toml index ca16b1a8..8ae8dfe7 100644 --- a/komorebi/Cargo.toml +++ b/komorebi/Cargo.toml @@ -1,6 +1,11 @@ [package] name = "komorebi" version = "0.1.3" +authors = ["Jade Iqbal "] +description = "A tiling window manager for Windows" +categories = ["tiling-window-manager", "windows"] +repository = "https://github.com/LGUG2Z/komorebi" +license = "MIT" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -10,6 +15,7 @@ bindings = { package = "bindings", path = "../bindings" } komorebi-core = { path = "../komorebi-core" } bitflags = "1" +clap = "3.0.0-beta.4" color-eyre = "0.5" crossbeam-channel = "0.5" crossbeam-utils = "0.8" diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index 135b692c..c9f80cef 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -3,12 +3,15 @@ use std::collections::HashMap; use std::process::Command; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; use std::sync::Arc; #[cfg(feature = "deadlock_detection")] use std::thread; #[cfg(feature = "deadlock_detection")] use std::time::Duration; +use clap::Clap; use color_eyre::eyre::anyhow; use color_eyre::Result; use crossbeam_channel::Receiver; @@ -73,6 +76,8 @@ lazy_static! { static ref BORDER_OVERFLOW_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(vec![])); } +pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false); + fn setup() -> Result<(WorkerGuard, WorkerGuard)> { if std::env::var("RUST_LIB_BACKTRACE").is_err() { std::env::set_var("RUST_LIB_BACKTRACE", "1"); @@ -195,62 +200,76 @@ fn detect_deadlocks() { }); } +#[derive(Clap)] +#[clap(author, about, version)] +struct Opts { + /// Allow the use of komorebi's custom focus-follows-mouse implementation + #[clap(long = "ffm")] + focus_follows_mouse: bool, +} + #[tracing::instrument] fn main() -> Result<()> { - match std::env::args().count() { - 1 => { - let mut system = sysinfo::System::new_all(); - system.refresh_processes(); + let opts: Opts = Opts::parse(); + CUSTOM_FFM.store(opts.focus_follows_mouse, Ordering::SeqCst); - if system.process_by_name("komorebi.exe").len() > 1 { - tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one"); - std::process::exit(1); - } + let arg_count = std::env::args().count(); + let has_valid_args = arg_count == 1 || (arg_count == 2 && CUSTOM_FFM.load(Ordering::SeqCst)); - // File logging worker guard has to have an assignment in the main fn to work - let (_guard, _color_guard) = setup()?; + if has_valid_args { + let mut system = sysinfo::System::new_all(); + system.refresh_processes(); - #[cfg(feature = "deadlock_detection")] - detect_deadlocks(); - - let process_id = WindowsApi::current_process_id(); - WindowsApi::allow_set_foreground_window(process_id)?; - - let (outgoing, incoming): (Sender, Receiver) = - crossbeam_channel::unbounded(); - - let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing))); - winevent_listener.start(); - - let wm = Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new( - incoming, - )))?)); - - wm.lock().init()?; - listen_for_commands(wm.clone()); - listen_for_events(wm.clone()); - listen_for_movements(wm.clone()); - - load_configuration()?; - - let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1); - ctrlc::set_handler(move || { - ctrlc_sender - .send(()) - .expect("could not send signal on ctrl-c channel"); - })?; - - ctrlc_receiver - .recv() - .expect("could not receive signal on ctrl-c channel"); - - tracing::error!( - "received ctrl-c, restoring all hidden windows and terminating process" - ); - - wm.lock().restore_all_windows(); - std::process::exit(130); + if system.process_by_name("komorebi.exe").len() > 1 { + tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one"); + std::process::exit(1); } - _ => Ok(()), + + // File logging worker guard has to have an assignment in the main fn to work + let (_guard, _color_guard) = setup()?; + + #[cfg(feature = "deadlock_detection")] + detect_deadlocks(); + + let process_id = WindowsApi::current_process_id(); + WindowsApi::allow_set_foreground_window(process_id)?; + + let (outgoing, incoming): (Sender, Receiver) = + crossbeam_channel::unbounded(); + + let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing))); + winevent_listener.start(); + + let wm = Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new( + incoming, + )))?)); + + wm.lock().init()?; + listen_for_commands(wm.clone()); + listen_for_events(wm.clone()); + + if CUSTOM_FFM.load(Ordering::SeqCst) { + listen_for_movements(wm.clone()); + } + + load_configuration()?; + + let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1); + ctrlc::set_handler(move || { + ctrlc_sender + .send(()) + .expect("could not send signal on ctrl-c channel"); + })?; + + ctrlc_receiver + .recv() + .expect("could not receive signal on ctrl-c channel"); + + tracing::error!("received ctrl-c, restoring all hidden windows and terminating process"); + + wm.lock().restore_all_windows(); + std::process::exit(130); } + + Ok(()) } diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 89df618e..9e34704a 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -2,6 +2,7 @@ use std::io::BufRead; use std::io::BufReader; use std::io::Write; use std::str::FromStr; +use std::sync::atomic::Ordering; use std::sync::Arc; use std::thread; @@ -18,6 +19,7 @@ use crate::window_manager; use crate::window_manager::WindowManager; use crate::windows_api::WindowsApi; use crate::BORDER_OVERFLOW_IDENTIFIERS; +use crate::CUSTOM_FFM; use crate::FLOAT_IDENTIFIERS; use crate::MANAGE_IDENTIFIERS; use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS; @@ -114,7 +116,12 @@ impl WindowManager { self.move_container_to_monitor(monitor_idx, false)?; } SocketMessage::TogglePause => { - tracing::info!("pausing"); + if self.is_paused { + tracing::info!("resuming"); + } else { + tracing::info!("pausing"); + } + self.is_paused = !self.is_paused; } SocketMessage::ToggleTiling => { @@ -198,82 +205,99 @@ impl WindowManager { SocketMessage::ResizeWindow(direction, sizing) => { self.resize_window(direction, sizing, Option::from(50))?; } - SocketMessage::FocusFollowsMouse(implementation, enable) => match implementation { - FocusFollowsMouseImplementation::Komorebi => { - if WindowsApi::focus_follows_mouse()? { - tracing::warn!( - "the komorebi implementation of focus follows mouse cannot be enabled while the windows implementation is enabled" - ); - } else if enable { - self.focus_follows_mouse = Option::from(implementation); - } else { - self.focus_follows_mouse = None; - self.has_pending_raise_op = false; - } + SocketMessage::FocusFollowsMouse(mut implementation, enable) => { + if !CUSTOM_FFM.load(Ordering::SeqCst) { + tracing::warn!( + "komorebi was not started with the --ffm flag, so the komorebi implementation of focus follows mouse cannot be enabled; defaulting to windows implementation" + ); + implementation = FocusFollowsMouseImplementation::Windows; } - FocusFollowsMouseImplementation::Windows => { - if let Some(FocusFollowsMouseImplementation::Komorebi) = - self.focus_follows_mouse - { - tracing::warn!( - "the windows implementation of focus follows mouse cannot be enabled while the komorebi implementation is enabled" - ); - } else if enable { - WindowsApi::enable_focus_follows_mouse()?; - self.focus_follows_mouse = - Option::from(FocusFollowsMouseImplementation::Windows); - } else { - WindowsApi::disable_focus_follows_mouse()?; - self.focus_follows_mouse = None; - } - } - }, - SocketMessage::ToggleFocusFollowsMouse(implementation) => match implementation { - FocusFollowsMouseImplementation::Komorebi => { - if WindowsApi::focus_follows_mouse()? { - tracing::warn!( - "the komorebi implementation of focus follows mouse cannot be toggled while the windows implementation is enabled" - ); - } else { - match self.focus_follows_mouse { - None => { - self.focus_follows_mouse = Option::from(implementation); - self.has_pending_raise_op = false; - } - Some(FocusFollowsMouseImplementation::Komorebi) => { - self.focus_follows_mouse = None; - } - Some(FocusFollowsMouseImplementation::Windows) => { - tracing::warn!("ignoring command that could mix different focus follows mouse implementations"); - } - } - } - } - FocusFollowsMouseImplementation::Windows => { - if let Some(FocusFollowsMouseImplementation::Komorebi) = - self.focus_follows_mouse - { - tracing::warn!( - "the windows implementation of focus follows mouse cannot be toggled while the komorebi implementation is enabled" - ); - } else { - match self.focus_follows_mouse { - None => { - WindowsApi::enable_focus_follows_mouse()?; - self.focus_follows_mouse = Option::from(implementation); - } - Some(FocusFollowsMouseImplementation::Windows) => { - WindowsApi::disable_focus_follows_mouse()?; - self.focus_follows_mouse = None; - } - Some(FocusFollowsMouseImplementation::Komorebi) => { - tracing::warn!("ignoring command that could mix different focus follows mouse implementations"); - } - } - } - } - }, + match implementation { + FocusFollowsMouseImplementation::Komorebi => { + if WindowsApi::focus_follows_mouse()? { + tracing::warn!( + "the komorebi implementation of focus follows mouse cannot be enabled while the windows implementation is enabled" + ); + } else if enable { + self.focus_follows_mouse = Option::from(implementation); + } else { + self.focus_follows_mouse = None; + self.has_pending_raise_op = false; + } + } + FocusFollowsMouseImplementation::Windows => { + if let Some(FocusFollowsMouseImplementation::Komorebi) = + self.focus_follows_mouse + { + tracing::warn!( + "the windows implementation of focus follows mouse cannot be enabled while the komorebi implementation is enabled" + ); + } else if enable { + WindowsApi::enable_focus_follows_mouse()?; + self.focus_follows_mouse = + Option::from(FocusFollowsMouseImplementation::Windows); + } else { + WindowsApi::disable_focus_follows_mouse()?; + self.focus_follows_mouse = None; + } + } + } + } + SocketMessage::ToggleFocusFollowsMouse(mut implementation) => { + if !CUSTOM_FFM.load(Ordering::SeqCst) { + tracing::warn!( + "komorebi was not started with the --ffm flag, so the komorebi implementation of focus follows mouse cannot be toggled; defaulting to windows implementation" + ); + implementation = FocusFollowsMouseImplementation::Windows; + } + + match implementation { + FocusFollowsMouseImplementation::Komorebi => { + if WindowsApi::focus_follows_mouse()? { + tracing::warn!( + "the komorebi implementation of focus follows mouse cannot be toggled while the windows implementation is enabled" + ); + } else { + match self.focus_follows_mouse { + None => { + self.focus_follows_mouse = Option::from(implementation); + self.has_pending_raise_op = false; + } + Some(FocusFollowsMouseImplementation::Komorebi) => { + self.focus_follows_mouse = None; + } + Some(FocusFollowsMouseImplementation::Windows) => { + tracing::warn!("ignoring command that could mix different focus follows mouse implementations"); + } + } + } + } + FocusFollowsMouseImplementation::Windows => { + if let Some(FocusFollowsMouseImplementation::Komorebi) = + self.focus_follows_mouse + { + tracing::warn!( + "the windows implementation of focus follows mouse cannot be toggled while the komorebi implementation is enabled" + ); + } else { + match self.focus_follows_mouse { + None => { + WindowsApi::enable_focus_follows_mouse()?; + self.focus_follows_mouse = Option::from(implementation); + } + Some(FocusFollowsMouseImplementation::Windows) => { + WindowsApi::disable_focus_follows_mouse()?; + self.focus_follows_mouse = None; + } + Some(FocusFollowsMouseImplementation::Komorebi) => { + tracing::warn!("ignoring command that could mix different focus follows mouse implementations"); + } + } + } + } + } + } SocketMessage::ReloadConfiguration => { Self::reload_configuration(); } @@ -315,14 +339,15 @@ impl WindowManager { let message = SocketMessage::from_str(&line?)?; if self.is_paused { - if let SocketMessage::TogglePause = message { - tracing::info!("resuming"); - self.is_paused = !self.is_paused; - return Ok(()); - } - - tracing::trace!("ignoring while paused"); - return Ok(()); + return match message { + SocketMessage::TogglePause | SocketMessage::State | SocketMessage::Stop => { + Ok(self.process_command(message)?) + } + _ => { + tracing::trace!("ignoring while paused"); + Ok(()) + } + }; } self.process_command(message)?; diff --git a/komorebic.lib.sample.ahk b/komorebic.lib.sample.ahk index 39f751f4..55f9dab5 100644 --- a/komorebic.lib.sample.ahk +++ b/komorebic.lib.sample.ahk @@ -1,7 +1,7 @@ ; Generated by komorebic.exe -Start() { - Run, komorebic.exe start, , Hide +Start(ffm) { + Run, komorebic.exe start --ffm %ffm%, , Hide } Stop() { diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 889e5e58..365c32d1 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -249,18 +249,25 @@ struct WorkspaceRule { #[derive(Clap, AhkFunction)] struct ToggleFocusFollowsMouse { - #[clap(arg_enum, short, long, default_value = "komorebi")] + #[clap(arg_enum, short, long, default_value = "windows")] implementation: FocusFollowsMouseImplementation, } #[derive(Clap, AhkFunction)] struct FocusFollowsMouse { - #[clap(arg_enum, short, long, default_value = "komorebi")] + #[clap(arg_enum, short, long, default_value = "windows")] implementation: FocusFollowsMouseImplementation, #[clap(arg_enum)] boolean_state: BooleanState, } +#[derive(Clap, AhkFunction)] +struct Start { + /// Allow the use of komorebi's custom focus-follows-mouse implementation + #[clap(long)] + ffm: bool, +} + #[derive(Clap)] #[clap(author, about, version, setting = AppSettings::DeriveDisplayOrder)] struct Opts { @@ -271,7 +278,7 @@ struct Opts { #[derive(Clap, AhkLibrary)] enum SubCommand { /// Start komorebi.exe as a background process - Start, + Start(Start), /// Stop the komorebi.exe process and restore all hidden windows Stop, /// Show a JSON representation of the current window manager state @@ -535,7 +542,7 @@ fn main() -> Result<()> { .as_bytes()?, )?; } - SubCommand::Start => { + SubCommand::Start(arg) => { let mut buf: PathBuf; // The komorebi.ps1 shim will only exist in the Path if installed by Scoop @@ -559,8 +566,25 @@ fn main() -> Result<()> { }; let script = exec.map_or_else( - || String::from("Start-Process komorebi -WindowStyle hidden"), - |exec| format!("Start-Process '{}' -WindowStyle hidden", exec), + || { + if arg.ffm { + String::from( + "Start-Process komorebi.exe -ArgumentList '--ffm' -WindowStyle hidden", + ) + } else { + String::from("Start-Process komorebi.exe -WindowStyle hidden") + } + }, + |exec| { + if arg.ffm { + format!( + "Start-Process '{}' -ArgumentList '--ffm' -WindowStyle hidden", + exec + ) + } else { + format!("Start-Process '{}' -WindowStyle hidden", exec) + } + }, ); match powershell_script::run(&script, true) {