Install: brew install itsdezen/tap/tili
Direct downloads are self-signed, not notarized; use right-click → Open. Signing details.
Fixed
- A macOS native fullscreen (a browser video, or the green button) no
longer yanks the display back to the previous Space and steals focus.
kAXWindowsAttribute— the AX attribute tili enumerates windows with —
only reports windows on the Space that's currently active, so entering a
fullscreen Space made every other window of that app vanish from its own
scan. tili read that as "closed", tore the still-open window out of its
workspace tree after the 100ms removal grace, and then real-focused a
sibling window — which activates that sibling's app and drops macOS
straight back out of fullscreen. Absence from a scan is no longer
evidence of a close: it now has to be confirmed by
_AXUIElementGetWindowon the window's own element, which answers
correctly across Spaces (and when theAXWindowsread itself times out). - A workspace no longer collapses to single-tile (
gaps.outer-solo)
geometry, with both windows stacked exactly on top of each other, after a
fullscreen round trip. Same root cause: the falsely-removed window left
the survivor alone in the tree, so it got the solo rect; the returning
window later got the same solo rect for itself. A quiet app isn't
rescanned until something makes it emit an AX notification, which is why
resizing a window by hand appeared to "fix" it. - A workspace's layout no longer silently reverts to its configured
layout=after a window leaves and rejoins the tree. Dropping to one
tiled window destroys the root container, and the next insert rebuilt it
from the config default — discarding alayout/layout --roottoggle
made at runtime. The root container's layout is now carried across that
collapse. Still reset by a config reload declaring a new default, and by
the workspace emptying out completely. - A macOS-disabled event tap no longer takes hotkeys (or
focus-follows-monitor) down for the rest of the session. macOS disables
an event tap outright if its callback overruns the tap timeout —
classically after a sleep/wake — and neither tap handled the disable
notification it gets, so the tap stayed dead until the daemon was
restarted. Both taps now reinstall themselves. The mouse tap also
synthesizes theLeftMouseUpa disable can swallow, which would
otherwise leave tili believing a drag was still in progress and skipping
every relayout from then on. tili fullscreen --nativecan now actually exit native fullscreen.
It was strictly one-way: entering worked, leaving never did. macOS reports
AXFullScreenand tili takes the window out of its workspace tree, so the
command's own "which window is focused" lookup — which only ever names
windows in the tree — could not name it. With that window alone in its
workspace the command failed withno window is focused; with a sibling
present it fullscreened the sibling instead, leaving two fullscreen
windows. The native branch now resolves its target from real OS focus.- Exiting a fullscreen video no longer leaves an unrelated window
focused and stacked on top of the browser. The throwaway window a
browser opens for a native-fullscreen video is destroyed a moment after
the exit, and tili reacted by raising whichever window happened to sit
next to it in the tree — over the browser macOS had just correctly
refocused. When the removed window's own app still has a window in that
workspace, macOS refocuses it itself, so tili no longer raises anything. - Closing a window while a natively-fullscreened window is showing no
longer pulls the user out of fullscreen.remove_placementskips its
reassigned-focus raise while the workspace holds a native-fullscreen
window: macOS is showing that window's own Space, so there is no sibling
on screen to hand focus to.
Full changelog: CHANGELOG.md