Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Desktop Voice Control

Say it, and the window manager does it.

Omarchy already turns your voice into text. Desktop Voice Control turns it into actions. Hold F10, say "focus left", "workspace three", "throw this to two", "lock the screen" — and it happens.

Everything runs on your machine. Audio is recorded with pw-record, transcribed by Voxtype (whisper.cpp), matched against a grammar you can edit, and then discarded. No network, no wake word, no always-on microphone: the mic opens when you hold the key and closes when you let go.

Why it isn't dictation

Dictation types what you said into the focused window. Desktop Voice Control never types anything. An utterance either matches a command in the grammar and runs it, or it is thrown away. That is the whole safety model, and it means a misheard sentence can't end up in your code.

F9 dictates words. F10 gives commands. Same hand, same gesture, different verb.

Install

omarchy plugin add https://github.com/RR-CodeBase/omarchy-utter.git --enable

Two things the plugin cannot do for you, both one command:

omarchy voxtype install                                              # local transcription
~/.config/omarchy/plugins/io.github.rr-codebase.utter/install.sh     # the F10 binding

install.sh is safe to re-run and only ever touches its own managed block in ~/.config/hypr/bindings.lua. It also links utter into ~/.local/bin and installs bash completion, which is what makes the bare utter … commands below work. Working from a clone instead? git clone, then ./install.sh — it registers the plugin, places the widget and adds the binding in one go.

Usage

Hold F10, say what you want, release. Click the bar icon for the current state, what was heard last, and an on/off switch; Escape closes the panel. Middle-click the icon to turn voice commands off without opening anything.

utter commands       # everything it understands
utter doctor         # check the install
utter say "focus left"   # try a command without speaking

Configure

omarchy bar move io.github.rr-codebase.utter --section right

The grammar is a JSON file you own at ~/.config/omarchy/utter/commands.json, and settings live beside it. Both are covered below.

Remove

~/.config/omarchy/plugins/io.github.rr-codebase.utter/install.sh --uninstall

That removes the keybinding block, the utter symlink, the completion and the plugin. omarchy plugin remove io.github.rr-codebase.utter on its own removes the plugin but leaves the binding behind. Your grammar and history stay in ~/.config/omarchy/utter/ and ~/.local/state/omarchy/ either way; delete those directories to go back to nothing.

What you can say

utter commands          # the full list, grouped

37 commands out of the box, covering windows, workspaces, apps, sound, display, focus modes, capture and session. A few of them:

Say Runs
"focus left" / "go right" hl.dsp.focus({ direction = "l" })
"workspace three" hl.dsp.focus({ workspace = "3" })
"throw this to two" hl.dsp.window.move({ workspace = "2" })
"close window" hl.dsp.window.close()
"open teams" / "focus on teams" focuses it if open, launches it if not
"take a screenshot" omarchy capture screenshot region
"do not disturb" omarchy toggle notification silencing
"lock the screen" omarchy system lock

Anything destructive — reboot, shut down, sleep — has to be said twice within eight seconds before it runs.

Editing the grammar

The grammar lives at ~/.config/omarchy/utter/commands.json and is yours. Add a command by adding an entry:

{
  "id": "notes.open",
  "group": "Apps",
  "say": ["open my notes", "show my notes", "notes"],
  "run": "omarchy launch or focus obsidian",
  "label": "Open notes"
}

Slots let one entry cover many phrasings. {dir} and {num} ship by default, and you can add your own under slots:

{
  "id": "focus.move",
  "say": ["focus {dir}", "go {dir}"],
  "run": "hyprctl dispatch movefocus {dir}",
  "label": "Focus {dir}"
}

A slot maps what you say to what gets run: "l": ["left"] means saying "left" substitutes l. Only the canonical value ever reaches the command line, so what you say can never inject arguments.

Opening and focusing apps

"open anything", and equally "focus on anything", is resolved against the apps actually installed on your machine — the .desktop entries a menu would show you — matching on the app's name, its binary, its window class and its file id, and tolerating how speech comes out ("one password" finds 1Password, "x journal" finds Xournal++). If nothing matches well enough you get "no app called …" rather than the nearest alphabetical guess, because opening the wrong app is worse than opening none.

If the app already has a window open, Desktop Voice Control focuses it rather than starting a second copy, and says which it did — "Focus Teams" against "Open Teams". The window is found by address from hyprctl clients, matched on the app's declared window class, its binary and its name, because handing a pattern to a launcher gets web apps wrong: Teams' Exec is omarchy-launch-webapp …, and matching windows against that finds nothing and opens a duplicate.

"focus left" stays a direction and "focus teams" becomes an app: where both could match, a known value beats a wildcard. And a near miss on a known command beats a wildcard too, so "focus lefd" is still corrected to left rather than being taken as an app nobody has.

Phrases that name a role rather than an app — "browser", "terminal", "editor" — go through Omarchy's configured default, so they open yours. That table lives in the grammar under appAliases and is yours to extend:

"appAliases": {
  "my notes": { "app": "obsidian" },
  "the shell": { "run": "omarchy launch terminal", "label": "terminal" }
}

This is the one place free speech enters the system, so it is fenced: a free slot ({appname}) may only be used by a command with an internal handler, never by one with a run line, and the handler launches a resolved desktop entry rather than anything the microphone heard. utter doctor enforces both rules, and the test suite asserts them over the whole grammar.

Hyprland commands go through its Lua dispatcher API (hl.dsp.…), not the older hyprctl dispatch workspace 3 form — that one is accepted by the CLI and rejected by the compositor, so it looks like it worked. utter doctor proves the calling convention against the running compositor and refuses to let the legacy form back into the grammar.

After editing, utter doctor re-checks the whole file.

When the shipped grammar changes

The grammar carries a version, and Desktop Voice Control records the fingerprint of the file it installed. When a newer grammar ships:

  • if your file is still exactly what it wrote, it is replaced and the old one kept beside it as commands.v<n>.json;
  • if you have edited it, it is left alone. The new grammar is written beside it as commands.new.json and utter doctor tells you it is there.

An upgrade never walks over your edits, which matters because a stale grammar can be silently broken — the v1 one drove Hyprland with a syntax this Hyprland rejects, so its commands matched, reported success, and did nothing.

How the matching works

Two passes. First an exact match of the normalized utterance against every phrase in the grammar — punctuation, capitalization, filler words ("um", "please") and wake words ("omarchy", "computer") are stripped first. That handles most clean transcriptions in microseconds.

If nothing matches exactly, a fuzzy pass scores the utterance against every phrase and accepts the best one above threshold (0.80 by default). This is what rescues "focus lefd" and "next workspase". Below the threshold you get "Didn't catch that" and nothing runs — including for ordinary conversation picked up by a hot mic, which the test suite pins down explicitly.

Whisper's habit of writing digits, and its homophones, are handled in the grammar itself: "2": ["two", "to", "too", "2"].

Commands

utter ptt start|stop     push-to-talk, for keybindings
utter listen             toggle recording
utter say "focus left"   interpret typed text as if spoken
utter match "focus left" match without running (add --threshold)
utter commands [--json]  list the grammar
utter status [--json]    what the widget reads
utter history            recent utterances and what they matched
utter enable|disable|toggle
utter doctor             check the whole install
utter selftest           record 3s, transcribe, match

utter say is useful beyond testing: bind it to a key and you have a text command palette that shares the grammar.

Settings

~/.config/omarchy/utter/settings.json

Key Default
enabled true master switch
threshold 0.80 fuzzy match floor; raise it if commands misfire
maxSeconds 8 recording safety limit
osd true show the Omarchy OSD on each result
notify false also send a notification
stripWakeWords true ignore a leading "omarchy" / "computer"
voxtype "" path to a specific voxtype binary
model "" override the whisper model

Requirements

  • Omarchy Quattro
  • pipewire (pw-record)
  • Voxtype and a whisper model — omarchy voxtype install
  • Python 3.11+ (standard library only)

Tests

python3 tests/test_grammar.py     # the matcher and the grammar
python3 tests/test_pipeline.py    # real audio through whisper to a process
python3 tests/test_plugin.py      # conformance with the Omarchy plugin guide

215 assertions covering slot canonicalization, homophones, transcription noise, fuzzy tolerance, argument-injection safety, a corpus of ordinary speech that must never match a command, the app resolver, the rule that free speech never reaches a command line, and the phrasings real use turned up. Plus tests/test_pipeline.py, which drives real audio through whisper into the matcher and out to a process.

Licence

MIT. See LICENSE, and NOTICE for what this builds on.

About

Say it, and the window manager does it. Push-to-talk voice commands for Omarchy, transcribed locally by Voxtype and matched against an editable grammar.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages