Focusing workspaces quickly moves windows between workspaces
I have Windows+Shift+1 bound to focus workspace 1 and Windows+Shift+2 to focus workspace 2. If I hold, Windows+Shift and spam 1 and 2 as quickly as possible, windows in workspace 2 will move to workspace 1 or vice-versa. While this procedure is extreme to reproduce the bug, this bug has happened to me during legitimate usage too.
I had the same issue when I was using Alt+1 and Alt+2 for my workspace keybinds so the bug is not specific to the key binds.
Are you running komorebi with window-hiding-behaviour set to hide or minimize? I know that hide can occasionally result in the sort of behaviour that you are describing but I haven't experienced this with minimize (yet).
hide is still the default as I'm reluctant to make a major version bump just for this one change, but I think that in the future this entire option should just be removed and the behaviour should default to minimize.
window-hiding-behaviour is set to minimize. I am using v0.1.9 which appears to default to minimize. When I switch workspaces, the windows for other workspaces are minimized - not hidden.
I went ahead an attached a video reproducing this behavior. I have a notepad in workspace 4 and another notepad in workspace 5 and I am simply focusing workspaces 4 and 5 rapidly. I know the bug appears extreme but I encounter this bug when for example, I focus workspace 2 and then immediately change my mind, to focus workspace 3.
I believe this bug occurs because of this timeline:
- I focus workspace 5 from workspace 4. Notepad 5 is shown and notepad 4 is in the middle of being minimized.
- I focus back to workspace 4 before notepad 4 is minimized and thus interrupted. Now komorebi believes that both notepad 4 and notepad 5 belong to workspace 5. After focusing workspace 4, all windows are minimized.
- I focus workspace 5 and komobrei shows both notepad 4 and 5.
So just guessing (I didn't look at any code), I think this bug is a consequence of the slowness from the win32 windows api. I imagine fixing this would involve a painful architecture change, to use an internal tree of windows rather relying on the win32 state of minimized/shown windows.
Looks like I've been working on this so long that I no longer know what I'm talking about. 😅
I think that this is indeed most likely due to a timing issue like you have mentioned between the application's internal state and the events being emitted by the Windows API.
It should be possible to wrap calls that update the currently focused workspace with a debouncer which can be personalized by the user with a CLI command. I will look into this when I have some free time over the next few weeks.
Dumping some logs here from the moment when I was able to trigger this on my machine:
2022-06-06T22:00:06.674828Z INFO read_commands:process_command{FocusWorkspaceNumber(0)}:focus_monitor{idx=0}: komorebi::window_manager: focusing monitor
2022-06-06T22:00:06.675438Z INFO read_commands:process_command{FocusWorkspaceNumber(0)}:focus_workspace{idx=0}: komorebi::window_manager: focusing workspace
2022-06-06T22:00:06.675873Z INFO read_commands:process_command{FocusWorkspaceNumber(0)}:focus_workspace{idx=0}:focus_workspace{idx=0}: komorebi::monitor: focusing workspace
2022-06-06T22:00:06.689844Z INFO read_commands:process_command{FocusWorkspaceNumber(0)}:focus_workspace{idx=0}:update_focused_workspace{follow_focus=true}: komorebi::window_manager: updating
2022-06-06T22:00:06.691534Z INFO read_commands:process_command{FocusWorkspaceNumber(0)}: komorebi::process_command: processed
2022-06-06T22:00:06.711443Z INFO process_event{event=FocusChange(SystemForeground, Window { hwnd: 722816 })}:focus_monitor{idx=0}: komorebi::window_manager: focusing monitor
2022-06-06T22:00:06.712118Z INFO process_event{event=FocusChange(SystemForeground, Window { hwnd: 722816 })}:focus_window{idx=0}: komorebi::container: focusing window
2022-06-06T22:00:06.712507Z INFO process_event{event=FocusChange(SystemForeground, Window { hwnd: 722816 })}:focus_container{idx=0}: komorebi::workspace: focusing container
2022-06-06T22:00:06.713615Z INFO process_event{event=FocusChange(SystemForeground, Window { hwnd: 722816 })}: komorebi::process_event: processed: (hwnd: 722816, title: komorebi – process_event.rs, exe: idea64.exe, class: SunAwtFrame)
2022-06-06T22:00:06.720491Z INFO read_commands:process_command{FocusWorkspaceNumber(1)}:focus_monitor{idx=0}: komorebi::window_manager: focusing monitor
2022-06-06T22:00:06.720911Z INFO read_commands:process_command{FocusWorkspaceNumber(1)}:focus_workspace{idx=1}: komorebi::window_manager: focusing workspace
2022-06-06T22:00:06.721240Z INFO read_commands:process_command{FocusWorkspaceNumber(1)}:focus_workspace{idx=1}:focus_workspace{idx=1}: komorebi::monitor: focusing workspace
2022-06-06T22:00:06.750470Z INFO read_commands:process_command{FocusWorkspaceNumber(1)}:focus_workspace{idx=1}:update_focused_workspace{follow_focus=true}: komorebi::window_manager: updating
2022-06-06T22:00:06.755325Z INFO read_commands:process_command{FocusWorkspaceNumber(1)}: komorebi::process_command: processed
2022-06-06T22:00:06.756630Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 722816 })}:focus_monitor{idx=0}: komorebi::window_manager: focusing monitor
2022-06-06T22:00:06.757139Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 722816 })}:focus_monitor{idx=0}: komorebi::window_manager: focusing monitor
2022-06-06T22:00:06.757501Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 722816 })}:focus_workspace{idx=0}: komorebi::window_manager: focusing workspace
2022-06-06T22:00:06.757863Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 722816 })}:focus_workspace{idx=0}:focus_workspace{idx=0}: komorebi::monitor: focusing workspace
2022-06-06T22:00:06.775542Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 722816 })}:focus_workspace{idx=0}:update_focused_workspace{follow_focus=true}: komorebi::window_manager: updating
2022-06-06T22:00:06.777997Z INFO process_event{event=Minimize(SystemMinimizeStart, Window { hwnd: 395786 })}: komorebi::process_event: processed: (hwnd: 395786, title: PowerShell, exe: WindowsTerminal.exe, class: CASCADIA_HOSTING_WINDOW_CLASS)
2022-06-06T22:00:06.778830Z INFO process_event{event=Minimize(SystemMinimizeStart, Window { hwnd: 722816 })}:update_focused_workspace{follow_focus=false}: komorebi::window_manager: updating
2022-06-06T22:00:06.780247Z INFO process_event{event=Minimize(SystemMinimizeStart, Window { hwnd: 722816 })}: komorebi::process_event: processed: (hwnd: 722816, title: komorebi – process_event.rs, exe: idea64.exe, class: SunAwtFrame)
2022-06-06T22:00:06.780978Z INFO process_event{event=FocusChange(SystemForeground, Window { hwnd: 395786 })}:focus_monitor{idx=0}: komorebi::window_manager: focusing monitor
2022-06-06T22:00:06.783281Z ERROR komorebi::process_event: there is no container/window
2022-06-06T22:00:06.789206Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 395786 })}:focus_monitor{idx=0}: komorebi::window_manager: focusing monitor
2022-06-06T22:00:06.789838Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 395786 })}:focus_monitor{idx=0}: komorebi::window_manager: focusing monitor
2022-06-06T22:00:06.790275Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 395786 })}:focus_workspace{idx=1}: komorebi::window_manager: focusing workspace
2022-06-06T22:00:06.790603Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 395786 })}:focus_workspace{idx=1}:focus_workspace{idx=1}: komorebi::monitor: focusing workspace
2022-06-06T22:00:06.811352Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 395786 })}:focus_workspace{idx=1}:update_focused_workspace{follow_focus=true}: komorebi::window_manager: updating
2022-06-06T22:00:06.815243Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 722816 })}:focus_monitor{idx=0}: komorebi::window_manager: focusing monitor
2022-06-06T22:00:06.815791Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 722816 })}:focus_window{idx=0}: komorebi::container: focusing window
2022-06-06T22:00:06.816159Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 722816 })}:focus_container{idx=1}: komorebi::workspace: focusing container
2022-06-06T22:00:06.816507Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 722816 })}:update_focused_workspace{follow_focus=false}: komorebi::window_manager: updating
2022-06-06T22:00:06.826723Z INFO process_event{event=Show(SystemMinimizeEnd, Window { hwnd: 722816 })}: komorebi::process_event: processed: (hwnd: 722816, title: komorebi – process_event.rs, exe: idea64.exe, class: SunAwtFrame)
It looks like SystemMinimizeEnd (associated with the received command FocusWorkspaceNumber(0)) fires after FocusWorkspaceNumber(1) has been received, which results in this window restoration happening on workspace 1 instead of workspace 0.
I tried blocking the hide and restore calls until it's possible to confirm via another API call that the window has indeed been minimized or restored, but I am still able to reproduce this behaviour:
pub fn hide(self) {
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
if !programmatically_hidden_hwnds.contains(&self.hwnd) {
programmatically_hidden_hwnds.push(self.hwnd);
}
let hiding_behaviour = HIDING_BEHAVIOUR.lock();
match *hiding_behaviour {
HidingBehaviour::Hide => WindowsApi::hide_window(self.hwnd()),
HidingBehaviour::Minimize => WindowsApi::minimize_window(self.hwnd()),
}
while !WindowsApi::is_iconic(self.hwnd()) {}
}
pub fn restore(self) {
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
if let Some(idx) = programmatically_hidden_hwnds
.iter()
.position(|&hwnd| hwnd == self.hwnd)
{
programmatically_hidden_hwnds.remove(idx);
}
WindowsApi::restore_window(self.hwnd());
while !WindowsApi::is_window_visible(self.hwnd()) {}
}
@tgharib Check out this commit, just set window-hiding-behaviour to cloak: https://github.com/LGUG2Z/komorebi/commit/80c98596dd2cb4e28666e49c753ffc7e5137e09e
Sweet, I cannot reproduce this bug when window-hiding-behaviour is cloak. It is still reproducible with minimize though.
Ultimately I think I will keep the hide and minimize options around to avoid breaking peoples' configurations, but for future releases I will set the default to cloak and encourage its use as this is the same (hidden) mechanism used by Microsoft when switching between virtual desktops and naturally it avoids entire classes of bugs like this one.