Hold a key. Speak. The words appear where your cursor is.
Push-to-talk dictation for macOS that runs entirely on your Mac — no accounts, no API keys, no audio leaving the machine.
hold ⌥ ──▶ 🎙 record ──▶ 🧠 transcribe on device ──▶ ⌨️ paste at caret ──▶ clipboard restored
A Superwhisper alternative in about a thousand lines of Swift.
- Fully offline. Apple's on-device speech engine, and Whisper on CoreML for the languages Apple does not cover. Your audio is never uploaded — the only network use is downloading the Whisper model once.
- Any language your keyboard speaks. The layout picks the language — Thai layout dictates Thai — or let Whisper detect it from the audio.
- Your trigger. Any modifier, or a recorded combination like ⌃⌥D, held to talk.
- Stays out of the way. A level-metered pill while recording, your clipboard handed back afterwards, and no window unless you ask for one.
- macOS 26 or later, Apple silicon
- Swift 6.3 toolchain (Xcode 26 or the command line tools)
make runThat builds a release binary, assembles Yapper.app, signs it, and launches it.
Yapper lives in the menu bar (and the Dock — see Dock presence).
On first launch macOS asks for two permissions:
- Microphone, to record you.
- Accessibility, for the event tap that watches the trigger key. Grant it in System Settings → Privacy & Security → Accessibility, then use the menu item Open Accessibility Settings… if the prompt does not appear.
The menu bar item says what it is waiting for at every step.
Hold the trigger key, speak, release. The transcript is pasted at the caret and your previous clipboard contents are restored a moment later. A small pill with a live level meter shows while recording.
The default trigger is right Option. A lone modifier is used on purpose: it does nothing by itself, so it can never shadow a shortcut in the app you are dictating into.
Everything lives in the menu bar menu.
| Menu | What it does |
|---|---|
| Trigger Key | Pick another modifier (left Option, right Command, right Control, Fn), or Record Shortcut… to hold any combination such as ⌃⌥D. A recorded combination is swallowed while bound, so its key does not type into your text. |
| Language | Follow Keyboard (default) picks the dictation language from the current keyboard layout. Detect From Speech ignores the keyboard and lets Whisper work out the language from the audio. |
| Indicator Position | Where the recording pill sits: any corner, or top/bottom center. |
Yapper uses whichever on-device engine fits the language:
- Apple's
SpeechTranscriberfor the 30 locales it supports (English, Japanese, Chinese, Korean, German, French, Spanish, Italian, Portuguese). Instant, streams while you talk, nothing to download beyond Apple's own model. - Whisper
large-v3-turbo, on-device via CoreML, for everything else — Thai among them, which Apple's engine does not support at all. Roughly one second for a ten second utterance on Apple silicon.
The Whisper model is 1.5 GB. It downloads once, on first use of a language that
needs it, into ~/Library/Application Support/Yapper, and the menu bar shows the
progress. After that it works with no network at all.
In Follow Keyboard mode the keyboard layout decides: a Thai layout dictates Thai, ABC dictates English. Speaking Thai while on an English layout will produce English nonsense — that is the mode working as intended. Switch to Detect From Speech if you would rather never think about the layout, at the cost of about a second per dictation and the Whisper model being needed for every language.
Switching layout re-arms the engine in the background. A press during those few seconds is skipped rather than transcribed in the wrong language.
Yapper is a regular app: it has a Dock icon and appears in the app switcher. For
a menu-bar-only app, set the activation policy to .accessory in
AppDelegate.applicationDidFinishLaunching and put LSUIElement back in
Info.plist.
make build # swift build -c release
make app # assemble and sign Yapper.app
make run # app + relaunch
make icon # regenerate the .icns from Resources/icon.svg
make selftest # audio conversion, level meter, logo, trigger, pill placement
make speechtest # two Apple-engine sessions back to back; needs the speech model
make whispertest # synthesizes Thai speech and transcribes it; needs the Whisper model
make dev-identity # one-time signing identity so permissions survive a rebuild
make reset-perms # clear the Microphone and Accessibility grants
make clean # remove .build and the app bundleAn ad-hoc signature changes on every build, and macOS keys Microphone and Accessibility grants to the signature — so every rebuild silently loses them. The symptom is confusing: Yapper keeps its checked box in System Settings → Privacy & Security → Accessibility while the app behaves as if it were never granted, because the box belongs to the signature of an earlier build.
Create a stable identity once and the grants stick:
make dev-identity # self-signed "Yapper Dev" certificate, then clears the
# stale grants that belonged to the ad-hoc signature
make runGrant Microphone and Accessibility one last time when the rebuilt app asks. If Yapper is still listed under Accessibility from before, remove it with the − button first — a stale row can shadow the new one.
The Makefile finds that identity by itself and falls back to ad-hoc signing when it is missing, so a fresh clone still builds. Override for a release build:
make app CODESIGN_ID="Developer ID Application: ..."A menu bar app has nowhere to print, so everything goes to the unified log:
log show --info --last 10m --predicate 'subsystem == "com.postttt.yapper"'It records which engine each dictation used, the text produced, and every keyboard layout change.
| File | Role |
|---|---|
AppDelegate.swift |
Status item, menus, permission polling, engine selection, the dictation lifecycle |
Hotkey.swift |
Event tap for the trigger, passive for modifiers and swallowing for recorded combinations |
Trigger.swift |
The trigger model, presets and recorded combinations, persistence |
ShortcutRecorder.swift |
Panel that captures one key press |
Recorder.swift |
Microphone capture, format conversion, level metering |
Transcriber.swift |
Apple's SpeechAnalyzer pipeline, one module per session |
WhisperTranscriber.swift |
Whisper via WhisperKit, model download and loading |
KeyboardLanguage.swift |
Current layout language and layout-change notifications |
Indicator.swift |
The floating recording pill and its placement |
Paste.swift |
Paste at the caret and restore the clipboard |
Permissions.swift |
Microphone and Accessibility state |
Log.swift |
The unified-log handle used across the app |
SelfTest.swift, SpeechSessionTest.swift, WhisperSessionTest.swift |
The checks behind the make test targets |
MIT. See LICENSE.