feat(ffm): explicitly require flag to enable

Following the discovery that the custom FFM implementation significantly
increases CPU usage, and that the underlying library used to track mouse
events is already as optimised as possible for CPU usage, this commit
makes the enabling of custom FFM explicit via a command line flag when
launching the window manager.

The underlying library does not provide for a way to clean up and
recreate a message loop on demand, which means that once it starts,
there is no way of reclaiming those CPU cycles even when FFM is
disabled.

If a user has not started komorebi with the --ffm flag and tries to
enable or toggle custom FFM, a warning will be shown in the logs and
komorebi will override their selection to operate on the Windows FFM
implementation.

In light of this, the default implementation values for komorebic's FFM
commands have been updated to 'windows'.

This commit also takes the opportunity to allow the state and stop
commands to pass when the window manager is in a paused state.

resolve #33
This commit is contained in:
LGUG2Z
2021-09-16 09:36:53 -07:00
parent 28a641609c
commit b8a27a93fe
7 changed files with 225 additions and 145 deletions
Generated
+1
View File
@@ -509,6 +509,7 @@ version = "0.1.3"
dependencies = [ dependencies = [
"bindings", "bindings",
"bitflags", "bitflags",
"clap",
"color-eyre", "color-eyre",
"crossbeam-channel", "crossbeam-channel",
"crossbeam-utils", "crossbeam-utils",
+8 -3
View File
@@ -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 implementation, which only considers windows managed by `komorebi` as valid targets to switch focus to when moving the
mouse. mouse.
When calling any of the `komorebic` commands related to focus-follows-mouse functionality, the `komorebi` To enable the `komorebi` implementation you must start the process with the `--ffm` flag to explicitly enable the feature.
implementation will be chosen as the default implementation. You can optionally specify the `windows` implementation by 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: passing it as an argument to the `--implementation` flag:
```powershell ```powershell
komorebic.exe toggle-focus-follows-mouse --implementation windows komorebic.exe toggle-focus-follows-mouse --implementation komorebi
``` ```
## Configuration with `komorebic` ## Configuration with `komorebic`
+6
View File
@@ -1,6 +1,11 @@
[package] [package]
name = "komorebi" name = "komorebi"
version = "0.1.3" version = "0.1.3"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "A tiling window manager for Windows"
categories = ["tiling-window-manager", "windows"]
repository = "https://github.com/LGUG2Z/komorebi"
license = "MIT"
edition = "2018" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # 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" } komorebi-core = { path = "../komorebi-core" }
bitflags = "1" bitflags = "1"
clap = "3.0.0-beta.4"
color-eyre = "0.5" color-eyre = "0.5"
crossbeam-channel = "0.5" crossbeam-channel = "0.5"
crossbeam-utils = "0.8" crossbeam-utils = "0.8"
+70 -51
View File
@@ -3,12 +3,15 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::process::Command; use std::process::Command;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
#[cfg(feature = "deadlock_detection")] #[cfg(feature = "deadlock_detection")]
use std::thread; use std::thread;
#[cfg(feature = "deadlock_detection")] #[cfg(feature = "deadlock_detection")]
use std::time::Duration; use std::time::Duration;
use clap::Clap;
use color_eyre::eyre::anyhow; use color_eyre::eyre::anyhow;
use color_eyre::Result; use color_eyre::Result;
use crossbeam_channel::Receiver; use crossbeam_channel::Receiver;
@@ -73,6 +76,8 @@ lazy_static! {
static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![])); static ref BORDER_OVERFLOW_IDENTIFIERS: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
} }
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
fn setup() -> Result<(WorkerGuard, WorkerGuard)> { fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
if std::env::var("RUST_LIB_BACKTRACE").is_err() { if std::env::var("RUST_LIB_BACKTRACE").is_err() {
std::env::set_var("RUST_LIB_BACKTRACE", "1"); 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] #[tracing::instrument]
fn main() -> Result<()> { fn main() -> Result<()> {
match std::env::args().count() { let opts: Opts = Opts::parse();
1 => { CUSTOM_FFM.store(opts.focus_follows_mouse, Ordering::SeqCst);
let mut system = sysinfo::System::new_all();
system.refresh_processes();
if system.process_by_name("komorebi.exe").len() > 1 { let arg_count = std::env::args().count();
tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one"); let has_valid_args = arg_count == 1 || (arg_count == 2 && CUSTOM_FFM.load(Ordering::SeqCst));
std::process::exit(1);
}
// File logging worker guard has to have an assignment in the main fn to work if has_valid_args {
let (_guard, _color_guard) = setup()?; let mut system = sysinfo::System::new_all();
system.refresh_processes();
#[cfg(feature = "deadlock_detection")] if system.process_by_name("komorebi.exe").len() > 1 {
detect_deadlocks(); tracing::error!("komorebi.exe is already running, please exit the existing process before starting a new one");
std::process::exit(1);
let process_id = WindowsApi::current_process_id();
WindowsApi::allow_set_foreground_window(process_id)?;
let (outgoing, incoming): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
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);
} }
_ => 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<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
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(())
} }
+108 -83
View File
@@ -2,6 +2,7 @@ use std::io::BufRead;
use std::io::BufReader; use std::io::BufReader;
use std::io::Write; use std::io::Write;
use std::str::FromStr; use std::str::FromStr;
use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
@@ -18,6 +19,7 @@ use crate::window_manager;
use crate::window_manager::WindowManager; use crate::window_manager::WindowManager;
use crate::windows_api::WindowsApi; use crate::windows_api::WindowsApi;
use crate::BORDER_OVERFLOW_IDENTIFIERS; use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::CUSTOM_FFM;
use crate::FLOAT_IDENTIFIERS; use crate::FLOAT_IDENTIFIERS;
use crate::MANAGE_IDENTIFIERS; use crate::MANAGE_IDENTIFIERS;
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS; use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
@@ -114,7 +116,12 @@ impl WindowManager {
self.move_container_to_monitor(monitor_idx, false)?; self.move_container_to_monitor(monitor_idx, false)?;
} }
SocketMessage::TogglePause => { SocketMessage::TogglePause => {
tracing::info!("pausing"); if self.is_paused {
tracing::info!("resuming");
} else {
tracing::info!("pausing");
}
self.is_paused = !self.is_paused; self.is_paused = !self.is_paused;
} }
SocketMessage::ToggleTiling => { SocketMessage::ToggleTiling => {
@@ -198,82 +205,99 @@ impl WindowManager {
SocketMessage::ResizeWindow(direction, sizing) => { SocketMessage::ResizeWindow(direction, sizing) => {
self.resize_window(direction, sizing, Option::from(50))?; self.resize_window(direction, sizing, Option::from(50))?;
} }
SocketMessage::FocusFollowsMouse(implementation, enable) => match implementation { SocketMessage::FocusFollowsMouse(mut implementation, enable) => {
FocusFollowsMouseImplementation::Komorebi => { if !CUSTOM_FFM.load(Ordering::SeqCst) {
if WindowsApi::focus_follows_mouse()? { tracing::warn!(
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"
"the komorebi implementation of focus follows mouse cannot be enabled while the windows implementation is enabled" );
); implementation = FocusFollowsMouseImplementation::Windows;
} 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(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 => { SocketMessage::ReloadConfiguration => {
Self::reload_configuration(); Self::reload_configuration();
} }
@@ -315,14 +339,15 @@ impl WindowManager {
let message = SocketMessage::from_str(&line?)?; let message = SocketMessage::from_str(&line?)?;
if self.is_paused { if self.is_paused {
if let SocketMessage::TogglePause = message { return match message {
tracing::info!("resuming"); SocketMessage::TogglePause | SocketMessage::State | SocketMessage::Stop => {
self.is_paused = !self.is_paused; Ok(self.process_command(message)?)
return Ok(()); }
} _ => {
tracing::trace!("ignoring while paused");
tracing::trace!("ignoring while paused"); Ok(())
return Ok(()); }
};
} }
self.process_command(message)?; self.process_command(message)?;
+2 -2
View File
@@ -1,7 +1,7 @@
; Generated by komorebic.exe ; Generated by komorebic.exe
Start() { Start(ffm) {
Run, komorebic.exe start, , Hide Run, komorebic.exe start --ffm %ffm%, , Hide
} }
Stop() { Stop() {
+30 -6
View File
@@ -249,18 +249,25 @@ struct WorkspaceRule {
#[derive(Clap, AhkFunction)] #[derive(Clap, AhkFunction)]
struct ToggleFocusFollowsMouse { struct ToggleFocusFollowsMouse {
#[clap(arg_enum, short, long, default_value = "komorebi")] #[clap(arg_enum, short, long, default_value = "windows")]
implementation: FocusFollowsMouseImplementation, implementation: FocusFollowsMouseImplementation,
} }
#[derive(Clap, AhkFunction)] #[derive(Clap, AhkFunction)]
struct FocusFollowsMouse { struct FocusFollowsMouse {
#[clap(arg_enum, short, long, default_value = "komorebi")] #[clap(arg_enum, short, long, default_value = "windows")]
implementation: FocusFollowsMouseImplementation, implementation: FocusFollowsMouseImplementation,
#[clap(arg_enum)] #[clap(arg_enum)]
boolean_state: BooleanState, 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)] #[derive(Clap)]
#[clap(author, about, version, setting = AppSettings::DeriveDisplayOrder)] #[clap(author, about, version, setting = AppSettings::DeriveDisplayOrder)]
struct Opts { struct Opts {
@@ -271,7 +278,7 @@ struct Opts {
#[derive(Clap, AhkLibrary)] #[derive(Clap, AhkLibrary)]
enum SubCommand { enum SubCommand {
/// Start komorebi.exe as a background process /// Start komorebi.exe as a background process
Start, Start(Start),
/// Stop the komorebi.exe process and restore all hidden windows /// Stop the komorebi.exe process and restore all hidden windows
Stop, Stop,
/// Show a JSON representation of the current window manager state /// Show a JSON representation of the current window manager state
@@ -535,7 +542,7 @@ fn main() -> Result<()> {
.as_bytes()?, .as_bytes()?,
)?; )?;
} }
SubCommand::Start => { SubCommand::Start(arg) => {
let mut buf: PathBuf; let mut buf: PathBuf;
// The komorebi.ps1 shim will only exist in the Path if installed by Scoop // 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( 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) { match powershell_script::run(&script, true) {