mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-03-26 19:31:16 +01:00
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
This commit is contained in:
31
README.md
31
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
|
||||
|
||||
@@ -93,6 +93,7 @@ pub enum SocketMessage {
|
||||
Query(StateQuery),
|
||||
FocusFollowsMouse(FocusFollowsMouseImplementation, bool),
|
||||
ToggleFocusFollowsMouse(FocusFollowsMouseImplementation),
|
||||
ToggleMouseFollowsFocus,
|
||||
AddSubscriber(String),
|
||||
RemoveSubscriber(String),
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -50,6 +50,7 @@ pub struct WindowManager {
|
||||
pub invisible_borders: Rect,
|
||||
pub work_area_offset: Option<Rect>,
|
||||
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
|
||||
pub mouse_follows_focus: bool,
|
||||
pub hotwatch: Hotwatch,
|
||||
pub virtual_desktop_id: Option<usize>,
|
||||
pub has_pending_raise_op: bool,
|
||||
@@ -63,6 +64,7 @@ pub struct State {
|
||||
pub invisible_borders: Rect,
|
||||
pub work_area_offset: Option<Rect>,
|
||||
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
|
||||
pub mouse_follows_focus: bool,
|
||||
pub has_pending_raise_op: bool,
|
||||
pub float_identifiers: Vec<String>,
|
||||
pub manage_identifiers: Vec<String>,
|
||||
@@ -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> {
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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(())
|
||||
|
||||
Reference in New Issue
Block a user