Skip to content

Instantly share code, notes, and snippets.

@Madd0g
Last active August 17, 2023 22:44
Show Gist options
  • Select an option

  • Save Madd0g/57e7f7fcbc0170b5b8392532b2db9780 to your computer and use it in GitHub Desktop.

Select an option

Save Madd0g/57e7f7fcbc0170b5b8392532b2db9780 to your computer and use it in GitHub Desktop.
Hammerspoon noises-based morse code trigger (short / medium / long hisses)
local mod = {}
-- thresholds in ms
local MED_THRESHOLD = 150
local LNG_THRESHOLD = 540
local HIDE_MARKERS_AFTER_MATCH_SECONDS = 1
-- never really consider events older than this
local EVENTS_CUTOFF = 2500
-- remember to place longer sequences ahead of short sequences
-- the cases are checked in order
mod.morseActions = {
-- { pat = {'sh', 'me', 'lo'}, fn = function() hs.alert.show("looooo and behooold") end},
-- { pat = {'sh', 'me', 'sh'}, fn = function() showSong() end},
-- { pat = {'sh', 'me', 'me'}, fn = winTrack.report},
-- { pat = {'sh', 'sh'}, fn = function() clickNotification() end},
-- { pat = {'sh'}, fn = function() modal.toggleScroll() end},
{
pat = {'sh', 'sh'},
fn = function()
myDoKeyStroke({}, 'j')
hs.timer.doAfter(0.25, readSelectedReddit)
end
},
}
local start = nil
local ev = {}
function slice(tbl, s, e)
local pos, new = 1, {}
for i = s, e do
new[pos] = tbl[i]
pos = pos + 1
end
return new
end
function mod.startTracking()
start = hs.timer.absoluteTime() / 1e6
end
function mod.trackEvent(customDuration)
if start == nil and not customDuration then return end
local elapsed
if customDuration then
elapsed = customDuration
else
local ts = hs.timer.absoluteTime() / 1e6
elapsed = math.floor(ts - start)
end
if elapsed > 1800 then return end
local type = nil
if elapsed > LNG_THRESHOLD then
type = 'lo'
elseif elapsed < MED_THRESHOLD then
type = 'sh'
else
type = 'me'
end
-- log.w('elapsed:', elapsed, type)
local event = {ts = ts, type = type}
table.insert(ev, event)
-- log.w('registering', event.type)
mod.clearOldEvents()
mod.draw()
mod.checkIfMatch()
start = nil
end
local con = nil
local markers = {}
local markerTimer = nil
local function createConsole()
con = console.new({
baseFont = {name = "Fira Code", size = 14},
location = {x = 0, y = 'top'},
padding = 4,
cornerRadius = 5,
backgroundColor = {alpha = 1, white = .03},
backgroundBorder = {alpha = .90},
initialText = '...'
})
end
function hidemarkers(andClearEvents)
for k, obj in pairs(markers) do
local v = obj.marker
v:hide(0.2)
end
markers = {}
if andClearEvents then ev = {} end
end
function flashMarkers(numMatched, individual)
for k, obj in pairs(markers) do
local v = obj.marker
if #markers - k < numMatched then
local oldSize = hs.geometry.new(v:frame())
local size = hs.geometry.new(v:frame())
size:scale(1.17)
v:setFrame({x = size.x, y = size.y, w = size.w, h = size.h})
hs.timer.doAfter(.20, function() v:setFrame(oldSize) end)
end
end
end
local sizes = {sh = 25, me = 35, lo = 45}
local colors = {
sh = {["red"] = 0, ["blue"] = 0, ["green"] = 1, ["alpha"] = 0.8},
me = {["red"] = 0.3, ["blue"] = 0.8, ["green"] = 0.7, ["alpha"] = 0.8},
lo = {["red"] = 1, ["blue"] = 0.2, ["green"] = 0.2, ["alpha"] = 0.8}
}
function mod.draw()
local frame = hs.screen.mainScreen():fullFrame()
if markerTimer ~= nil then markerTimer:stop() end
-- hidemarkers()
local lastTop = 0
for k, v in pairs(ev) do if #ev - k < 3 then end end
for k, v in pairs(ev) do
if #ev - k < 3 then
local size = sizes[v.type]
local currColor = colors[v.type]
if lastTop == 0 then
lastTop = frame.h - 60
else
lastTop = lastTop - 5 - size
end
local rect = hs.geometry.rect(30, lastTop, 20, size)
local marker = hs.drawing.rectangle(rect)
marker:setFillColor(currColor):setRoundedRectRadii(7, 7):show()
table.insert(markers, {marker = marker, color = currColor})
-- flashMarkers(1, true)
end
end
markerTimer = hs.timer.doAfter(2, function() hidemarkers(true) end)
end
function mod.checkIfMatch()
-- if #ev < 2 then
-- return
-- end
if #ev > 5 then
-- log.w('too many events, ignoring everything')
return
end
for index, v in pairs(mod.morseActions) do
-- if #v.pat > 1 and v.fn then
if v.fn then
local isMatch = #ev >= #v.pat and
checkVariation(v.pat, slice(ev, math.max(
#ev - #v.pat + 1,
1), #ev))
if isMatch then
flashMarkers(#v.pat)
ev = {}
start = nil
-- delay the hide trigger 1 sec
if markerTimer ~= nil then
markerTimer:setNextTrigger(HIDE_MARKERS_AFTER_MATCH_SECONDS)
end
v.fn()
return
end
end
end
end
function checkVariation(variation, events)
-- log.w('checking', events[1].type, '=', variation[1], 'and', events[2].type, '=', variation[2])
for k, v in pairs(events) do
-- log.w('isEq', v.type, variation[k])
if v.type ~= variation[k] then return false end
end
return true
end
function mod.clearOldEvents()
local ts = hs.timer.absoluteTime() / 1e6
local sliceIndex = 0
local lastTs = 0
for k, v in pairs(ev) do
-- print('from last ' .. hs.inspect(v)
if v ~= nil and v.ts ~= nil then
local diff = ts - v.ts
if diff < EVENTS_CUTOFF then break end
lastTs = v.ts
sliceIndex = k
end
end
if sliceIndex > 0 then
-- log.w('unpacking from', sliceIndex + 1, #ev)
ev = slice(ev, sliceIndex + 1, #ev)
end
end
return mod
local morse = require('morse_code')
local clickState
local mousepoint
local point
function holdMouse()
clickState = hs.eventtap.event.properties.mouseEventClickState
mousepoint = hs.mouse.getAbsolutePosition()
point = {x=mousepoint.x, y=mousepoint.y}
hs.eventtap.event.newMouseEvent(hs.eventtap.event.types["leftMouseDown"], point):setProperty(clickState, 1):post()
end
function releaseMouse()
clickState = hs.eventtap.event.properties.mouseEventClickState
mousepoint = hs.mouse.getAbsolutePosition()
point = {x=mousepoint.x, y=mousepoint.y}
hs.eventtap.event.newMouseEvent(hs.eventtap.event.types["leftMouseUp"], point):setProperty(clickState, 1):post()
end
local mod = {}
function mod.start()
noisesListener:start()
end
function stopNoiseListen()
noisesListener:stop()
hs.alert.show("stop listening")
end
function mod.stop()
stopNoiseListen()
end
local noiseCallback = {
-- mouse hold/click
-- [1] = holdMouse,
-- [2] = releaseMouse,
-- morse code
[1] = morse.startTracking,
[2] = morse.trackEvent,
[3] = function()
-- hs.alert.show("you did it!")
end,
}
-- local mode = 'click'
local mode = nil -- morse
function mod.callback(type)
if type == 'scroll' then
-- noiseCallback[1] = function() autoScroll.toggleAutoScroll() end
-- noiseCallback[2] = function() end
elseif type == 'click' then
noiseCallback[1] = holdMouse
noiseCallback[2] = releaseMouse
else
noiseCallback[1] = morse.startTracking
noiseCallback[2] = morse.trackEvent
end
end
mod.callback(mode)
noisesListener = hs.noises.new(function(noiseType)
noiseCallback[noiseType]()
end)
hs.hotkey.bind(cah, "y", function()
hs.alert.show("listening")
noisesListener:start()
end)
hs.hotkey.bind(cah, "u", stopNoiseListen)
return mod
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment