session-tools/hammerspoon/rvoice.lua

73 lines
2.7 KiB
Lua
Raw Normal View History

-- 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 PATH for ffmpeg/jq is set and rvoice can
-- source ~/.config/rvoice/config to pick up any user overrides.
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