From 4b1d6808c93bfa096ed12d795445136808e93f80 Mon Sep 17 00:00:00 2001 From: Franco Pellicciotti Date: Sat, 16 May 2026 03:18:30 -0400 Subject: [PATCH] Added MouseJiggle Module --- modules/mouseJiggle.lua | 99 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 modules/mouseJiggle.lua diff --git a/modules/mouseJiggle.lua b/modules/mouseJiggle.lua new file mode 100644 index 0000000..7a931df --- /dev/null +++ b/modules/mouseJiggle.lua @@ -0,0 +1,99 @@ +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 \ No newline at end of file