# heed
# Focus follows mouse and directional focus for macOS, after Hyprland's follow_mouse and movefocus.
# Heed moves keyboard focus to the window under the pointer. It runs in the background with no Dock icon or main window; a menu bar icon and a global hotkey turn it on and off. macOS 14 or later, Apple Silicon.
install {
brew install --cask rbstp/tap/heed# Or from source. make cert once, and only without a Developer ID; it keeps the Accessibility grant across rebuilds.
make cert && make install# build, sign, install, start# On first launch, grant Heed access in System Settings › Privacy & Security › Accessibility. It picks the grant up without a restart. Every release is signed with a Developer ID, notarized, and carries a build provenance attestation next to it:
gh attestation verify Heed-0.12.3.zip --repo rbstp/heed}
binds {
# Heed registers hotkeys exclusively: a combination it claims is gone from every other app. These work whether or not focus following is on.
# Directional focus, off by default because every bind is taken from every other app. WASD rather than HJKL: the toggle is already on H. A window sharing a row or column with the focused one wins over a closer one that does not, and the edge is a dead end rather than a wrap.
# A hotkey needs at least one modifier other than Shift. If another app already registered it, Heed logs the refusal and registers nothing. An empty string disables a bind. The menu offers Command-Option with a warning and does not offer Command-Shift at all; defaults write accepts either.
defaults write io.github.rbstp.heed hotkey 'cmd+ctrl+alt+f' && make restart}
warp {
# Mouse follows focus. Focus moved by keyboard leaves the pointer over the window you just left, and the next flick of the mouse drags focus back there. Turn it around and the pointer follows focus into the new window: on Command-Tab, on the focus binds, on a window picked from Raycast's Switch Windows, and on any other keyboard-driven activation. Not for a click, mid-drag, into a window Heed would not focus by pointer, or when the pointer is already inside.
defaults write io.github.rbstp.heed warpPointer -bool true && make restart}
remote {
# Drive it from somewhere else. A URL reaches the running agent directly, and the same vocabulary works as flags on the installed binary, so Heed can be driven without registering anything.
open 'heed://focus/next'# also prev, left, right, up, downopen 'heed://focus/3'# numbers past 9 work here; heed://focus/id/<n> takes the window server's own numberopen 'heed://toggle'# also enable, disable~/Applications/Heed.app/Contents/MacOS/Heed --focus next~/Applications/Heed.app/Contents/MacOS/Heed --windows# the focus ring as JSON, whether or not Heed is running# With Raycast. Raycast already tiles and switches windows; Heed adds the pointer. The extension has a command for each of these, so the hotkey is Raycast's and Heed claims nothing. Or a Quicklink to heed://focus/next with a hotkey, then free the combination Heed holds:
defaults write io.github.rbstp.heed focusNextHotkey '' && make restart# The flags reach the agent over a distributed notification, which any process in your login session can post and read. Heed holds Accessibility permission, so anything in your session can switch it off or move focus around while it runs. The vocabulary is fixed at toggle-and-focus: no arbitrary arguments, no window contents, nothing read back. If that is not a trade you want, leave the commands alone and use the binds; nothing else in Heed listens.
}
# Settings live in the io.github.rbstp.heed defaults domain, flat: defaults write io.github.rbstp.heed <key> <value>, then restart Heed. The blocks below group them for reading; the values are the defaults.
general {
}
pointer {
}
guards {
# Heed follows the pointer but avoids the common focus fights. A window that appears under a still pointer does not take focus; dialogs and prompts keep theirs; focus that arrived by shortcut, launch, menu or Command-Tab stays put until the pointer rests on another window, and crossing the menu bar, Dock or empty space on the way does not release it. Pointer focus is also suppressed while the window numbers are up.
}
exclude {
# Heed always excludes itself, Dock, WindowServer, loginwindow, Control Center, Notification Center, SystemUIServer, the screenshot UI, Spotlight, Raycast and AltTab.
defaults write io.github.rbstp.heed excludedWindowTitles -array '^Picture in Picture$'defaults write io.github.rbstp.heed excludedBundleIDs -array com.example.Overlay}
troubleshooting {
# See what Heed sees under the pointer, or at a point, and log each decision. The log is append-only; make logs-clear truncates it.
make probe# or make probe X=960 Y=540defaults write io.github.rbstp.heed verbose -bool true && make restart && make logs# Accessibility stopped working after an upgrade. Releases from 0.12.0 on are signed with a Developer ID and notarized, so the grant carries across versions. Upgrading from an older, ad-hoc signed build is the one exception: that grant was tied to the old binary and cannot transfer. If Heed stays dimmed:
tccutil reset Accessibility io.github.rbstp.heed}
# Limitations. Focusing another application raises it. Apps with incomplete Accessibility support may only work at application level: some games, XQuartz and Java applications expose no individual windows, so the pointer focuses them as a whole and the focus binds skip them. Stage Manager may override window ordering.
# Prior art. AutoRaise does the same in C++. yabai and AeroSpace include related features in larger window managers.