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:
dttb
2026-05-05 16:27:17 +03:00
parent 89fbfec1b8
commit 265d99b378
6 changed files with 333 additions and 0 deletions

View 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")