mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-05-08 11:53:29 +02:00
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:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -509,6 +509,7 @@ version = "0.1.3"
|
||||
dependencies = [
|
||||
"bindings",
|
||||
"bitflags",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-utils",
|
||||
|
||||
11
README.md
11
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`
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
; Generated by komorebic.exe
|
||||
|
||||
Start() {
|
||||
Run, komorebic.exe start, , Hide
|
||||
Start(ffm) {
|
||||
Run, komorebic.exe start --ffm %ffm%, , Hide
|
||||
}
|
||||
|
||||
Stop() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user