From a55069df4858e6b63f7c93930a7e8d9e0f5186e1 Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Fri, 29 Oct 2021 10:04:19 -0700 Subject: [PATCH] feat(wm): add mouse follows focus toggle This commit adds a toggle for the mouse follows focus behaviour that has been the default for komorebi until now. resolve #63 --- README.md | 31 ++++++++++++------ komorebi-core/src/lib.rs | 1 + komorebi/src/monitor.rs | 4 +-- komorebi/src/process_command.rs | 7 +++-- komorebi/src/process_event.rs | 2 +- komorebi/src/window.rs | 6 ++-- komorebi/src/window_manager.rs | 56 +++++++++++++++++++-------------- komorebi/src/workspace.rs | 4 +-- komorebic.lib.sample.ahk | 4 +++ komorebic/src/main.rs | 5 +++ 10 files changed, 77 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 5d9dccb3..ed206870 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,16 @@ passing it as an argument to the `--implementation` flag: komorebic.exe toggle-focus-follows-mouse --implementation komorebi ``` +#### Mouse Follows Focus + +By default, the mouse will move to the center of the window when the focus is changed in a given direction. This +behaviour is know is 'mouse follows focus'. To disable this behaviour across all workspaces, add the following command +to your configuration file: + +```ahk +Run, komorebic.exe toggle-mouse-follows-focus, , Hide +``` + #### Saving and Loading Resized Layouts If you create a BSP layout through various resize adjustments that you want to be able to restore easily in the future, @@ -385,6 +395,7 @@ used [is available here](komorebi.sample.with.lib.ahk). - [x] Toggle floating windows - [x] Toggle monocle window - [x] Toggle native maximization +- [x] Toggle mouse follows focus - [x] Toggle Xmouse/Windows focus follows mouse implementation - [x] Toggle Komorebi focus follows mouse implementation (desktop and system tray-aware) - [x] Toggle automatic tiling @@ -470,16 +481,16 @@ Note that you do not have to include the full path of the named pipe, just the n If the named pipe exists, `komorebi` will start pushing JSON data of successfully handled events and messages: ```json lines -{"event":{"type":"AddSubscriber","content":"yasb"},"state":{...}} -{"event":{"type":"FocusWindow","content":"Left"},"state":{...}} -{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":131444,"title":"komorebi – README.md","exe":"idea64.exe","class":"SunAwtFrame","rect":{"left":13,"top":60,"right":1520,"bottom":1655}}]},"state":{...}} -{"event":{"type":"MonitorPoll","content":["ObjectCreate",{"hwnd":5572450,"title":"OLEChannelWnd","exe":"explorer.exe","class":"OleMainThreadWndClass","rect":{"left":0,"top":0,"right":0,"bottom":0}}]},"state":{...}} -{"event":{"type":"FocusWindow","content":"Right"},"state":{...}} -{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{}...} -{"event":{"type":"FocusWindow","content":"Down"},"state":{...}} -{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":329264,"title":"den — Mozilla Firefox","exe":"firefox.exe","class":"MozillaWindowClass","rect":{"left":1539,"top":894,"right":1520,"bottom":821}}]},"state":{...}} -{"event":{"type":"FocusWindow","content":"Up"},"state":{...}} -{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{...}} +{"event":{"type":"AddSubscriber","content":"yasb"},"state":{}} +{"event":{"type":"FocusWindow","content":"Left"},"state":{}} +{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":131444,"title":"komorebi – README.md","exe":"idea64.exe","class":"SunAwtFrame","rect":{"left":13,"top":60,"right":1520,"bottom":1655}}]},"state":{}} +{"event":{"type":"MonitorPoll","content":["ObjectCreate",{"hwnd":5572450,"title":"OLEChannelWnd","exe":"explorer.exe","class":"OleMainThreadWndClass","rect":{"left":0,"top":0,"right":0,"bottom":0}}]},"state":{}} +{"event":{"type":"FocusWindow","content":"Right"},"state":{}} +{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{}} +{"event":{"type":"FocusWindow","content":"Down"},"state":{}} +{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":329264,"title":"den — Mozilla Firefox","exe":"firefox.exe","class":"MozillaWindowClass","rect":{"left":1539,"top":894,"right":1520,"bottom":821}}]},"state":{}} +{"event":{"type":"FocusWindow","content":"Up"},"state":{}} +{"event":{"type":"FocusChange","content":["SystemForeground",{"hwnd":132968,"title":"Windows PowerShell","exe":"WindowsTerminal.exe","class":"CASCADIA_HOSTING_WINDOW_CLASS","rect":{"left":1539,"top":60,"right":1520,"bottom":821}}]},"state":{}} ``` You may then filter on the `type` key to listen to the events that you are interested in. For a full list of possible diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 9376f7a4..4e4b4a1c 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -93,6 +93,7 @@ pub enum SocketMessage { Query(StateQuery), FocusFollowsMouse(FocusFollowsMouseImplementation, bool), ToggleFocusFollowsMouse(FocusFollowsMouseImplementation), + ToggleMouseFollowsFocus, AddSubscriber(String), RemoveSubscriber(String), } diff --git a/komorebi/src/monitor.rs b/komorebi/src/monitor.rs index f459f24a..6c6bbc08 100644 --- a/komorebi/src/monitor.rs +++ b/komorebi/src/monitor.rs @@ -45,11 +45,11 @@ pub fn new(id: isize, size: Rect, work_area_size: Rect) -> Monitor { } impl Monitor { - pub fn load_focused_workspace(&mut self) -> Result<()> { + pub fn load_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> { let focused_idx = self.focused_workspace_idx(); for (i, workspace) in self.workspaces_mut().iter_mut().enumerate() { if i == focused_idx { - workspace.restore()?; + workspace.restore(mouse_follows_focus)?; } else { workspace.hide(); } diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 4f281e39..7e56591b 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -150,11 +150,11 @@ impl WindowManager { ); self.focus_monitor(monitor_idx)?; - self.update_focused_workspace(true)?; + self.update_focused_workspace(self.mouse_follows_focus)?; } SocketMessage::FocusMonitorNumber(monitor_idx) => { self.focus_monitor(monitor_idx)?; - self.update_focused_workspace(true)?; + self.update_focused_workspace(self.mouse_follows_focus)?; } SocketMessage::Retile => self.retile_all()?, SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?, @@ -453,6 +453,9 @@ impl WindowManager { let mut pipes = SUBSCRIPTION_PIPES.lock(); pipes.remove(&subscriber); } + SocketMessage::ToggleMouseFollowsFocus => { + self.mouse_follows_focus = !self.mouse_follows_focus; + } }; tracing::info!("processed"); diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index eaaead1e..07394527 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -347,7 +347,7 @@ impl WindowManager { workspace.swap_containers(focused_container_idx, target_idx); self.update_focused_workspace(false)?; } - None => self.update_focused_workspace(true)?, + None => self.update_focused_workspace(self.mouse_follows_focus)?, } } } else { diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 34010f60..5dea60a4 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -187,7 +187,7 @@ impl Window { WindowsApi::set_focus(self.hwnd()) } - pub fn focus(self) -> Result<()> { + pub fn focus(self, mouse_follows_focus: bool) -> Result<()> { // Attach komorebi thread to Window thread let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd()); let current_thread_id = WindowsApi::current_thread_id(); @@ -205,7 +205,9 @@ impl Window { }; // Center cursor in Window - WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd())?)?; + if mouse_follows_focus { + WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd())?)?; + } // This isn't really needed when the above command works as expected via AHK WindowsApi::set_focus(self.hwnd()) diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index d630110b..e7c44d09 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -50,6 +50,7 @@ pub struct WindowManager { pub invisible_borders: Rect, pub work_area_offset: Option, pub focus_follows_mouse: Option, + pub mouse_follows_focus: bool, pub hotwatch: Hotwatch, pub virtual_desktop_id: Option, pub has_pending_raise_op: bool, @@ -63,6 +64,7 @@ pub struct State { pub invisible_borders: Rect, pub work_area_offset: Option, pub focus_follows_mouse: Option, + pub mouse_follows_focus: bool, pub has_pending_raise_op: bool, pub float_identifiers: Vec, pub manage_identifiers: Vec, @@ -79,6 +81,7 @@ impl From<&WindowManager> for State { invisible_borders: wm.invisible_borders, work_area_offset: wm.work_area_offset, focus_follows_mouse: wm.focus_follows_mouse.clone(), + mouse_follows_focus: wm.mouse_follows_focus, has_pending_raise_op: wm.has_pending_raise_op, float_identifiers: FLOAT_IDENTIFIERS.lock().clone(), manage_identifiers: MANAGE_IDENTIFIERS.lock().clone(), @@ -151,6 +154,7 @@ impl WindowManager { }, work_area_offset: None, focus_follows_mouse: None, + mouse_follows_focus: true, hotwatch: Hotwatch::new()?, virtual_desktop_id, has_pending_raise_op: false, @@ -628,7 +632,7 @@ impl WindowManager { } #[tracing::instrument(skip(self))] - pub fn update_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> { + pub fn update_focused_workspace(&mut self, follow_focus: bool) -> Result<()> { tracing::info!("updating"); let invisible_borders = self.invisible_borders; @@ -638,15 +642,15 @@ impl WindowManager { .ok_or_else(|| anyhow!("there is no monitor"))? .update_focused_workspace(offset, &invisible_borders)?; - if mouse_follows_focus { + if follow_focus { if let Some(window) = self.focused_workspace()?.maximized_window() { - window.focus()?; + window.focus(self.mouse_follows_focus)?; } else if let Some(container) = self.focused_workspace()?.monocle_container() { if let Some(window) = container.focused_window() { - window.focus()?; + window.focus(self.mouse_follows_focus)?; } } else if let Ok(window) = self.focused_window_mut() { - window.focus()?; + window.focus(self.mouse_follows_focus)?; } else { let desktop_window = Window { hwnd: WindowsApi::desktop_window()?, @@ -773,6 +777,7 @@ impl WindowManager { let invisible_borders = self.invisible_borders; let offset = self.work_area_offset; + let mouse_follows_focus = self.mouse_follows_focus; let monitor = self .focused_monitor_mut() @@ -797,28 +802,29 @@ impl WindowManager { .ok_or_else(|| anyhow!("there is no monitor"))?; target_monitor.add_container(container)?; - target_monitor.load_focused_workspace()?; + target_monitor.load_focused_workspace(mouse_follows_focus)?; target_monitor.update_focused_workspace(offset, &invisible_borders)?; if follow { self.focus_monitor(idx)?; } - self.update_focused_workspace(true) + self.update_focused_workspace(self.mouse_follows_focus) } #[tracing::instrument(skip(self))] pub fn move_container_to_workspace(&mut self, idx: usize, follow: bool) -> Result<()> { tracing::info!("moving container"); + let mouse_follows_focus = self.mouse_follows_focus; let monitor = self .focused_monitor_mut() .ok_or_else(|| anyhow!("there is no monitor"))?; monitor.move_container_to_workspace(idx, follow)?; - monitor.load_focused_workspace()?; + monitor.load_focused_workspace(mouse_follows_focus)?; - self.update_focused_workspace(true) + self.update_focused_workspace(mouse_follows_focus) } #[tracing::instrument(skip(self))] @@ -831,7 +837,7 @@ impl WindowManager { .ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?; workspace.focus_container(new_idx); - self.focused_window_mut()?.focus()?; + self.focused_window_mut()?.focus(self.mouse_follows_focus)?; Ok(()) } @@ -849,7 +855,7 @@ impl WindowManager { workspace.swap_containers(current_idx, new_idx); workspace.focus_container(new_idx); - self.update_focused_workspace(true) + self.update_focused_workspace(self.mouse_follows_focus) } #[tracing::instrument(skip(self))] @@ -862,7 +868,7 @@ impl WindowManager { .ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?; workspace.focus_container(new_idx); - self.focused_window_mut()?.focus()?; + self.focused_window_mut()?.focus(self.mouse_follows_focus)?; Ok(()) } @@ -880,7 +886,7 @@ impl WindowManager { workspace.swap_containers(current_idx, new_idx); workspace.focus_container(new_idx); - self.update_focused_workspace(true) + self.update_focused_workspace(self.mouse_follows_focus) } #[tracing::instrument(skip(self))] @@ -902,7 +908,7 @@ impl WindowManager { container.focus_window(next_idx); container.load_focused_window(); - self.update_focused_workspace(true) + self.update_focused_workspace(self.mouse_follows_focus) } #[tracing::instrument(skip(self))] @@ -935,7 +941,7 @@ impl WindowManager { }; workspace.move_window_to_container(adjusted_new_index)?; - self.update_focused_workspace(true)?; + self.update_focused_workspace(self.mouse_follows_focus)?; } Ok(()) @@ -947,7 +953,7 @@ impl WindowManager { let workspace = self.focused_workspace_mut()?; workspace.promote_container()?; - self.update_focused_workspace(true) + self.update_focused_workspace(self.mouse_follows_focus) } #[tracing::instrument(skip(self))] @@ -961,7 +967,7 @@ impl WindowManager { let workspace = self.focused_workspace_mut()?; workspace.new_container_for_focused_window()?; - self.update_focused_workspace(true) + self.update_focused_workspace(self.mouse_follows_focus) } #[tracing::instrument(skip(self))] @@ -1009,7 +1015,7 @@ impl WindowManager { .ok_or_else(|| anyhow!("there is no floating window"))?; window.center(&work_area, &invisible_borders)?; - window.focus()?; + window.focus(self.mouse_follows_focus)?; Ok(()) } @@ -1143,7 +1149,7 @@ impl WindowManager { } workspace.set_layout(Layout::Default(layout)); - self.update_focused_workspace(true) + self.update_focused_workspace(self.mouse_follows_focus) } #[tracing::instrument(skip(self))] @@ -1169,7 +1175,7 @@ impl WindowManager { } workspace.set_layout(Layout::Custom(layout)); - self.update_focused_workspace(true) + self.update_focused_workspace(self.mouse_follows_focus) } #[tracing::instrument(skip(self))] @@ -1451,28 +1457,30 @@ impl WindowManager { pub fn focus_workspace(&mut self, idx: usize) -> Result<()> { tracing::info!("focusing workspace"); + let mouse_follows_focus = self.mouse_follows_focus; let monitor = self .focused_monitor_mut() .ok_or_else(|| anyhow!("there is no workspace"))?; monitor.focus_workspace(idx)?; - monitor.load_focused_workspace()?; + monitor.load_focused_workspace(mouse_follows_focus)?; - self.update_focused_workspace(true) + self.update_focused_workspace(mouse_follows_focus) } #[tracing::instrument(skip(self))] pub fn new_workspace(&mut self) -> Result<()> { tracing::info!("adding new workspace"); + let mouse_follows_focus = self.mouse_follows_focus; let monitor = self .focused_monitor_mut() .ok_or_else(|| anyhow!("there is no workspace"))?; monitor.focus_workspace(monitor.new_workspace_idx())?; - monitor.load_focused_workspace()?; + monitor.load_focused_workspace(mouse_follows_focus)?; - self.update_focused_workspace(true) + self.update_focused_workspace(self.mouse_follows_focus) } pub fn focused_container(&self) -> Result<&Container> { diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index d1b965f7..75ad8f7b 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -101,7 +101,7 @@ impl Workspace { } } - pub fn restore(&mut self) -> Result<()> { + pub fn restore(&mut self, mouse_follows_focus: bool) -> Result<()> { let idx = self.focused_container_idx(); let mut to_focus = None; for (i, container) in self.containers_mut().iter_mut().enumerate() { @@ -132,7 +132,7 @@ impl Workspace { // Maximised windows should always be drawn at the top of the Z order if let Some(window) = to_focus { if self.maximized_window().is_none() { - window.focus()?; + window.focus(mouse_follows_focus)?; } } diff --git a/komorebic.lib.sample.ahk b/komorebic.lib.sample.ahk index b0292154..dc732add 100644 --- a/komorebic.lib.sample.ahk +++ b/komorebic.lib.sample.ahk @@ -244,6 +244,10 @@ ToggleFocusFollowsMouse(implementation) { Run, komorebic.exe toggle-focus-follows-mouse --implementation %implementation%, , Hide } +ToggleMouseFollowsFocus() { + Run, komorebic.exe toggle-mouse-follows-focus, , Hide +} + AhkLibrary() { Run, komorebic.exe ahk-library, , Hide } \ No newline at end of file diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 9ca67b62..d3305791 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -499,6 +499,8 @@ enum SubCommand { /// Toggle focus follows mouse for the operating system #[clap(setting = AppSettings::ArgRequiredElseHelp)] ToggleFocusFollowsMouse(ToggleFocusFollowsMouse), + /// Toggle mouse follows focus on all workspaces + ToggleMouseFollowsFocus, /// Generate a library of AutoHotKey helper functions AhkLibrary, } @@ -919,6 +921,9 @@ fn main() -> Result<()> { SubCommand::Unsubscribe(arg) => { send_message(&*SocketMessage::RemoveSubscriber(arg.named_pipe).as_bytes()?)?; } + SubCommand::ToggleMouseFollowsFocus => { + send_message(&*SocketMessage::ToggleMouseFollowsFocus.as_bytes()?)?; + } } Ok(())