99 lines
3.2 KiB
Lua
99 lines
3.2 KiB
Lua
local M = {}
|
|
|
|
-- Configuration
|
|
local REQUIRED_REVERSALS = 3 -- Must switch direction 3 times (e.g., Left -> Right -> Left)
|
|
local RESET_TIMEOUT = 0.3 -- Fast window: shake must be completed rapidly or it resets
|
|
local LOCKOUT_DURATION = 3.0 -- Cooldown duration (seconds) before allowed to trigger again
|
|
|
|
-- Asymmetrical Thresholds
|
|
local SPEED_TO_ENABLE = 115.0 -- Slightly lower: easier to turn ON
|
|
local SPEED_TO_DISABLE = 150.0 -- Slightly higher: harder to accidentally turn OFF
|
|
|
|
-- Focusdim Global Hotkey Configuration from your settings
|
|
local FOCUS_DIM_MODS = { "ctrl", "alt", "shift", "cmd" }
|
|
local FOCUS_DIM_KEY = "f"
|
|
|
|
-- Internal state tracking
|
|
local jiggleWatcher = nil
|
|
local isLocked = false
|
|
local isFocusDimActive = false -- Tracks state to apply the correct threshold
|
|
local lastXDirection = 0 -- -1 for left, 1 for right, 0 for still
|
|
local reversalCount = 0
|
|
local lastEventTime = 0
|
|
|
|
-- Forward declaration for the timer loop
|
|
M.start = function() end
|
|
|
|
local function eventCallback(event)
|
|
if isLocked then return false end
|
|
|
|
local now = hs.timer.secondsSinceEpoch()
|
|
|
|
-- Reset the counter if the movements aren't happening fast enough
|
|
if (now - lastEventTime) > RESET_TIMEOUT then
|
|
reversalCount = 0
|
|
lastXDirection = 0
|
|
end
|
|
|
|
lastEventTime = now
|
|
|
|
-- Extract horizontal movement exclusively
|
|
local dx = event:getProperty(hs.eventtap.event.properties.mouseEventDeltaX)
|
|
|
|
-- Dynamically choose threshold based on current state
|
|
local currentThreshold = isFocusDimActive and SPEED_TO_DISABLE or SPEED_TO_ENABLE
|
|
|
|
-- Only evaluate if it's a high-velocity horizontal snap matching the current state's rule
|
|
if math.abs(dx) > currentThreshold then
|
|
local currentDirection = (dx > 0) and 1 or -1
|
|
|
|
-- Track direction flips
|
|
if lastXDirection ~= 0 and currentDirection ~= lastXDirection then
|
|
reversalCount = reversalCount + 1
|
|
|
|
-- Trigger only when the full rapid shake sequence completes
|
|
if reversalCount >= REQUIRED_REVERSALS then
|
|
isLocked = true
|
|
reversalCount = 0
|
|
lastXDirection = 0
|
|
|
|
-- Toggle our internal tracking state
|
|
isFocusDimActive = not isFocusDimActive
|
|
|
|
-- Instantly detach the listener
|
|
M.stop()
|
|
|
|
-- Simulate the hotkey press to toggle Focusdim
|
|
hs.eventtap.keyStroke(FOCUS_DIM_MODS, FOCUS_DIM_KEY, 0)
|
|
|
|
-- Re-enable after the lockout duration
|
|
hs.timer.doAfter(LOCKOUT_DURATION, function()
|
|
isLocked = false
|
|
M.start()
|
|
end)
|
|
|
|
return false
|
|
end
|
|
end
|
|
|
|
lastXDirection = currentDirection
|
|
end
|
|
|
|
return false -- Pass event through to macOS normally
|
|
end
|
|
|
|
function M.start()
|
|
if jiggleWatcher then return end
|
|
|
|
jiggleWatcher = hs.eventtap.new({ hs.eventtap.event.types.mouseMoved }, eventCallback)
|
|
jiggleWatcher:start()
|
|
end
|
|
|
|
function M.stop()
|
|
if jiggleWatcher then
|
|
jiggleWatcher:stop()
|
|
jiggleWatcher = nil
|
|
end
|
|
end
|
|
|
|
return M |