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.