Execute window actions when the window id is unavailable#1788
Execute window actions when the window id is unavailable#1788johncarmack1984 wants to merge 2 commits into
Conversation
After a session transition (waking to the login window, or an idle-induced logout), macOS can stop vending window ids and window lists to accessibility clients until the screen is locked and unlocked again (rxhanson#640, rxhanson#1244, rxhanson#1325, rxhanson#1673). Rectangle treated the window id as required for every action, so keyboard, menu, and URL actions beeped and did nothing, and drag to snap went dead, even though the per-app accessibility connection still worked. The window id is only used for history bookkeeping (restore rects, repeated-action cycling, todo and stage checks). Make it optional: actions now execute without it and skip only the id-keyed bookkeeping. Restore still requires an id since it is entirely history-driven. Also add a last-resort window-under-cursor fallback that hit-tests the frontmost app's own accessibility windows, since both existing lookups (the window list and the system-wide hit test) depend on window server info that is unavailable in this state.
|
Thanks! Looks good to me. What are your thoughts on some form of temporary/alternate bookkeeping on windows without ids? Perhaps we could hash the window attributes from the accessibility API to come up with a "best guess" unique identifier and still maintain some notion of the existing bookkeeping. I don't mind merging this without it, but figured I'd see if it was something you'd be interested in exploring and potentially making a change for. |
When both real sources fail (the broken session state in rxhanson#640), derive a bookkeeping-only id from CFHash of the window's AXUIElement, which is stable for the same window across fetches and unaffected by moves or resizes. The high bit keeps derived ids out of the real window id space. Restores cycling, restore rects, and unsnap history in the broken state.
|
Turns out to be pretty feasible! Pushed. The wrinkle with hashing attributes: the obvious ones are exactly what churns. We mutate the frame ourselves, and titles change on their own (a browser retitles on every tab switch), so that hash dies mid-cycle. What holds is the element's own identity. Separate fetches of the same window come back CFEqual with the same CFHash, same across retrieval paths, distinct across sibling windows, and the hash tracks the element rather than its attributes, so our own moves don't churn it. So the new commit derives the stand-in from CFHash of the window element, high bit set to keep it out of the real id space, and it only fires when both real sources fail. Cycling, restore, and unsnap history come back in the broken state. About 15 lines plus tests. |
The failure in #640 (also #1244, #1325, #1673): after waking to the login window or an idle logout, every action beeps and nothing moves until you lock and unlock the screen. Restarting Rectangle doesn't clear it. Rebooting does.
The logs in those threads show what macOS is actually withholding. "Unable to obtain window id" on commands means the focused window element was retrieved fine and both id sources then failed:
_AXUIElementGetWindow, and the window list frame+pid fallback. The hit-test failure on drags meansCGWindowListCopyWindowInfoand the system-wide element are dead too. Per-app accessibility keeps working the whole time, which also matches Magnet, BetterTouchTool, and HyperSwitch breaking simultaneously: the window server stops vending cross-app window info until a lock/unlock resets the session. I don't think Rectangle can clear that state. It can stop needing the window id to act. The id only feeds history bookkeeping (restore rects, cycling, todo and stage checks), butexecute()requires it in the same guard as the window element, so a bookkeeping miss fails the whole action with a beep, and the drag path bails the same way.With this change the id is optional. Actions run without it and skip the id-keyed bookkeeping: cycling restarts from the first step, no restore rect gets recorded. Restore alone still requires it and beeps, since it's pure history. The drag path tolerates a missing id, and window-under-cursor gets a last-resort fallback that hit-tests the frontmost app's own AX windows, since both existing lookups depend on window server info.
Verified by stubbing the two failing calls (
_AXUIElementGetWindowerroring,CGWindowListCopyWindowInfoempty) and drivingrectangle://execute-action?name=left-halfat a real TextEdit window: on main nothing moves, on this branch it snaps, and with the stubs removed behavior is unchanged and the tests pass. There's a new test pinning that calculated geometry never depends on the id. The one thing I can't confirm on a healthy machine is the drag fallback under the real condition. If someone from #640 or #1673 can run this branch after the next occurrence, that settles it.Multi-window actions and todo reflow still need the real window list, so they stay degraded in the broken state. Left alone on purpose.