71 lines
2.6 KiB
Lua
71 lines
2.6 KiB
Lua
-- rvoice.lua — Right-Option push-to-talk for the rvoice helper.
|
|
--
|
|
-- Install:
|
|
-- 1. Hammerspoon → Preferences → enable "Launch Hammerspoon at login"
|
|
-- 2. Add this line to ~/.hammerspoon/init.lua:
|
|
-- require("rvoice")
|
|
-- 3. Symlink this file so init.lua can find it:
|
|
-- ln -sfn ~/Code/@scripts/session-tools/hammerspoon/rvoice.lua \
|
|
-- ~/.hammerspoon/rvoice.lua
|
|
-- 4. Reload Hammerspoon config (menu bar → Reload Config)
|
|
-- 5. Grant Accessibility + Microphone permissions when prompted.
|
|
--
|
|
-- Behavior: hold Right-Option to talk. Release to transcribe + inject into
|
|
-- the active iTerm2 tab's remote tmux session. Taps shorter than 200ms are
|
|
-- ignored (configurable via RVOICE_MIN_MS env in rvoice config).
|
|
|
|
local M = {}
|
|
|
|
-- Resolve `rvoice` once at load. Hammerspoon's task PATH is barebones, so
|
|
-- prefer an explicit symlink in ~/.local/bin or fall back to the repo path.
|
|
local function resolveRvoice()
|
|
local candidates = {
|
|
os.getenv("HOME") .. "/.local/bin/rvoice",
|
|
os.getenv("HOME") .. "/Code/@scripts/session-tools/bin/rvoice",
|
|
}
|
|
for _, p in ipairs(candidates) do
|
|
local f = io.open(p, "r")
|
|
if f then f:close(); return p end
|
|
end
|
|
return "rvoice"
|
|
end
|
|
|
|
local RVOICE = resolveRvoice()
|
|
local holding = false
|
|
|
|
-- Run rvoice <cmd> in the background; capture stderr to the system log so
|
|
-- failures are visible via Hammerspoon's console.
|
|
local function run(cmd)
|
|
local t = hs.task.new("/bin/sh", function(exit, _, err)
|
|
if exit ~= 0 then
|
|
hs.printf("[rvoice] %s exited %d: %s", cmd, exit, err or "")
|
|
end
|
|
end, {"-c", RVOICE .. " " .. cmd})
|
|
-- Inherit user shell env so GROQ_API_KEY (and PATH for ffmpeg/jq) work.
|
|
t:setEnvironment(hs.execute("env", true):gsub("\n$", "") and nil or nil)
|
|
t:start()
|
|
end
|
|
|
|
-- Right-Option keyDown/keyUp. Hammerspoon delivers modifier changes through
|
|
-- eventtap.flagsChanged; we watch for the rightAlt flag transitioning.
|
|
M.tap = hs.eventtap.new({ hs.eventtap.event.types.flagsChanged }, function(e)
|
|
-- macOS exposes the side via a per-key mask. Right-Option is 0x40 in the
|
|
-- raw `keyCode` event of type flagsChanged (code 61).
|
|
local code = e:getKeyCode()
|
|
if code ~= 61 then return false end -- 61 = Right Option
|
|
local flags = e:getFlags()
|
|
local pressed = flags.alt or false
|
|
if pressed and not holding then
|
|
holding = true
|
|
run("start")
|
|
elseif (not pressed) and holding then
|
|
holding = false
|
|
run("stop")
|
|
end
|
|
return false -- don't swallow the modifier; other apps may use it
|
|
end)
|
|
|
|
M.tap:start()
|
|
hs.alert.show("rvoice: Right ⌥ to talk")
|
|
|
|
return M
|