Fixing window frame manipulation in Hammerspoon
🦔 🦔 🦔
I use Hammerspoon to automate all sorts of things on my Mac. I've been running into some annoying issues where trying to use :setFrame(...)
and the like on hs.window
objects would behave erratically.
Here's what's actually supposed to happen:
The fix
---
--- Monkeypatch for hs.window operations to temporarily
--- disable accessibility while moving/resizing windows
---
do
local axOnTimers = {}
local axOriginalState = {}
local windowMT = hs.getObjectMetatable("hs.window")
-- clean up when apps are closed
hs.window.axTimerWatcher = hs.application.watcher.new(function(name, event, app)
if event == hs.application.watcher.terminated then
local pid = app:pid()
if axOnTimers[pid] then
axOnTimers[pid]:stop()
end
axOnTimers[pid] = nil
axOriginalState[pid] = nil
end
end):start()
local function patch(fn)
return function(window, ...)
local app = window:application()
local pid = app:pid()
local ax = hs.axuielement.applicationElement(app)
-- disable accessibility, remembering what the original state was
pcall(function()
if not axOriginalState[pid] then
axOriginalState[pid] = {
AXEnhancedUserInterface = ax.AXEnhancedUserInterface,
AXManualAccessibility = ax.AXManualAccessibility
}
end
ax.AXEnhancedUserInterface = false
ax.AXManualAccessibility = false
end)
local ok, result = pcall(fn, window, ...)
-- restore accessibility after a short delay
axOnTimers[pid] = (
axOnTimers[pid]
or hs.timer.delayed.new(
math.max(0.2, hs.window.animationDuration or 0),
function()
local orig = axOriginalState[pid] or {}
pcall(function()
ax.AXEnhancedUserInterface = orig.AXEnhancedUserInterface
ax.AXManualAccessibility = orig.AXManualAccessibility
end)
axOriginalState[pid] = nil
end)):start()
if ok then
return result
else
error(result)
end
end
end
for _, key in ipairs({
"setFrame", "setFrameInScreenBounds",
"setFrameWithWorkarounds", "setTopLeft",
"setSize", "maximize", "move",
"moveToUnit", "moveToScreen",
"moveOneScreenEast", "moveOneScreenNorth",
"moveOneScreenSouth", "moveOneScreenWest",
}) do
windowMT[key] = patch(windowMT[key])
end
end
-- 🦔🦔🦔
This issue seems to be related to accessibility services – when they are enabled for a specific app, it may misbehave. The Hammerspoon team is tracking the problem as Bug #3224, but this appears to be an application-specific issue.
Unfortunately the application in question is Chrome and in the future, everything is Chrome.
The more you know, the more Chrome you start to notice.