Last active
September 10, 2024 13:45
-
-
Save temocj/b0af557fce7449a21b1c217b2d074711 to your computer and use it in GitHub Desktop.
Hammerspoon script to mimic MS Windows behavior when clicking on un-focused windows.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Description: | |
-- Mimics Microsoft Windows click-through | |
-- behavior when changing focused window. | |
-- Installation: | |
-- Add anywhere to your Hammerspoon config. | |
-- Hammerspoon modules used: | |
-- hs.eventtap, hs.eventtap.event, hs.mouse, hs.timer, hs.window.filter | |
-- Compatibility: | |
-- Tested with Hammerspoon 0.9.90 and macOS 10.15.4 (Catalina) | |
-- Todo: | |
-- 1. Right- and middle-click? 2. Application opt-out filter. | |
-- License: | |
-- Public Domain | |
local evt <const> = hs.eventtap.event | |
-- Sequence overview: | |
-- 1a. MOUSE BUTTON PRESSED DOWN (or trackpad drag started) | |
-- 2. mouse down event | |
-- 3. window focus changed | |
-- 4. post virtual mouse down event (enables step 1b) | |
-- 5. script is unaffected by it, but target app receives the event | |
-- 6. virtual mouse up event occurs (HS bug) and we suppress it | |
-- 1b. MOUSE CAN BE DRAGGED AROUND to select text or whatnot | |
-- 1c. MOUSE BUTTON RELEASED (or trackpad drag ended) | |
-- 2. mouse up event | |
-- | |
-- 1. TRACKPAD TAPPED (briefly) | |
-- 2. mouse down event | |
-- 3. window focus changed | |
-- 4a. mouse up event (while we're still responding to step 3) | |
-- 4b. post virtual mouse down event | |
-- 5. script sees it and posts a virtual mouse up event | |
-- 6. script is unaffected, but target app receives both events (in the correct order hopefully) | |
local clickthroughInProgress | |
local virtualEventNumber -- need to keep track of it in order to work around HS bug | |
local function isVirtual (event) | |
return virtualEventNumber == event:getProperty(evt.properties.mouseEventNumber) | |
end | |
local function mouseDown (event) | |
-- check if virtual down-event occured AFTER corresponding up-event (HS bug) | |
if isVirtual(event) and not clickthroughInProgress then | |
-- post another up-event to get it unstuck | |
local cursorPos = hs.mouse.absolutePosition() | |
local mouseUpEvent = evt.newMouseEvent(evt.types.leftMouseUp, cursorPos) | |
mouseUpEvent:setProperty(evt.properties.mouseEventNumber, 1) | |
mouseUpEvent:post() | |
return false -- race condition here? (we want the up-event to occur last), but it always works for me | |
end | |
clickthroughInProgress = true | |
-- dirty fix for erratic behavior when dragging items between apps | |
-- makes windowFocusChanged inert after 0.1 seconds | |
hs.timer.doAfter(0.1, function() clickthroughInProgress = false end) | |
return false | |
end | |
local function mouseUp (event) | |
clickthroughInProgress = false | |
return isVirtual(event) -- ignore extraneous up-event (HS bug) | |
end | |
local function windowFocusChanged () | |
if clickthroughInProgress then | |
-- post another down-event so we can continue dragging (to select text or whatnot) | |
local cursorPos = hs.mouse.absolutePosition() | |
local mouseDownEvent = evt.newMouseEvent(evt.types.leftMouseDown, cursorPos) | |
virtualEventNumber = mouseDownEvent:getProperty(evt.properties.mouseEventNumber) | |
mouseDownEvent:post() -- causes extraneous up-event when dragging & switching window (HS bug) | |
end | |
end | |
listener1 = hs.eventtap.new({evt.types.leftMouseDown}, mouseDown):start() | |
listener2 = hs.eventtap.new({evt.types.leftMouseUp}, mouseUp):start() | |
-- unfortunately hs.window.filter may cause a bit of unresponsiveness during config load | |
hs.window.filter.default:subscribe(hs.window.filter.windowFocused, windowFocusChanged) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Known issues