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

1
Cargo.lock generated
View File

@@ -509,6 +509,7 @@ version = "0.1.3"
dependencies = [
"bindings",
"bitflags",
"clap",
"color-eyre",
"crossbeam-channel",
"crossbeam-utils",

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
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`

View File

@@ -1,6 +1,11 @@
[package]
name = "komorebi"
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"
# 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"

View File

@@ -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<Mutex<Vec<String>>> = 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<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);
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<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(())
}

View File

@@ -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)?;

View File

@@ -1,7 +1,7 @@
; Generated by komorebic.exe
Start() {
Run, komorebic.exe start, , Hide
Start(ffm) {
Run, komorebic.exe start --ffm %ffm%, , Hide
}
Stop() {

View File

@@ -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) {