Skip to content

Instantly share code, notes, and snippets.

@luckman212
Created December 14, 2021 15:06
Show Gist options
  • Select an option

  • Save luckman212/615ad90c3ac4fce2460fc22505671a26 to your computer and use it in GitHub Desktop.

Select an option

Save luckman212/615ad90c3ac4fce2460fc22505671a26 to your computer and use it in GitHub Desktop.
Hammerspoon module for draggable live updating floating preview windows
--[[ Live-updating floating previews
https://www.hammerspoon.org/docs/hs.window.html
https://www.hammerspoon.org/docs/hs.canvas.html
https://www.hammerspoon.org/docs/hs.image.html
https://www.hammerspoon.org/docs/hs.mouse.html
https://www.hammerspoon.org/docs/hs.timer.html
https://www.lua.org/manual/5.4/manual.html#6.2 (coroutines)
https://github.com/Hammerspoon/hammerspoon/issues/2710#issuecomment-788266990 (thanks @asmagill)
]]--
-- user-settable prefs
local main_key = 'f13' -- PrtScr
local refreshIntervals = { 2, 1, 0.5, 0.25 } -- 0.10, 0.05 removed due to bad perf on 4k
local sizes = { 400, 300, 200, 100, 50, 25 }
local sizeIndex = 2 -- initial size of floater
local animateBorderDraw = true
local reactivateWhenClosing = true -- bring app back to foreground when stopped
local notifyOnClose = true -- notify if monitored window closes unexpectedly
local soundEnabled = true
local invertScrollDirection = true
local moveOffscreen = true -- push main app window offscreen upon activation
local hiliteCaptureArea = false -- for debugging source win capture area
local captureRectColor = { red=0, green=1, blue=0, alpha=1 }
local lineColor = { red=1, green=0, blue=0, alpha=1 }
-- scale
local scale = hs.screen.primaryScreen():currentMode().scale
-- sounds
local spath=(os.getenv("HOME") .. "/path/to/sounds/dir/")
local snd_on=hs.sound.getByFile(spath .. "filename-of-on-sound.aif"):device(nil):volume(0.25)
local snd_off=hs.sound.getByFile(spath .. "filename-of-off-sound.aif"):device(nil):volume(0.50)
local snd_size=hs.sound.getByFile(spath .. "filename-of-tick-sound.aif"):device(nil):volume(0.50)
-- use hs.styledtext.fontNames() to list available fonts
local textFontSize = 20
local textPadding = 4.0
local fTextStyle = {
font = { name="SFProText-Medium", size=textFontSize },
color = { red=1, green=1, blue=1, alpha=0.9 },
paragraphStyle = {
alignment="left",
lineBreak="clip",
maximumLineHeight=textFontSize + 2
},
shadow = {
offset = { h=-1, w=-2 },
color = { red=0, green=0, blue=0, alpha=0.7 }
}
}
-- globals
local _cMouseAction
local floater
local w -- current hs.window object to capture from
local i -- windowID of w
local e -- elapsed time between snapshots
local app -- stores hs.application object that owns w
local appname -- app:name()
local capture_origin = {}
local capture_center = {}
local floater_origin = {}
local win_frame = {}
local winPosCache = {}
local canvasSize = {}
local patterns = {}
local _lastClickPos = {}
local _lastClickTime = hs.timer.absoluteTime()
local doubleClickTime = 250000000 -- nanoseconds (0.25s)
local refreshInt = refreshIntervals[1]
local loopCount = 0
local failCount = 0
local refreshFailCount = 0
local hasBeenMovedOffscreen = false
local hkTable = {} -- hotkeys table
local timerTable = {}
local screenSleep = false
local screenSaver = false
local screenLock = false
local _updatesPaused = false
local _screenOff = false
function get_size(incr)
idx = sizeIndex or 0
max = math.min(win_frame.h,win_frame.h)
if incr then idx = idx+1 end
if idx > #sizes then idx = 1 end
for i=idx, #sizes do
if sizes[i] < max then
sizeIndex = i
break
end
sizeIndex = 1
end
return sizes[sizeIndex]
end
function displayRefreshInt()
if canvasSize.w >= 100 then
txt = hs.styledtext.new(refreshInt..'s', fTextStyle)
removeOverlays('interval')
floater:insertElement({
id = 'interval',
type = 'text',
text = txt,
padding = textPadding,
action = 'stroke',
frame = { x="0%", y="0%", h="100%", w="100%" },
antialias = true
})
end
destroyTimers('refreshInt')
timerTable['refreshInt'] = hs.timer.doAfter(1.5, function() removeOverlays('interval') end)
end
function cycleRefreshInt()
for c,int in ipairs(refreshIntervals) do
if refreshInt == int then
refreshInt = (refreshIntervals[c+1] or refreshIntervals[1])
break
end
end
startMainTimer()
_stopCoroutine('cycle refreshInt')
resumeUpdates(false)
end
function cycleRefreshIntReverse()
for c,int in ipairs(refreshIntervals) do
if refreshInt == int then
refreshInt = (refreshIntervals[c-1] or refreshIntervals[1])
break
end
end
startMainTimer()
_stopCoroutine('cycle refreshInt')
resumeUpdates(false)
end
function resetSrcWindow()
w,i = nil
app,appname = nil
canvasSize = {}
end
function destroyCanvasObj(cObj,gc)
if not cObj or hstype(cObj) ~= 'hs.canvas' then return end
-- explicit :delete() is deprecated, use gc
-- see https://github.com/Hammerspoon/hammerspoon/issues/3021
-- cObj:delete(delay or 0)
for i=#cObj,1,-1 do
cObj[i] = nil
end
cObj:clickActivating(false)
cObj:mouseCallback(nil)
cObj:canvasMouseEvents(nil, nil, nil, nil)
cObj = nil
if gc and gc == true then collectgarbage() end
end
function close_floater(flag,log,delay,reason)
if log then print('closing floater: '..(reason or 'unknown')) end
_stopCoroutine('called by close_floater')
destroyTimers({'animGuardTimer','strokeDashPatternTimer'})
destroyCanvasObj(floater,false)
destroyCanvasObj(captureHilite,true)
-- floater = nil
-- captureHilite = nil
destroyHotkeys({'hk_destroy','hk_speed','hk_togglewin'})
if flag then
playSound(snd_off,soundEnabled)
destroyTimers('main')
moveWinOffscreen(false)
resetSrcWindow()
end
failCount = 0
_updatesPaused = false
collectgarbage()
end
function point_near(p1,p2,d)
local delta = math.abs(p1.x - p2.x) + math.abs(p1.y - p2.y)
return (delta <= d)
end
function checkForDoubleClick(ts,pos)
if ts - _lastClickTime < doubleClickTime then
if point_near(pos, _lastClickPos, 2*scale) then
return true
end
end
_lastClickPos = pos
_lastClickTime = ts
return false
end
function startGuardTimer()
destroyTimers('guardTimer')
timerTable['guardTimer'] = hs.timer.doWhile(
function() return w ~= nil end,
function() srcWinExists(w) end,
1)
end
function startMainTimer()
destroyTimers('main')
timerTable['main'] = hs.timer.doEvery(refreshInt, function() refreshWin(i) end)
displayRefreshInt()
startGuardTimer()
end
function dumpWin(w)
print('wID:',w:id())
print('min?',w:isMinimized())
print('vis?',w:isVisible())
print('axui:',hs.inspect(hs.axuielement.windowElement(w):isValid()))
print('_updatesPaused:',_updatesPaused)
print('_screenOff:',_screenOff)
end
function srcWinExists(w)
-- dumpWin(w)
if _updatesPaused or _screenOff then return true end
if w and hs.axuielement.windowElement(w):isValid() then
return true
else
w = nil
if notifyOnClose then
msg = 'The window that was being tracked from '..appname..' has closed.'
hs.notify.show('Underlying window closed','',msg)
-- print(msg)
end
close_floater(true, true, 0, 'unexpected close')
return false
end
end
function valid_capture_pos(p,max)
if p < 0 then return 0 end
if p > max then return to_int(max) end
return to_int(p)
end
function get_relative_mouse(mouse,frame)
local left = frame.x
local right = frame.x + frame.w
local top = frame.y
local bottom = frame.y + frame.h
local rmx = mouse.x - left
local rmy = mouse.y - top
if rmx < 0 then rmx = 0 end
if rmy < 0 then rmy = 0 end
if mouse.x > right then rmx = right end
if mouse.y > bottom then rmy = bottom end
return { x=to_int(rmx), y=to_int(rmy) }
end
function valid_origin(o,min,max)
local max_adj = min + max
if o < min then return to_int(min) end
if o > max_adj then return to_int(max_adj) end
return to_int(o)
end
function _updateCaptureOrigin(new_origin,offset)
capture_origin = {
x = new_origin.x,
y = new_origin.y
}
capture_center = {
x = capture_origin.x + offset.x,
y = capture_origin.y + offset.y
}
end
function max_x_pos()
-- hs.inspect(hs.screen.screenPositions())
local max_x = 0
for _,scr in ipairs(hs.screen.allScreens()) do
scrWidth = scr:currentMode().w
scrPos = { scr:position() }
scrX = scrWidth + scrPos[1]
if scrX > max_x then max_x = scrX end
end
return (to_int(max_x) or 0)
end
function mouseHasMoved(old,new)
if (type(old) ~= 'table') or (type(new) ~= 'table') then return true end
if old == new then return false end
if to_int(old.x) ~= to_int(new.x) then return true end
if to_int(old.y) ~= to_int(new.y) then return true end
return false
end
function showPausedText()
if canvasElementExists('pausedrect') then return end
if canvasSize.w and canvasSize.w >= 100 then
txt = hs.styledtext.new('PAUSED', fTextStyle)
else
txt = hs.styledtext.new('X', fTextStyle)
end
floater:appendElements({
id = 'pausedrect',
type = 'rectangle',
action = 'fill',
fillColor = { red=0, green=0, blue=0, alpha=0.5 },
antialias = false,
frame = { x = "0%", y = "0%", h = "100%", w = "100%" },
trackMouseDown = true,
trackMouseUp = true
},
{
id = 'pausedtxt',
type = 'text',
text = txt,
padding = textPadding,
action = 'stroke',
frame = { x="0%", y="0%", h="100%", w="100%" },
antialias = true,
trackMouseDown = true,
trackMouseUp = true
})
end
function pauseUpdates(bTriggeredByScreenEvent)
if bTriggeredByScreenEvent then _screenOff = true end
if not floater then return end
if not _updatesPaused then
_updatesPaused = true
-- print('_updatesPaused: ',_updatesPaused)
-- print('_screenOff: ',_screenOff)
showPausedText()
end
end
function resumeUpdates(bTriggeredByScreenEvent)
_screenOff = false
if not floater then return end
if _updatesPaused then
_updatesPaused = false
removeOverlays({'pausedrect','pausedtxt'})
end
end
function refreshWin(i,force)
if not floater then return end
if _screenOff then return end
if not w then return end
-- dumpWin(w)
if w:isVisible() then
if _updatesPaused then
resumeUpdates(false)
if force then showPausedText() end
return
end
else
pauseUpdates(false)
return
end
-- https://github.com/Hammerspoon/hammerspoon/issues/2742 (now fixed)
-- local snap = hs.window.snapshotForID(i, false)
if e then
local e_in_s = (hs.timer.absoluteTime() - e) / 1000000000
local e_rounded = round(e_in_s,0.25)
-- print('elapsed time (s): ' .. e_rounded)
if e_rounded > refreshInt then
refreshFailCount = refreshFailCount + 1
if refreshFailCount >= 10 then
print('reducing refresh interval ('..e_rounded..' vs '..refreshInt..')')
refreshFailCount = 0
cycleRefreshIntReverse()
end
end
end
e = hs.timer.absoluteTime()
local snap = w:snapshot(false)
if not (snap and hstype(snap) == 'hs.image') then
failCount = failCount + 1
if failCount >= 10 then
close_floater(true,true,0.1,'failed to get snapshot, giving up after '..failCount..' tries')
end
return
end
local cropped = snap:croppedCopy({
x=capture_origin.x * scale,
y=capture_origin.y * scale,
h=canvasSize.h * scale,
w=canvasSize.w * scale
}):size({ h=canvasSize.h, w=canvasSize.w }, true)
--[[
print(
'x='..capture_origin.x,
'y='..capture_origin.y,
'h='..canvasSize.h,
'w='..canvasSize.w
)
]]--
if floater[2] == nil then
floater[2] = {
id = 'preview',
type = 'image',
action = 'fill',
compositeRule = 'copy',
antialias = false,
frame = { x = "0%", y = "0%", h = "100%", w = "100%" },
padding = 2.0,
image = cropped,
imageAlpha = 1.0,
imageAlignment = 'center',
imageScaling = 'none',
trackMouseDown = true,
trackMouseUp = true,
trackMouseEnterExit = true
}
else
floater[2].image = cropped
end
loopCount = loopCount + 1
if loopCount > 40 then
collectgarbage()
-- print("Process resident size: "..hs.crash.residentSize())
-- print("Lua state size: "..math.floor(collectgarbage("count")*1024))
loopCount = 0
end
failCount = 0
end
function moveWinOffscreen(bool)
if not bool then
if winPosCache[i] then
if w then w:setFrame(winPosCache[i]) end
winPosCache[i] = nil
end
if reactivateWhenClosing and app and w then
app:activate()
w:unminimize()
end
hasBeenMovedOffscreen = false
elseif bool then
if moveOffscreen and w then
winPosCache[i] = win_frame
w:setFrame({ x=max_x_pos(), y=win_frame.y, w=win_frame.w, h=win_frame.h }, 0)
w:move({35,0}, false, 0.5) -- kludge because setFrame won't push a window fully offscreen
hasBeenMovedOffscreen = true
end
end
end
function drawBoxAroundCaptureArea()
captureHilite = hs.canvas.new({
x=win_frame.x + capture_origin.x,
y=win_frame.y + capture_origin.y,
h=canvasSize.h,
w=canvasSize.w
})
captureHilite[1] = {
id = 'border',
type = 'rectangle',
action = 'stroke',
strokeWidth = 4.0,
strokeColor = captureRectColor,
antialias = false,
frame = { x = "0%", y = "0%", h = "100%", w = "100%" }
}
captureHilite:level('overlay')
captureHilite:show()
end
function drawCrosshairs()
if _updatesPaused then return end
if canvasElementExists(floater,'crosshair') then return end
top = { x=floater:frame().w/2, y=0 }
bottom = { x=floater:frame().w/2, y=floater:frame().h }
left = { x=0, y=floater:frame().h/2 }
right = { x=floater:frame().w, y=floater:frame().h/2 }
center = { x=floater:frame().w/2, y=floater:frame().h/2 }
floater:insertElement({
id = 'crosshair',
type = 'segments',
action = 'stroke',
coordinates = { top, bottom, center, left, right },
strokeWidth = 1.0,
antialias = false,
strokeColor = lineColor
})
end
function drawEscText()
if canvasElementExists(floater,'esctxt') then return end
if canvasElementExists(floater,'crosshair') then
if canvasSize.w >= 100 then
removeOverlays('interval')
txt = hs.styledtext.new('ESC', fTextStyle)
floater:insertElement({
id = 'esctxt',
type = 'text',
text = txt,
padding = textPadding,
action = 'stroke',
frame = { x="0%", y="0%", h="100%", w="100%" },
antialias = true
})
end
end
end
function canvasElementExists(canvas,id)
if not (
canvas and
id and
hstype(canvas) == 'hs.canvas' and
type(id) == 'string'
) then return false end
for _,e in pairs(canvas:canvasElements()) do
if id == e.id then return true end
end
return false
end
function removeOverlays(ids) -- (...)
-- local arg={...}
if not ids then return end
if not (floater and #floater > 2) then return end
if type(ids) == 'string' then ids = { ids } end
for _,id in pairs(ids) do
for i=#floater,3,-1 do -- use hs.canvas:elementCount() ?
if (id == 'all') or (floater[i].id == id) then -- floater:elementAttribute(e,'id')
floater:removeElement(i) -- floater[e] = nil
end
end
end
end
-- https://www.hammerspoon.org/docs/hs.hotkey.html
function activateHotkey(name,mods,key,fn)
if not hkTable then return end
if not name then return end
if not fn then return end
if type(key) ~= 'string' then return end
if hkTable[name] then destroyHotkeys(name) end
hkTable[name] = hs.hotkey.bind(mods,key,fn,nil,nil)
-- if hkTable[name] then print('bound hotkey '..name..' to '..hkTable[name].idx) end
end
function destroyHotkeys(hklist)
if not hklist then return end
if type(hklist) == 'string' then hklist = { hklist } end
for _,name in pairs(hklist) do
if hkTable[name] and hstype(hkTable[name]) == 'hs.hotkey' then
hkTable[name]:delete()
hkTable[name] = nil
end
end
end
function destroyTimers(tlist)
if not tlist then return end
if type(tlist) == 'string' then tlist = { tlist } end
for _,name in pairs(tlist) do
if timerTable[name] and hstype(timerTable[name]) == 'hs.timer' then
timerTable[name]:stop()
timerTable[name] = nil
end
end
end
function timerIsRunning(name)
if timerTable[name] and timerTable[name]:running() then
return true
else
return false
end
end
function _stopCoroutine(reason)
_cMouseAction = nil
-- print('stopping coroutine: '..(reason or 'unknown'))
removeOverlays({'esctxt','crosshair'})
destroyHotkeys({'hk_esc'})
end
function createFail(msg)
print(msg)
resetSrcWindow()
return false
end
function destroy_floater()
if floater and floater:elementCount() > 0 then
close_floater(true,false,0.1,'closed by hotkey')
end
end
function animateFloaterBorder(cObj)
if not next(patterns) then
destroyTimers({'animGuardTimer','strokeDashPatternTimer'})
cObj[1].strokeDashPattern = nil
cObj[1].strokeColor = lineColor
return
end
if not timerIsRunning('animGuardTimer') then
timerTable['animGuardTimer'] = hs.timer.doWhile(
function() return next(patterns) ~= nil end,
function() animateFloaterBorder(cObj) end,
1)
else
timerTable['animGuardTimer']:setNextTrigger(1)
end
cObj[1].strokeDashPattern = patterns[1].p
cObj[1].strokeColor = lineColor
table.remove(patterns,1)
if next(patterns) then
-- print('will set dashPat = '..hs.inspect(patterns[1].p)..' in '..patterns[1].d..'s')
destroyTimers('strokeDashPatternTimer')
timerTable['strokeDashPatternTimer'] = hs.timer.doAfter(patterns[1].d, function() animateFloaterBorder(cObj) end)
end
end
function strokeFloaterWithAnimation(cObj)
if hstype(cObj) ~= 'hs.canvas' then return end
patterns = {}
local gap = 24
local step = 2 -- orig:6
local mod = gap % step
if mod > 0 then
print('invalid gap/step pair, adjusting gap '..gap..'->'..gap - mod)
gap = gap - mod
end
local duration = 0.25 -- total duration of the animation
local speed = duration / (gap/step)
for p=step,gap,step do table.insert(patterns, {d=speed, p={ p, gap-p }}) end
-- print('animation has '..#patterns..' steps:')
-- print(hs.inspect(patterns))
animateFloaterBorder(cObj)
end
function strokeFloaterWithoutAnimation(cObj)
if hstype(cObj) ~= 'hs.canvas' then return end
cObj[1].strokeColor = lineColor
cObj[1].strokeDashPattern = nil
end
function create_floater()
cur_pos = hs.mouse.absolutePosition()
-- make sure we have a valid window to track
if not w then
w = hs.window.focusedWindow()
if not w then return createFail('no focused window') end
if w == hs.window.desktop() then return createFail('can\'t float the desktop window') end
if not w:isVisible() then createFail('can\'t float an invisible window') end
i = w:id()
if not i then return createFail('couldn\'t obtain the windowID') end
scale = hs.screen.mainScreen():currentMode().scale
app = w:application()
appname = (app:name() or 'unknown')
win_frame = w:frame()
win_scr = w:screen()
win_scr_frame = win_scr:fullFrame()
capture_center = get_relative_mouse(cur_pos,win_frame)
end
if floater and floater:elementCount() > 0 then
floater_center = {
x = to_int(floater:frame().x + floater:frame().w/2),
y = to_int(floater:frame().y + floater:frame().h/2)
}
close_floater(false,false,0,'size change')
presetSize = get_size(true)
_animate = false
_moveOffscreenNow = false
playSound(snd_size,soundEnabled)
else
floater_center = {
x = to_int(cur_pos.x),
y = to_int(cur_pos.y)
}
presetSize = get_size(false)
_animate = animateBorderDraw
_moveOffscreenNow = moveOffscreen
playSound(snd_on,soundEnabled)
end
canvasSize = { h=math.min(presetSize,to_int(win_frame.h)), w=math.min(presetSize,to_int(win_frame.w)) }
canvasAdj = { x=to_int(canvasSize.w/2), y=to_int(canvasSize.h/2) }
floater_origin = {
x = valid_origin(floater_center.x - canvasAdj.x, win_scr_frame.x, win_scr_frame.x + win_scr_frame.w - canvasSize.w),
y = valid_origin(floater_center.y - canvasAdj.y, win_scr_frame.y, win_scr_frame.y + win_scr_frame.h - canvasSize.h)
}
_updateCaptureOrigin({
x = valid_capture_pos(capture_center.x - canvasAdj.x, win_frame.w - canvasSize.w),
y = valid_capture_pos(capture_center.y - canvasAdj.y, win_frame.h - canvasSize.h)},
canvasAdj)
if hiliteCaptureArea then drawBoxAroundCaptureArea() end
floater = hs.canvas.new({
x=floater_origin.x,
y=floater_origin.y,
h=canvasSize.h,
w=canvasSize.w
})
floater:level('overlay') -- allows floater to be positioned above dock/menubar
floater[1] = {
id = 'border',
type = 'rectangle',
action = 'stroke',
strokeWidth = 4.0,
strokeColor = { red=0, green=0, blue=0, alpha=0 },
antialias = false,
frame = { x = "0%", y = "0%", h = "100%", w = "100%" }
}
refreshWin(i,true)
floater:show()
if _animate then
strokeFloaterWithAnimation(floater)
else
strokeFloaterWithoutAnimation(floater)
end
timerTable['waitForAnimCompletion'] = hs.timer.waitUntil(
function() return (floater and floater.strokeDashPattern == nil) end,
function()
if _moveOffscreenNow then moveWinOffscreen(true) end
if not timerIsRunning('main') then startMainTimer() end
end, 0.1)
if floater and floater:elementCount() > 0 then
activateHotkey('hk_speed', {'ctrl'}, main_key, cycleRefreshInt)
activateHotkey('hk_destroy', {'shift'}, main_key, destroy_floater)
activateHotkey('hk_togglewin', {'cmd'}, main_key, function() moveWinOffscreen(not hasBeenMovedOffscreen) end)
floater:clickActivating(false):mouseCallback(floaterCallback)
end
end
-- https://www.hammerspoon.org/docs/hs.canvas.html#mouseCallback
function floaterCallback(_c, _m, _i, _x, _y)
if _m == "mouseUp" then _stopCoroutine(_m) return end
if _m == "mouseExit" then drawEscText() return end
if _m == "mouseDown" then
local _buttons = hs.eventtap.checkMouseButtons()
local last_pos = hs.mouse.absolutePosition()
if _buttons.left and checkForDoubleClick(hs.timer.absoluteTime(),last_pos) then
moveWinOffscreen(not hasBeenMovedOffscreen)
return
end
if _buttons.right then drawCrosshairs() end
local initial_pos = last_pos
local orig_capture_origin = capture_origin
_cMouseAction = coroutine.wrap(
function()
activateHotkey('hk_esc', nil, 'escape', function() _stopCoroutine('hotkey') end)
while _cMouseAction do
local cur_pos = hs.mouse.absolutePosition()
if mouseHasMoved(last_pos,cur_pos) then
last_pos = cur_pos
if _buttons.right and not _updatesPaused then -- move capture area
local delta = { x=(to_int(cur_pos.x - initial_pos.x)), y=(to_int(cur_pos.y - initial_pos.y)) }
-- print('delta: x:'..delta.x..' y:'..delta.y..' invert:'..tostring(invertScrollDirection))
if invertScrollDirection then delta.x, delta.y = -delta.x, -delta.y end
_updateCaptureOrigin({
x = valid_capture_pos(orig_capture_origin.x + delta.x, win_frame.w - canvasSize.w),
y = valid_capture_pos(orig_capture_origin.y + delta.y, win_frame.h - canvasSize.h)},
canvasAdj)
refreshWin(i)
elseif _buttons.left then -- move floater
local frame = _c:frame()
frame.x, frame.y = to_int(cur_pos.x - _x), to_int(cur_pos.y - _y)
_c:frame(frame)
end
end
coroutine.applicationYield() -- 0.001 ?
end -- end of while _cMouseAction loop
end) -- end of coroutine.wrap
_cMouseAction()
end
end
function screenEvent()
if not floater then return end
-- print('screenSaver:',screenSaver)
-- print('screenLock: ',screenLock)
-- print('screenSleep: ',screenSleep)
if screenSaver or screenLock or screenSleep then
-- print('pausing updates')
pauseUpdates(true)
else
-- print('resuming updates')
resumeUpdates(true)
end
end
function floatWatcherCallback(event)
-- print('floatWatcherCallback fired: '..tostring(event))
if event == hs.caffeinate.watcher.screensaverDidStart then
screenSaver = true
-- print('screensaverDidStart')
elseif event == hs.caffeinate.watcher.screensDidLock then
screenLock = true
-- print('screensDidLock')
elseif event == hs.caffeinate.watcher.screensDidSleep then
screenSleep = true
-- print('screensDidSleep')
elseif event == hs.caffeinate.watcher.screensaverDidStop then
screenSaver = false
-- print('screensaverDidStop')
elseif event == hs.caffeinate.watcher.screensDidUnlock then
screenLock = false
-- print('screensDidUnlock')
elseif hs.caffeinate.watcher.screensDidWake then
screenSleep = false
-- print('screensDidWake')
end
screenEvent()
end
floatWatcher = hs.caffeinate.watcher.new(floatWatcherCallback)
floatWatcher:start()
activateHotkey('hk_create', nil, main_key, create_floater)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment