Initial commit of Hammerspoon config
This commit is contained in:
+129
@@ -0,0 +1,129 @@
|
||||
-- ~/.hammerspoon/Monocle.lua
|
||||
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
|
||||
Reference in New Issue
Block a user