個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Hold a key, speak, release it — local push-to-talk dictation powered by Whisper. Free, cross-platform (Linux + macOS), no audio ever leaves your machine.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Hold a key, speak, release it — your words get typed wherever your cursor is, transcribed locally with Whisper. Cross-platform (Linux + macOS), local-only (no audio ever leaves your machine), with a GUI setup wizard for configuring the hotkey, microphone, and model.
This started as a hardcoded personal bash script and was rebuilt as a proper Tauri v2 app so it's installable and reconfigurable without editing shell variables.
Grab the latest build for your OS from Releases:
.deb (Debian/Ubuntu) or .AppImage (portable, any distro).dmg — pick aarch64 for Apple Silicon or x86_64 for IntelmacOS builds are ad-hoc signed (no Apple Developer account behind this project — that costs $99/year, so this stays free) rather than signed with a real Developer ID and notarized. Gatekeeper will still flag the first launch. Try right-click the app → Open first; if macOS instead says the app "is damaged and can't be opened" (a misleading message some macOS versions show for unsigned apps, particularly on Apple Silicon — it's not actually a corrupted download), strip the quarantine flag instead:
xattr -cr /Applications/push2talk.app
On first launch, a setup wizard walks through: picking a microphone, setting your push-to-talk key, choosing and downloading a Whisper model, checking the typing backend, and optionally enabling launch-on-login. After that it lives in the tray/menu bar — reopen the window any time from there to change settings.
Installing the .deb/.rpm via your package manager (apt install ./push2talk_*.deb, etc.) already handles what a package can safely
automate: it pulls in libvulkan1/vulkan-loader (required at launch) and
ydotool (recommended — apt/dnf
installs it by default unless you pass --no-install-recommends) as
dependencies.
Two things are left as manual one-time steps, both because they touch
kernel-level input device permissions — package managers deliberately don't
grant these silently during install, without you explicitly running the
command yourself. This isn't push2talk being lazy: ydotool's own Debian
package documents the exact same two steps, for the exact same reason.
input group — this one group covers both
push2talk reading raw keyboard events and ydotool's daemon writing
synthetic ones via /dev/uinput:
sudo usermod -aG input $USER # log out and back in afterward
ydotool background service once — it ships a systemd
user service, which a package's install script can't reliably enable on
your behalf (it needs your active login session, not available yet during
apt install):
systemctl --user enable --now ydotool
The setup wizard's "Typing backend" step checks both and tells you exactly
what's missing. If you installed the .AppImage instead of .deb/.rpm,
you'll need to install ydotool yourself too — AppImages don't go through a
package manager, so there's no dependency mechanism to lean on there.
push2talk needs three separate permissions granted in System Settings → Privacy & Security, confirmed the hard way across real testing — macOS doesn't always prompt for these automatically on an ad-hoc-signed, unnotarized app the way it would for one signed with a real Developer ID, so check each one manually if something silently doesn't work:
NSMicrophoneUsageDescription, which it does). If you're not on a
build with that Info.plist entry, the OS silently denies access instead of
prompting at all.After granting or changing any of these, fully quit push2talk (tray menu → Quit, not just closing the window) and relaunch it — permission changes often don't take effect for an already-running process.
Linux build dependencies (Ubuntu/Debian):
sudo apt install build-essential curl wget file cmake pkg-config \
libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev \
libxdo-dev libssl-dev libasound2-dev clang libclang-dev libvulkan-dev glslc
clang/libclang-dev are required by bindgen (used by whisper-rs-sys) —
without them the build silently falls back to a stale bundled bindings file
instead of failing, which is easy to not notice (see
Known limitations for the exact failure mode this
causes if skipped). libvulkan-dev + glslc (package name is literally
glslc on Ubuntu) are for whisper.cpp's Vulkan GPU backend.
macOS build dependencies: Xcode Command Line Tools (xcode-select --install) and cmake (brew install cmake). GPU acceleration there uses
Metal, which needs no extra install — it's part of the OS.
Then:
npm install
npm run tauri dev # run in dev mode
npm run tauri build # produce an installable bundle for this OS
whisper-rs compiles whisper.cpp from source as part of the build (that's
what cmake is for) — no separate whisper.cpp clone or model files are
needed at build time; Whisper models are downloaded on demand by the setup
wizard.
To regenerate the app icon from its source (src-tauri/icons/app-icon-source.svg)
after editing it:
npx tauri icon src-tauri/icons/app-icon-source.svg --output src-tauri/icons
rm -rf src-tauri/icons/android src-tauri/icons/ios # this project is desktop-only
/dev/input/eventN (via the evdev crate), the same approach as the
original script's evtest usage; this works under both X11 and Wayland
since it bypasses the display server entirely. macOS uses a hand-rolled
CGEventTap (via core-graphics/core-foundation) that reads only the raw
keycode integer field from each event — see Known limitations
for why it's hand-rolled instead of using the rdev crate.cpal captures from the selected microphone; audio is
downmixed to mono and resampled to 16kHz for Whisper.whisper-rs (Rust bindings to whisper.cpp) runs
locally against the model you picked in the wizard, GPU-accelerated via
Vulkan (Linux) or Metal (macOS) — falls back to CPU automatically if no
compatible GPU/driver is found at runtime. The setup wizard detects your
CPU/RAM/GPU and recommends a model tier accordingly.llama-server
(from an official llama.cpp release, downloaded once) as a local
subprocess and asks a small LLM to rephrase the transcription for clarity
and concision; the rewrite is typed on a second line right under the
original so you can pick which one to keep (and delete the other). Enable
it in Settings, where you can also point it at any GGUF model you already
have locally (e.g. from LM Studio) instead of downloading the bundled
default. Like Whisper, this stays entirely on-device — it's a separate
process rather than linked into push2talk's own binary, because
statically linking llama.cpp and whisper.cpp together fails at link time
(both vendor their own copy of ggml under the same symbol names).ydotool type (uinput-based, works under
Wayland). macOS uses enigo to simulate keystrokes via CGEvent.~/.config/push2talk/config.json (Linux) or
~/Library/Application Support/push2talk/config.json (macOS); models cache
alongside it in the platform's data directory.Pushing a v* tag runs .github/workflows/release.yml,
which builds Linux and macOS bundles via tauri-action and attaches them to
a draft GitHub release.
Note: the Linux binary dynamically links libvulkan.so.1 (for GPU
acceleration) but Tauri's bundler doesn't auto-detect that as a package
dependency the way it does for webkit2gtk/GTK — without it, a machine
lacking the Vulkan loader would install the .deb/.rpm fine via the
package manager but fail to launch the app. libvulkan1 (deb) /
vulkan-loader (rpm) are declared explicitly in tauri.conf.json's
bundle.linux.deb.depends/bundle.linux.rpm.depends to cover this.
whisper-rs's vulkan feature fails to compile with an error like
no ggml_backend_vk_get_device_count in the root, it's very likely a stale
whisper-rs-sys build cache from an earlier attempt where clang/
libclang-dev weren't installed yet — without them, bindgen silently
falls back to an incomplete bundled bindings file instead of failing
loudly, and Cargo can keep reusing that stale output across later builds
even after the real prerequisites are installed. Fix: cargo clean -p whisper-rs-sys -p whisper-rs and rebuild. (This isn't a hypothetical —
it's exactly what happened building this project; a manual bindgen run
with identical flags produced the correct bindings immediately, which is
what pointed at stale cache rather than an upstream bug.)rdev crate used to crash on real Apple Silicon hardware (confirmed
via an actual crash report on macOS 26.5/M4): its event tap callback
unconditionally resolves a human-readable key name via Carbon/TSM
(Keyboard::string_from_code → TSMGetInputSourceProperty), which asserts
it's running on the main dispatch queue and aborts
(dispatch_assert_queue_fail, EXC_BREAKPOINT) when the tap runs on a
background thread — which is exactly how rdev::listen() is meant to be
used. Replaced with a hand-rolled CGEventTap that only ever reads the raw
keycode integer field (see "How it works" above). Confirmed fixed on
real Apple Silicon hardware once Input Monitoring permission was granted
(see macOS prerequisites above) — no more crash.NSMicrophoneUsageDescription Info.plist entry silently broke
recording on macOS (confirmed on real hardware, not a hypothetical):
without it, macOS never prompts for Microphone access and just denies it,
which doesn't surface as an error — cpal still "records" successfully,
just silence, which Whisper then hallucinates into something like "Thank
you" (a well-known artifact of transcribing near-silent audio, not a bug
in the transcription itself). Fixed by adding src-tauri/Info.plist with
the required usage description string, which Tauri auto-merges into the
bundled Info.plist.exit()ggml_metal_rsets_free_exit()app.exit()exit()_exit()window.hide() and
bring it back with window.show(). On this project that left the
window's titlebar buttons (minimize/maximize/close) permanently
unresponsive after the first hide/show cycle — the window content itself
kept rendering and accepting clicks fine, just not its own chrome.
window.minimize() / window.unminimize() is a far more standard,
better-tested GTK/Wayland window lifecycle and doesn't have this problem;
see lib.rs's window setup code.