Voice dictation plugin for Steam Deck using faster-whisper with context-aware transcription.
- Push-to-Talk: Hold button combo (default: L1+R1) to record
- Configurable buttons: Choose 1-5 button combinations from any Steam Deck controller buttons
- Game presets: Switch between game profiles with different chat systems (WoW, Guild Wars 2, Generic)
- Channel-aware: Speak channel prefixes like "party pull boss" and it routes to the right chat channel
- Test Recording: Built-in 3-second test with automatic transcription display
- Context-aware: Optional WoW addon integration for better accuracy with zone, boss, and party context
- Fast transcription: Uses faster-whisper for efficient CPU-based speech recognition
- Auto-type: Automatically types transcribed text into active window via ydotool
- Toast notifications: Optional notifications when recording starts/stops
Important
Use a direct ZIP URL that points to the packaged plugin artifact.
Recommended short install URL:
https://silverfoxy.github.io/decktation/latest.zip
Full stable URL:
https://silverfoxy.github.io/decktation/releases/latest/decktation.zip
Branch build URL pattern:
https://silverfoxy.github.io/decktation/branches/<url-encoded-branch-name>/decktation.zip
GitHub release assets also work:
https://github.com/silverfoxy/decktation/releases/latest/download/decktation.zip
Do not use GitHub's Source code (zip) or Source code (tar.gz) archives. Those do not contain Decktation's bundled Python dependencies.
-
Open Decky Settings, enable developer options if necessary, and select Install Plugin from URL or Install Plugin from ZIP.
-
Paste the direct ZIP URL above, or download
decktation.zipfirst and then choose the file locally. Decky imports the archive and reloads the plugin automatically.
For example, branch feature/chat polish becomes
feature%2Fchat%20polish in the URL path.
All dependencies and the private keyboard helper are pre-bundled in the
release. No system packages or one-time sudo setup are required.
To make Decktation appear in Decky's plugin browser and receive update notifications, select Custom under Decky Settings → General → Store Channel, then enter:
https://homebrew.imsilverfoxy.com/plugins.json
The catalog is updated when a new version tag is published. Switching to a custom store replaces the catalog shown by Decky's Default or Testing store; users can switch channels again at any time.
- Open Quick Access Menu (... button on Steam Deck)
- Navigate to Decktation plugin
- Select your game from the Game dropdown
- Enable the plugin (waits for Whisper model to load)
- (Optional) Change the button combination in the plugin UI
- In any app/game: hold [button1]+[button2] together to record, release to transcribe and type
Decktation ships with three presets configured in defaults/game_presets.json:
| Preset | Behavior |
|---|---|
| World of Warcraft | Presses Enter to open chat, types channel prefix + message, presses Enter to send |
| Guild Wars 2 | Presses Enter to focus chat, types the Guild Wars 2 channel command + message, presses Enter to send |
| Generic | Types text directly into whatever window is focused — no Enter presses |
In WoW mode, start your message with a channel name to route it to the right channel:
"party let's go" → /p let's go
"raid: focus adds first" → /raid focus adds first
"say, hello everyone" → /s hello everyone
"guild heads up" → /g heads up
"type search term" → types directly without opening chat
Separators supported: space, colon, comma, or period after the channel name. Case-insensitive.
Available WoW channels: say, party, raid, guild, officer, yell, instance, whisper, type
Guild Wars 2 mode uses the same voice-first flow. For example:
"map meta starts in five" → /m meta starts in five
"party stack on me" → /p stack on me
"squad: ready check" → /d ready check
"team push mid" → /t push mid
"guild one hello everyone" → /g1 hello everyone
"whisper Player Name hello" → /w Player Name hello
Available Guild Wars 2 channels: say, map, party, squad, raid (an alias for squad), team, guild, guild one through guild six, whisper, type.
You can add channels beyond the built-in ones (e.g. WoW's numbered channels /1, /2) by editing two config files.
Step 1 — add the channel command to defaults/game_presets.json under the preset's channels map:
"channels": {
"say": "/s ",
"party": "/p ",
"one": "/1 ",
"two": "/2 "
}Step 2 — add the spoken trigger words to defaults/channel_languages.json under each language's channels map:
"en": {
"channels": {
"one": ["one", "channel one"],
"two": ["two", "channel two"]
}
}After saving both files, saying "one hello" or "channel one hello" will send /1 hello.
You can add as many trigger words per channel as you like (e.g., aliases in multiple languages). The key in defaults/channel_languages.json must match the key in defaults/game_presets.json.
Edit defaults/game_presets.json to add new games — no code changes needed. Each preset specifies:
chat_open_key— key to open the chat box ("enter"ornull)chat_send_key— key to send the message ("enter"ornull)default_channel— channel to use when no prefix is spokenchannels— map of spoken words to slash-command prefixeswhisper_prompt— vocabulary hint for the Whisper model
The plugin uses a configurable button combo for push-to-talk. You can set 1–5 buttons from the plugin UI.
Available buttons:
- L1, R1 (bumpers) — Default combo
- L2, R2 (triggers)
- L4, R4, L5, R5 (Steam Deck back grips, detected through raw HID)
- A, B, X, Y (face buttons)
Controller input combines Linux evdev gamepad events with Valve raw HID reports. Xbox and other Linux gamepads can use face buttons, bumpers, and triggers without a device-ID allowlist. Steam Deck raw input preserves L4/R4/L5/R5 and combos mixing grips with other buttons, independently of Steam Input layouts. Original Steam Controllers retain raw L5/R5 grip support on wired/USB receiver connections; other connections can use evdev when a gamepad is exposed by Linux or Steam Input. Generic evdev input follows the exposed gamepad layout; a keyboard/mouse-only Steam Input layout may not expose usable gamepad buttons. Third-party paddles are not universally exposed as distinct buttons, so use a standard-button combo on those controllers. Face-button names follow Xbox positions.
Controllers are detected automatically when connected. Each combo must be held on one input device; disconnecting it releases push-to-talk.
- WoW chat: Dictate messages with correct channel routing (party, raid, guild, etc.)
- Generic text input: Type player names, search terms, chat in any game without a keyboard
- Web browsing: Fill forms, search, comment
- Discord/messaging: Send messages hands-free
- Any text input: Works in any active window using the Generic preset
For improved accuracy with WoW-specific vocabulary, install the included WoW addon and run convert_wow_context.py --watch. It reads your current zone, target, party members, and class/spec from the game and feeds them to Whisper as context.
See doc/TESTING_GUIDE.md for setup instructions.
- Check Decky Loader logs:
/tmp/decky-*.log - Ensure all Python dependencies are installed
- Restart Decky Loader
Decktation uses decky.logger, including forwarded controller-listener output.
Decky writes timestamped .log files in DECKY_PLUGIN_LOG_DIR, normally
/home/deck/homebrew/logs/decktation/. Open the newest file for the current
session; after a plugin restart, Decky creates a new log file.
ls -lt /home/deck/homebrew/logs/decktation/*.logThe plugin no longer creates /tmp/decktation.log. This logging change does
not migrate the controller's separate /tmp state files or change plugin
privileges.
- Ensure the plugin is enabled
- Check that Steam Deck mic is working (test in another app)
- Check
/home/deck/homebrew/logs/decktation/*.logforydotoold ready - Check logs:
/home/deck/homebrew/logs/decktation/*.log
The button preview shows currently held buttons on the backend's selected controller, including triggers, using the same mappings as recording combos. It clears on release and refreshes every 100 ms while the panel is mounted.
Create controller_mappings.json in the plugin settings directory (normally
/home/deck/homebrew/settings/decktation/). Each key is a lowercase hexadecimal
bus:vendor:product ID, with four digits per component. For example, this
explicitly maps X/Y for the Bluetooth Xbox 045e:02fd:
{
"0005:045e:02fd": {
"0x133": "X",
"0x134": "Y"
}
}Values may be A, B, X, Y, L1, R1, L2, or R2. Keys are Linux
evdev button codes written as hex strings (not Steam button IDs). Unspecified
codes retain their built-in mapping. This config overrides digital buttons;
analog trigger axes still use automatic detection. Controllers must expose
the standard A/B capabilities to be discovered.
Use controller diagnostics and Controller input log lines to identify the
device and its raw codes. Restart the plugin or reconnect the controller after
editing; mappings are read when devices open. Invalid overrides are logged and
ignored. USB and Bluetooth IDs can differ, and Steam's virtual controller is a
separate device with its own mapping.
Xbox evdev devices and Steam's virtual Xbox pads use Xbox X/Y button labels; other evdev gamepads use the default positional mapping. Steam Deck raw HID input is decoded separately. Button-change logs include the device path, decoded held buttons, and held evdev key codes to distinguish physical input from Steam's virtual output. If a controller still mismatches, capture separate X and Y presses from the same device before changing its mapping.
- Try a different button combination in the plugin UI
- Rear grip buttons are supported on Steam Deck hardware through raw HID
- Check
/home/deck/homebrew/logs/decktation/*.logfor controller listener errors - Verify controller listener is running:
pgrep -f controller_listener
- Default
basemodel is recommended (good balance) - For faster: use
tinymodel (edit wow_voice_chat.py line 28) - For accuracy: use
smallmodel (slower, needs more resources)
- Speech recognition: faster-whisper (CTranslate2 backend)
- Model: base (150MB, ~2-4s transcription time)
- Input: Steam Deck microphone or connected headset
- Controller input: Linux evdev gamepads plus Valve raw HID for physical Deck controls and grips
- Output: Keyboard simulation via ydotool (bundled)
- Game presets:
defaults/game_presets.json— data-driven, no code changes needed to add games - Dependencies: Pre-bundled Python 3.11 libraries in
lib/folder - Architecture: TypeScript frontend + Python backend + separate controller listener process
npm install # Install Node dependencies (package-lock.json is ignored)
npm run build # Compile TypeScript to dist/index.js
npm run watch # Watch mode for development
# Unit tests (no hardware required)
python3 -m venv .venv && .venv/bin/pip install pytest sentry-sdk==2.66.0
.venv/bin/pytest tests/ -vCI runs unit tests and the TypeScript build on every push via GitHub Actions.
- Voice data stays local. Recording, transcription, chat parsing, and simulated keyboard input run on your Steam Deck. Decktation does not upload audio recordings, transcribed text, or WoW context data.
- Model downloads. The first use of each selected Whisper model downloads its model files from Hugging Face. This requires an internet connection; subsequent use is local while the model remains cached on the device.
- Optional diagnostics. The Diagnostics → Share setting is off by default. When enabled, Decktation sends scrubbed error and performance data to Sentry to help diagnose failures. It excludes audio, transcription text, WoW context, credentials, device/host identifiers, IP addresses, and paths containing the local user name. Diagnostics include the plugin release, error category/type, selected game preset, and whether dictation succeeded. Controller diagnostics include family, vendor/product IDs (model identifiers, not unique serials), USB/Bluetooth/virtual connection, evdev/raw HID backend, configured combo and supported buttons, connected input-source count, first input received, dropped-event recovery count, connection lifecycle, and numeric input errors or listener exit code. Controller names, serial numbers, Bluetooth addresses, and individual button-press streams are not uploaded.
- You can disable diagnostics at any time from the plugin’s Diagnostics section; no diagnostics connection is initialized while the setting is off.
Decktation declares Decky’s _root permission solely for the Steam Deck
devices required by push-to-talk and text input:
- It reads Valve raw controller reports from
/dev/hidraw*so the configured physical button combination works independently of a game’s Steam Input layout. - It also reads gamepad events from
/dev/input/event*for other controllers, without exclusively grabbing devices or changing their mappings. - It runs its bundled
ydotooldhelper against/dev/uinputto simulate the keystrokes that enter the transcription in the active window. The helper uses a private, owner-only socket in/tmpand is stopped when the plugin unloads. - Dictated text is passed as data to
ydotool; it is never evaluated as a shell command. Decktation does not modify the system filesystem, install system packages, or create system services.
Built with:
- faster-whisper - Efficient Whisper implementation
- Decky Loader - Steam Deck plugin framework
MIT
