-- ~/.hammerspoon/Monocle.lua -- test gitfiles sync local Monocle = {} -- CONFIGURATION local blurRadius = 20 local dimAlpha = 0.3 local shakeTrigger = 250 -- Energy required (higher = harder shake) local overlay = nil local filter = nil local mouseTimer = nil local clickWatcher = nil local energy = 0 function Monocle.show() local screen = hs.screen.mainScreen() local res = screen:fullFrame() local win = hs.window.focusedWindow() if not win then return end if not overlay then overlay = hs.canvas.new(res) overlay:level(101) overlay:behavior({ hs.canvas.windowBehaviors.canJoinAllSpaces, hs.canvas.windowBehaviors.ignoresMouseEvents }) -- Layer 1: Background Dimming overlay[1] = { type = "rectangle", action = "fill", fillColor = { black = 1, alpha = dimAlpha }, frame = { x = 0, y = 0, w = "100%", h = "100%" }, } -- APPLY THE BLUR FILTER TO THE WHOLE CANVAS overlay:setFilter({ name = "CIGaussianBlur", inputRadius = blurRadius }) end overlay:frame(res) overlay:show() win:raise() end function Monocle.hide() if overlay then overlay:hide() end energy = 0 end function Monocle.toggle() if overlay and overlay:isShowing() then Monocle.hide() else Monocle.show() end end function Monocle.init() -- 1. HYPERKEY BINDING if hyper then hs.hotkey.bind(hyper, "f", function() Monocle.toggle() end) end -- 2. Focus Follower filter = hs.window.filter.new(nil) filter:subscribe(hs.window.filter.windowFocused, function() if overlay and overlay:isShowing() then Monocle.show() end end) -- 3. Click Outside to Disable clickWatcher = hs.eventtap.new({1}, function(event) if overlay and overlay:isShowing() then local clickPoint = event:location() local win = hs.window.focusedWindow() if win then local f = win:frame() local isInside = (clickPoint.x >= f.x and clickPoint.x <= (f.x + f.w) and clickPoint.y >= f.y and clickPoint.y <= (f.y + f.h)) if not isInside then Monocle.hide() end end end return false end):start() -- 4. Vector-Based Shake Detection local lastPos = hs.mouse.absolutePosition() local lastDx, lastDy = 0, 0 mouseTimer = hs.timer.doEvery(0.02, function() local newPos = hs.mouse.absolutePosition() local dx = newPos.x - lastPos.x local dy = newPos.y - lastPos.y -- If current direction (dx) is opposite of last direction (lastDx) -- That indicates a shake reversal. local reversedX = (dx * lastDx < 0) local reversedY = (dy * lastDy < 0) if reversedX or reversedY then local speed = math.sqrt(dx*dx + dy*dy) if speed > 15 then energy = energy + (speed * 2) end else -- Constant decay (15 units per cycle) energy = math.max(0, energy - 15) end if energy > shakeTrigger then Monocle.toggle() energy = 0 end lastDx, lastDy = dx, dy lastPos = newPos end) end return Monocle