Mac dictation: Hammerspoon + Groq Whisper решение
- decisions/2026-05-05-mac-dictation-groq-hammerspoon.md: полный план, грабли с раскладкой, fallback на whisper-cpp, восстановление на новом Mac - notes/ru-geoblocked-services.md: реестр CDN с RU-блоком (cdn.spokenly, dl.wisprflow и пр.) + принципы обхода - snippets/mac-dictation/: рабочая версия скриптов и init.lua Триггер — одиночный Fn, Groq cloud first → tiny local fallback, вставка через hs.eventtap.event keycode 9 (минует ru-keymap warnings). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
52
snippets/mac-dictation/init.lua
Normal file
52
snippets/mac-dictation/init.lua
Normal file
@@ -0,0 +1,52 @@
|
||||
-- Groq Dictate: trigger по одиночному нажатию Fn (Globe)
|
||||
-- Fn нельзя биндить через hs.hotkey, поэтому слушаем flagsChanged
|
||||
|
||||
local SCRIPT = os.getenv("HOME") .. "/bin/groq-dictate.sh"
|
||||
|
||||
local function runScript()
|
||||
hs.task.new("/bin/bash", function(exitCode, stdOut, stdErr)
|
||||
local f = io.open("/tmp/groq-dictate.last", "r")
|
||||
if f then
|
||||
local text = f:read("*a")
|
||||
f:close()
|
||||
os.remove("/tmp/groq-dictate.last")
|
||||
if text and #text > 0 then
|
||||
hs.pasteboard.setContents(text)
|
||||
-- keycode 9 = физическая V, в обход keymap lookup чтобы не спамить warnings на ru-раскладке
|
||||
hs.eventtap.event.newKeyEvent({"cmd"}, 9, true):post()
|
||||
hs.eventtap.event.newKeyEvent({"cmd"}, 9, false):post()
|
||||
end
|
||||
end
|
||||
end, {SCRIPT}):start()
|
||||
end
|
||||
|
||||
-- Отслеживаем Fn: keycode 63 — это physical Fn/Globe
|
||||
-- Триггер по press (а не по release), чтобы было снапи
|
||||
local fnPressed = false
|
||||
local fnTime = 0
|
||||
|
||||
fnTap = hs.eventtap.new({hs.eventtap.event.types.flagsChanged}, function(event)
|
||||
local kc = event:getKeyCode()
|
||||
if kc ~= 63 then return false end -- 63 = Fn/Globe
|
||||
|
||||
local flags = event:getFlags()
|
||||
local now = hs.timer.absoluteTime() / 1e6 -- ms
|
||||
|
||||
if flags.fn and not fnPressed then
|
||||
-- Fn pressed
|
||||
fnPressed = true
|
||||
fnTime = now
|
||||
elseif (not flags.fn) and fnPressed then
|
||||
-- Fn released
|
||||
fnPressed = false
|
||||
local elapsed = now - fnTime
|
||||
-- Только короткое нажатие (<400ms) триггерит — long-press для других целей
|
||||
if elapsed < 400 then
|
||||
runScript()
|
||||
end
|
||||
end
|
||||
return false -- не блокировать event для других слушателей
|
||||
end)
|
||||
fnTap:start()
|
||||
|
||||
hs.alert.show("Groq Dictate: Fn (Globe) ready")
|
||||
Reference in New Issue
Block a user