diff --git a/README.md b/README.md index bd53f0b..8d7c616 100644 --- a/README.md +++ b/README.md @@ -76,24 +76,20 @@ The script is safe to rerun but does not back up previous values or run automatically during `chezmoi apply`. Log out and back in if changes are not visible. -### Hover window focus +### Window focus -AutoRaise focuses and raises windows on hover. Its settings are managed in -`~/.config/AutoRaise/config`. - -After installing dependencies and applying the dotfiles, enable it at login: - -```bash -brew services start dimentium/autoraise/autoraise -``` - -Grant AutoRaise access in **System Settings → Privacy & Security → Accessibility**. -If it is not listed, use **+**, press **Cmd+Shift+G**, and enter the path printed by -`echo "$(brew --prefix autoraise)/bin/AutoRaise"`. Then restart it with -`brew services restart dimentium/autoraise/autoraise`. - -Hold **Control** to temporarily suspend hover focus. To turn it off entirely: +Hover focus is disabled in AeroSpace. Leave AutoRaise stopped so moving the +pointer does not focus or raise windows: ```bash brew services stop dimentium/autoraise/autoraise ``` + +Hammerspoon's `~/.hammerspoon/clickthrough.lua` focuses inactive windows on +mouse-down before passing through the original click, so the same click can +activate the clicked control. This is a workaround, not a native macOS setting; +some apps may still consume the first click. Modified clicks are left unchanged. +Use **Windows → Toggle single-click window activation** to disable it if an app +misbehaves. Hammerspoon needs Accessibility access and must be running. + +Optional AutoRaise settings remain in `~/.config/AutoRaise/config`. diff --git a/home/dot_config/aerospace/aerospace.toml b/home/dot_config/aerospace/aerospace.toml index d9d2709..964464a 100644 --- a/home/dot_config/aerospace/aerospace.toml +++ b/home/dot_config/aerospace/aerospace.toml @@ -9,7 +9,7 @@ default-root-container-layout = 'tiles' default-root-container-orientation = 'auto' enable-normalization-flatten-containers = true enable-normalization-opposite-orientation-for-nested-containers = true -focus-follows-mouse.enabled = true +focus-follows-mouse.enabled = false key-mapping.preset = 'qwerty' persistent-workspaces = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] diff --git a/home/dot_hammerspoon/clickthrough.lua b/home/dot_hammerspoon/clickthrough.lua new file mode 100644 index 0000000..99d4c8c --- /dev/null +++ b/home/dot_hammerspoon/clickthrough.lua @@ -0,0 +1,45 @@ +-- Focus on mouse-down, before macOS delivers the original click to the app. +-- Never synthesize an extra click: controls that accept first mouse must not fire twice. +local clickthrough = {} +local ax = require("hs.axuielement") +local log = hs.logger.new("clickthrough", "warning") + +local function focusClickedWindow(event) + -- Leave modified clicks (context menus, background links, etc.) unchanged. + if next(event:getFlags()) then return false end + local element = ax.systemWideElement():elementAtPosition(event:location()) + if not element then return false end + local role = element:attributeValue("AXRole") + if role == "AXMenu" or role == "AXMenuItem" or role == "AXMenuBarItem" + or role == "AXMenuBar" then return false end + + local windowElement = element:attributeValue("AXWindow") + if not windowElement and role == "AXWindow" then windowElement = element end + local window = windowElement and windowElement:asHSWindow() + if not window or not window:isStandard() then return false end + local focused = hs.window.focusedWindow() + if not focused or focused:id() ~= window:id() then window:focus() end + return false +end + +clickthrough.tap = hs.eventtap.new({hs.eventtap.event.types.leftMouseDown}, function(event) + local ok, err = pcall(focusClickedWindow, event) + if not ok then log.w(tostring(err)) end + return false -- Always pass through the original click, including on errors. +end) + +function clickthrough.toggle() + clickthrough.enabled = not clickthrough.enabled + if clickthrough.enabled then clickthrough.tap:start() else clickthrough.tap:stop() end +end + +clickthrough.enabled = true +clickthrough.tap:start() +-- macOS can disable event taps after sleep or a slow accessibility response. +clickthrough.watchdog = hs.timer.doEvery(5, function() + if clickthrough.enabled and not clickthrough.tap:isEnabled() then + clickthrough.tap:start() + end +end) + +return clickthrough diff --git a/home/dot_hammerspoon/init.lua b/home/dot_hammerspoon/init.lua index 0470689..4d47e73 100644 --- a/home/dot_hammerspoon/init.lua +++ b/home/dot_hammerspoon/init.lua @@ -1,5 +1,6 @@ -- Placement rules inferred from the running windows on this Mac. require("hs.ipc") +local clickthrough = require("clickthrough") local spaces = require("hs.spaces") local log = hs.logger.new("placement", "info") @@ -190,6 +191,7 @@ windowPlacement = { } windowPlacement.menu:setTitle("Windows") windowPlacement.menu:setMenu({ + {title = "Toggle single-click window activation", fn = clickthrough.toggle}, {title = "Reapply placement rules", fn = restore}, {title = "Reload configuration", fn = hs.reload}, {title = "Open Console", fn = hs.openConsole},