fix(window-restore): re-read workspace/window state after the wofi pick
The picker blocks on user input for however long they take. In that window niri can destroy a dynamic workspace (its last window closed) and renumber every later one down, so the pre-pick current["idx"] can point at the wrong workspace by the time move-window-to-workspace runs — restoring to the wrong place. The stashed window itself can also have closed in the meantime, in which case the move/focus calls would silently no-op. main() now re-reads workspaces/windows after pick() returns, recomputes the current workspace, and confirms the chosen window still exists before acting; each bails with a notify and the documented exit code otherwise. Adds window_exists() as a pure, tested helper alongside the existing pure functions. Also: clarify the focus=false comment in niri/config.kdl (it read as if focus=false caused the problem it actually prevents), and note in the design doc's Out of scope section that the stash's index-1 pinning is per-output, so a second monitor would need its own analysis of the Mod+1..9 offset.
This commit is contained in:
@@ -50,6 +50,13 @@ def resolve(windows, chosen):
|
||||
return next((w for w in windows if label(w) == chosen), None)
|
||||
|
||||
|
||||
def window_exists(windows, window_id):
|
||||
"""Whether window_id is still present in a fresh `windows` list. Used after
|
||||
the picker returns, since wofi can block long enough for the chosen window
|
||||
to have closed out from under us."""
|
||||
return any(w.get("id") == window_id for w in windows)
|
||||
|
||||
|
||||
def pick(labels, chooser):
|
||||
"""chooser(text) -> selected line. Injected so tests need no wofi."""
|
||||
return chooser("\n".join(labels))
|
||||
@@ -140,6 +147,30 @@ def main():
|
||||
if window is None:
|
||||
return 0
|
||||
|
||||
# wofi may have sat open for however long the user took. In that time niri
|
||||
# can destroy a dynamic workspace (its last window closed) and renumber
|
||||
# every later one down, so the pre-pick `current["idx"]` may now name the
|
||||
# wrong workspace; the stashed window may also have closed. Re-read state
|
||||
# rather than trust anything captured before pick() blocked.
|
||||
workspaces = niri_json("workspaces")
|
||||
if workspaces is None:
|
||||
notify("niri IPC unavailable")
|
||||
return 1
|
||||
|
||||
current = current_workspace(workspaces)
|
||||
if current is None:
|
||||
notify("no focused workspace")
|
||||
return 1
|
||||
|
||||
windows = niri_json("windows")
|
||||
if windows is None:
|
||||
notify("niri IPC unavailable")
|
||||
return 1
|
||||
|
||||
if not window_exists(windows, window["id"]):
|
||||
notify("that window closed before it could be restored")
|
||||
return 0
|
||||
|
||||
# The window is not focused (it is in the stash), so --focus does not apply;
|
||||
# focus it explicitly afterwards.
|
||||
niri_action("move-window-to-workspace", "--window-id", str(window["id"]),
|
||||
|
||||
Reference in New Issue
Block a user