Add selectable Voxtype and Superwhisper dictation (#14552)
* Add selectable dictation backends * Move dictation migration after repository priority update * Extract backend-specific dictation installers * Keep dictation installer dispatcher executable * Call dictation backend installers directly * Drop legacy Superwhisper migration and runtime cleanup * Separate dictation defaults from generic recording protocol * Add backend-specific dictation removers * Preserve existing Voxtype selection during upgrade * Make dictation backend selection safe and retryable * Show the dictation indicator only for selected Voxtype * Allow switching away from stopped Superwhisper * Remove the built-in bar dictation indicator * Store default dictation selection under defaults * Rename dictation backend selector to use * Move dictation removers into their own menu category
This commit is contained in:
1 parent
1554a522cc
commit
c24889096e
34 files changed
+639
-141
No files matched your search
@@ -46,6 +46,7 @@ GROUP_DESCRIPTIONS[finalize]="Finalize user setup"
|
||||
GROUP_DESCRIPTIONS[default]="Default application selection"
|
||||
GROUP_DESCRIPTIONS[dev]="Omarchy development tools"
|
||||
GROUP_DESCRIPTIONS[disk]="Disk performance helpers"
|
||||
GROUP_DESCRIPTIONS[dictation]="Dictation recording"
|
||||
GROUP_DESCRIPTIONS[display]="Display and text scaling"
|
||||
GROUP_DESCRIPTIONS[dns]="DNS resolver configuration"
|
||||
GROUP_DESCRIPTIONS[drive]="Drive selection and encryption"
|
||||
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Get or set the default dictation backend
|
||||
# omarchy:args=[backend]
|
||||
# omarchy:examples=omarchy default dictation | omarchy default dictation superwhisper
|
||||
|
||||
if (($# > 1)); then
|
||||
echo "Usage: omarchy default dictation [backend]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if (($# == 0)); then
|
||||
config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
|
||||
if [[ ! -s $config ]]; then
|
||||
echo "No dictation backend configured. Select one in Setup > Defaults > Dictation." >&2
|
||||
exit 1
|
||||
fi
|
||||
read -r backend < "$config"
|
||||
else
|
||||
backend=$1
|
||||
fi
|
||||
|
||||
if [[ ! $backend =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
|
||||
echo "Invalid dictation backend: $backend" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if (($# == 0)); then
|
||||
echo "$backend"
|
||||
else
|
||||
installer="omarchy-install-dictation-$backend"
|
||||
if omarchy-cmd-missing "$installer"; then
|
||||
echo "No installer available for dictation backend: $backend" >&2
|
||||
exit 1
|
||||
fi
|
||||
omarchy-launch-floating-terminal-with-presentation "$installer"
|
||||
fi
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Record dictation with the configured backend
|
||||
# omarchy:args=<start|stop|toggle>
|
||||
# omarchy:examples=omarchy dictation start | omarchy dictation toggle
|
||||
|
||||
if (($# != 1)) || [[ $1 != "start" && $1 != "stop" && $1 != "toggle" ]]; then
|
||||
echo "Usage: omarchy dictation <start|stop|toggle>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
|
||||
mkdir -p "${config%/*}"
|
||||
exec 9> "$config.lock"
|
||||
if ! flock -w 5 9; then
|
||||
echo "Dictation is busy; try again." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
backend=$(omarchy-default-dictation) || exit 1
|
||||
adapter="omarchy-dictation-$backend"
|
||||
if omarchy-cmd-missing "$adapter"; then
|
||||
echo "The selected dictation backend ($backend) is not installed." >&2
|
||||
exit 1
|
||||
fi
|
||||
"$adapter" "$1" 9>&-
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Record dictation using Superwhisper
|
||||
# omarchy:args=<start|stop|toggle>
|
||||
|
||||
case "${1:-}" in
|
||||
start) superwhisper start ;;
|
||||
stop)
|
||||
if omarchy-cmd-missing superwhisper || ! systemctl --user is-active --quiet superwhisper.service; then
|
||||
exit 0
|
||||
else
|
||||
superwhisper stop
|
||||
fi
|
||||
;;
|
||||
toggle) superwhisper record ;;
|
||||
*) echo "Usage: omarchy dictation superwhisper <start|stop|toggle>" >&2; exit 1 ;;
|
||||
esac
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:hidden=true
|
||||
# omarchy:summary=Save a configured dictation backend after stopping the previous backend
|
||||
# omarchy:args=<backend>
|
||||
|
||||
set -e
|
||||
|
||||
if (($# != 1)) || [[ ! $1 =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
|
||||
echo "Usage: omarchy-dictation-use <backend>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
|
||||
mkdir -p "${config%/*}"
|
||||
exec 9> "$config.lock"
|
||||
if ! flock -w 5 9; then
|
||||
echo "Dictation is busy; try again." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
previous=$(omarchy-default-dictation 2>/dev/null) || previous=""
|
||||
if [[ -n $previous && $previous != "$1" ]] && omarchy-cmd-present "omarchy-dictation-$previous"; then
|
||||
if ! "omarchy-dictation-$previous" stop 9>&-; then
|
||||
echo "Could not stop $previous; keeping the existing dictation backend." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
temporary=$(mktemp "$config.XXXXXX")
|
||||
trap 'rm -f "$temporary"' EXIT
|
||||
printf '%s\n' "$1" > "$temporary"
|
||||
mv -f "$temporary" "$config"
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Record dictation using Voxtype
|
||||
# omarchy:args=<start|stop|toggle>
|
||||
|
||||
case "${1:-}" in
|
||||
start|toggle) voxtype record "$1" ;;
|
||||
stop)
|
||||
if omarchy-cmd-missing voxtype || [[ $(voxtype status) == "stopped" ]]; then
|
||||
exit 0
|
||||
else
|
||||
voxtype record stop
|
||||
fi
|
||||
;;
|
||||
*) echo "Usage: omarchy dictation voxtype <start|stop|toggle>" >&2; exit 1 ;;
|
||||
esac
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Install and configure Superwhisper dictation
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
|
||||
omarchy-pkg-add superwhisper-bin
|
||||
bash /usr/share/superwhisper/setup-user
|
||||
# Superwhisper requires a native recording shortcut; keep it separate from
|
||||
# Omarchy's push-to-talk keys.
|
||||
superwhisper shortcuts set toggle "Alt+Space" >/dev/null
|
||||
superwhisper shortcuts set hold none >/dev/null
|
||||
omarchy-dictation-use superwhisper
|
||||
hyprctl reload >/dev/null
|
||||
omarchy-notification-send -g "Superwhisper Dictation Ready" "Hold Right Alt or F9 to dictate (or toggle with Super + Ctrl + X)." -t 10000
|
||||
superwhisper settings
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Install and configure Voxtype dictation
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
|
||||
if omarchy-cmd-missing voxtype; then
|
||||
if gum confirm "Install Voxtype + AI model (~150MB) to enable dictation?"; then
|
||||
omarchy-pkg-add wtype voxtype-bin
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
mkdir -p "$config_home/voxtype"
|
||||
if [[ ! -e $config_home/voxtype/config.toml ]]; then
|
||||
cp "$OMARCHY_PATH/default/voxtype/config.toml" "$config_home/voxtype/"
|
||||
if omarchy-hw-vulkan; then
|
||||
voxtype setup gpu --enable || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Setup reuses the configured model and downloads it only when missing.
|
||||
# Retry these steps even when an earlier attempt already installed the binary.
|
||||
voxtype setup --download --no-post-install
|
||||
voxtype setup systemd
|
||||
systemctl --user enable --now voxtype.service
|
||||
|
||||
omarchy-dictation-use voxtype
|
||||
hyprctl reload >/dev/null
|
||||
omarchy-notification-send -g "Voxtype Dictation Ready" "Hold Right Alt or F9 to dictate (or toggle with Super + Ctrl + X)." -t 10000
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Remove Superwhisper dictation and its desktop integration
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
|
||||
config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||
data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||
|
||||
systemctl --user disable --now superwhisper.service 2>/dev/null || true
|
||||
omarchy-shell -q shell setPluginEnabled superwhisper.panel false >/dev/null
|
||||
omarchy-pkg-drop superwhisper-bin
|
||||
systemctl --user daemon-reload
|
||||
|
||||
# Remove the package setup's links, preserving independently installed copies
|
||||
# and the user's preferences, history, and models.
|
||||
unlink_package_link() {
|
||||
if [[ -L $1 && $(readlink "$1") == "$2" ]]; then
|
||||
rm "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
unlink_package_link "$HOME/.local/bin/superwhisper" /usr/bin/superwhisper
|
||||
unlink_package_link "$data_home/superwhisper/app/current" /opt/superwhisper
|
||||
unlink_package_link "$config_home/omarchy/plugins/superwhisper-panel" /opt/superwhisper/assets/omarchy-plugin/superwhisper-panel
|
||||
for agent in .agents .claude .codex .pi/agent; do
|
||||
unlink_package_link "$HOME/$agent/skills/superwhisper" /opt/superwhisper/assets/agent-skill/superwhisper
|
||||
done
|
||||
|
||||
backend_config="$config_home/omarchy/defaults/dictation"
|
||||
if [[ -f $backend_config && $(< "$backend_config") == "superwhisper" ]]; then
|
||||
rm "$backend_config"
|
||||
fi
|
||||
|
||||
omarchy-shell -q shell rescanPlugins
|
||||
hyprctl reload >/dev/null
|
||||
if systemctl --user -q is-active omarchy-fcitx5.service; then
|
||||
systemctl --user restart omarchy-fcitx5.service
|
||||
fi
|
||||
@@ -18,6 +18,10 @@ if omarchy-cmd-present voxtype; then
|
||||
omarchy-pkg-drop voxtype-bin
|
||||
rm -rf ~/.config/voxtype
|
||||
rm -rf ~/.local/share/voxtype
|
||||
backend_config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
|
||||
if [[ -f $backend_config && $(< "$backend_config") == "voxtype" ]]; then
|
||||
rm "$backend_config"
|
||||
fi
|
||||
hyprctl reload >/dev/null
|
||||
else
|
||||
echo "Voxtype was not installed."
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Install and configure Voxtype dictation
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
|
||||
# Install voxtype and configure it for use.
|
||||
|
||||
if gum confirm "Install Voxtype + AI model (~150MB) to enable dictation?"; then
|
||||
omarchy-pkg-add wtype voxtype-bin
|
||||
|
||||
# Setup voxtype
|
||||
mkdir -p ~/.config/voxtype
|
||||
cp "$OMARCHY_PATH/default/voxtype/config.toml" ~/.config/voxtype/
|
||||
|
||||
voxtype setup --download --no-post-install
|
||||
if omarchy-hw-vulkan; then
|
||||
voxtype setup gpu --enable || true
|
||||
fi
|
||||
voxtype setup systemd
|
||||
|
||||
hyprctl reload >/dev/null
|
||||
omarchy-restart-shell
|
||||
omarchy-notification-send -g "Voxtype Dictation Ready" "Hold F9 to dictate (or toggle with Super + Ctrl + X)." -t 10000
|
||||
fi
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Stream voxtype --follow status as bar-friendly JSON
|
||||
|
||||
if omarchy-cmd-missing voxtype; then
|
||||
echo '{"alt": "", "class": "idle", "tooltip": ""}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Keep the status follower as the direct child of the bar. Quickshell
|
||||
# clean up their direct child when they restart; a shell pipeline/coproc would
|
||||
# leave `voxtype status --follow` orphaned under the user systemd instance.
|
||||
exec setpriv --pdeathsig TERM voxtype status --follow --extended --format json
|
||||
@@ -0,0 +1,9 @@
|
||||
if o.shell_succeeds("omarchy-default-dictation") then
|
||||
o.bind("SUPER + CTRL + X", "Toggle dictation", "omarchy-dictation toggle")
|
||||
o.bind("F9", "Start dictation (push-to-talk)", "omarchy-dictation start")
|
||||
o.bind("F9", "Stop dictation (push-to-talk)", "omarchy-dictation stop", { release = true })
|
||||
-- A modifier's mask changes between its press and release. Match the keysym
|
||||
-- independently of that mask; AltGr layouts use a different keysym.
|
||||
o.bind("ALT + Alt_R", "Start dictation (push-to-talk)", "omarchy-dictation start", { ignore_mods = true })
|
||||
o.bind("ALT + Alt_R", "Stop dictation (push-to-talk)", "omarchy-dictation stop", { release = true, ignore_mods = true })
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
if o.cmd_present("voxtype") then
|
||||
o.bind("SUPER + CTRL + X", "Toggle dictation", "voxtype record toggle")
|
||||
o.bind("F9", "Start dictation (push-to-talk)", "voxtype record start")
|
||||
o.bind("F9", "Stop dictation (push-to-talk)", "voxtype record stop", { release = true })
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
-- Backends may supply their own desktop integration without editing the
|
||||
-- user's Hyprland config. Recording bindings remain owned by Omarchy.
|
||||
local paths = require("default.hypr.paths")
|
||||
local pipe = io.popen("omarchy-default-dictation 2>/dev/null", "r")
|
||||
if not pipe then
|
||||
return
|
||||
end
|
||||
|
||||
local backend = pipe:read("*l")
|
||||
pipe:close()
|
||||
|
||||
if not backend or not backend:match("^[%w%-]+$") or o.cmd_missing("omarchy-dictation-" .. backend) then
|
||||
return
|
||||
end
|
||||
|
||||
local integration = paths.config_home .. "/" .. backend .. "/shortcuts.lua"
|
||||
local file = io.open(integration, "r")
|
||||
if file then
|
||||
file:close()
|
||||
local ok, error = pcall(dofile, integration)
|
||||
if not ok then
|
||||
print("Could not load dictation backend " .. backend .. ": " .. tostring(error))
|
||||
end
|
||||
end
|
||||
@@ -10,7 +10,7 @@ if _G.omarchy_default_bindings ~= false then
|
||||
require("default.hypr.bindings.clipboard")
|
||||
require("default.hypr.bindings.tiling")
|
||||
require("default.hypr.bindings.utilities")
|
||||
require("default.hypr.bindings.voxtype")
|
||||
require("default.hypr.bindings.dictation")
|
||||
require_optional.module("default.hypr.bindings.applications")
|
||||
end
|
||||
require("default.hypr.envs")
|
||||
@@ -18,6 +18,7 @@ require("default.hypr.looknfeel")
|
||||
require("default.hypr.qconsole")
|
||||
require("default.hypr.input")
|
||||
require("default.hypr.windows")
|
||||
require("default.hypr.dictation-backend")
|
||||
|
||||
-- Current theme overrides.
|
||||
require_optional.module("omarchy.current.theme.hyprland")
|
||||
@@ -159,6 +159,9 @@
|
||||
"setup.default.browser.edge": {"icon":"","label":"Edge","checked":"[[ \"$(omarchy-default-browser)\" == \"edge\" ]]","action":"omarchy-default-browser edge"},
|
||||
"setup.default.browser.firefox": {"icon":"","label":"Firefox","checked":"[[ \"$(omarchy-default-browser)\" == \"firefox\" ]]","action":"omarchy-default-browser firefox"},
|
||||
"setup.default.browser.zen": {"icon":"","label":"Zen","checked":"[[ \"$(omarchy-default-browser)\" == \"zen\" ]]","action":"omarchy-default-browser zen"},
|
||||
"setup.default.dictation": {"icon":"","label":"Dictation","title":"Default Dictation"},
|
||||
"setup.default.dictation.voxtype": {"icon":"","label":"Voxtype","checked":"[[ \"$(omarchy-default-dictation)\" == \"voxtype\" ]]","action":"omarchy-default-dictation voxtype"},
|
||||
"setup.default.dictation.superwhisper": {"icon":"","label":"Superwhisper","checked":"[[ \"$(omarchy-default-dictation)\" == \"superwhisper\" ]]","action":"omarchy-default-dictation superwhisper"},
|
||||
"setup.default.terminal": {"icon":"","label":"Terminal","title":"Default Terminal"},
|
||||
"setup.default.terminal.alacritty": {"icon":"","label":"Alacritty","checked":"[[ \"$(omarchy-default-terminal)\" == \"alacritty\" ]]","action":"omarchy-default-terminal alacritty"},
|
||||
"setup.default.terminal.foot": {"icon":"","label":"Foot","checked":"[[ \"$(omarchy-default-terminal)\" == \"foot\" ]]","action":"omarchy-default-terminal foot"},
|
||||
@@ -247,7 +250,6 @@
|
||||
"install.terminal.kitty": {"icon":"","label":"Kitty","disabled":"omarchy-pkg-present kitty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal kitty'"},
|
||||
"install.ai.chatgpt": {"icon":"","iconFont":"omarchy","label":"ChatGPT Desktop","disabled":"omarchy-pkg-present openai-codex-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-ai-chatgpt"},
|
||||
"install.ai.claude": {"icon":"","iconFont":"omarchy","label":"Claude Desktop","disabled":"omarchy-pkg-present claude-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-ai-claude"},
|
||||
"install.ai.dictation": {"icon":"","label":"Dictation","disabled":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install"},
|
||||
"install.ai.grok-bot": {"icon":"","iconFont":"omarchy","label":"Grok Bot","disabled":"omarchy-pkg-present grok-bot","action":"omarchy-install-and-launch 'Grok Bot' grok-bot grok-bot"},
|
||||
"install.ai.hermes": {"icon":"","iconFont":"omarchy","label":"Hermes Desktop","disabled":"omarchy-pkg-present hermes-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-ai-hermes"},
|
||||
"install.ai.lm-studio": {"icon":"","iconFont":"omarchy","label":"LM Studio","disabled":"omarchy-pkg-present lmstudio-bin","action":"omarchy-install-app 'LM Studio' lmstudio-bin"},
|
||||
@@ -291,6 +293,7 @@
|
||||
// Remove
|
||||
"remove.package": {"icon":"","label":"Package","action":"xdg-terminal-exec --app-id=org.omarchy.terminal omarchy-pkg-remove"},
|
||||
"remove.ai": {"icon":"","label":"AI","title":"Remove"},
|
||||
"remove.dictation": {"icon":"","label":"Dictation","when":"omarchy-pkg-present voxtype-bin || omarchy-pkg-present superwhisper-bin"},
|
||||
"remove.service": {"icon":"","label":"Services","title":"Remove"},
|
||||
"remove.development": {"icon":"","label":"Development","title":"Remove"},
|
||||
"remove.theme": {"icon":"","label":"Theme","action":"omarchy-theme-remove"},
|
||||
@@ -318,7 +321,8 @@
|
||||
"remove.service.ssh-agent": {"icon":"","label":"SSH Agent","when":"systemctl --user -q is-enabled gcr-ssh-agent.socket","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-service-ssh-agent"},
|
||||
"remove.ai.chatgpt": {"icon":"","iconFont":"omarchy","label":"ChatGPT Desktop","when":"omarchy-pkg-present openai-codex-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-chatgpt"},
|
||||
"remove.ai.claude": {"icon":"","iconFont":"omarchy","label":"Claude Desktop","when":"omarchy-pkg-present claude-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-claude"},
|
||||
"remove.ai.dictation": {"icon":"","label":"Dictation","when":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-remove"},
|
||||
"remove.dictation.voxtype": {"icon":"","label":"Voxtype","when":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-dictation-voxtype"},
|
||||
"remove.dictation.superwhisper": {"icon":"","label":"Superwhisper","when":"omarchy-pkg-present superwhisper-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-dictation-superwhisper"},
|
||||
"remove.ai.grok-bot": {"icon":"","iconFont":"omarchy","label":"Grok Bot","when":"omarchy-pkg-present grok-bot","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-grok-bot"},
|
||||
"remove.ai.lm-studio": {"icon":"","iconFont":"omarchy","label":"LM Studio","when":"omarchy-pkg-present lmstudio-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-lm-studio"},
|
||||
"remove.ai.ollama": {"icon":"","iconFont":"omarchy","label":"Ollama","when":"omarchy-pkg-present ollama","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-ollama"},
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
state_file = "auto"
|
||||
|
||||
[hotkey]
|
||||
# Hotkey is configured in Hyprland. Default is Super + Ctrl + X
|
||||
# Hotkeys are configured in Hyprland: hold Right Alt or F9, or toggle with Super + Ctrl + X.
|
||||
enabled = false
|
||||
|
||||
[audio]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
set -e
|
||||
|
||||
if omarchy-done ensure voxtype-install-invitation; then
|
||||
omarchy-notification-send -u critical -g "Install Dictation with Voxtype" \
|
||||
omarchy-notification-send -u critical -g "Install Dictation" \
|
||||
"Click to install voice dictation for Omarchy." \
|
||||
--exec omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install
|
||||
--exec omarchy-menu summon setup.default.dictation
|
||||
fi
|
||||
@@ -150,8 +150,8 @@ Usually on Linux, you need `Ctrl + Shift + C/V` to copy'n'paste in the terminal
|
||||
| `Super + Alt + ]` | Make webcam overlay larger while recording |
|
||||
| `Alt + Shift + L` | Copy current URL from webapp or Chromium |
|
||||
| `Alt + Shift + D` | Download the video on the current page to `~/Videos` |
|
||||
| `Super + Ctrl + X` | Start/stop dictation (requires _Install > AI > Dictation_) |
|
||||
| `F9` | Push-to-talk dictation (requires _Install > AI > Dictation_) |
|
||||
| `Super + Ctrl + X` | Start/stop dictation (requires _Setup > Defaults > Dictation_) |
|
||||
| `Right Alt` / `F9` | Push-to-talk dictation (requires Voxtype or Superwhisper) |
|
||||
|
||||
With screenrecordings, the hotkey first asks which audio you want, then starts recording. Hit it again to stop. See [screenshots and recording](12-screenshots-recording.md) for the details.
|
||||
|
||||
|
||||
@@ -10,6 +10,14 @@ This is very helpful for grabbing addresses out of image footers or phone number
|
||||
|
||||
### Dictation
|
||||
|
||||
Omarchy offers AI dictation via [Voxtype](https://voxtype.io/). You install it via _Install > AI > Dictation_ through the Omarchy menu. By default, it'll load a base English model that takes up 150MB. But you can tweak which model you'd like to use by running `voxtype setup model` in the terminal. And you can tweak all the settings via `~/.config/voxtype/config.toml`.
|
||||
Install [Voxtype](https://voxtype.io/) or [Superwhisper](https://superwhisper.com/) through _Setup > Defaults > Dictation_ in the Omarchy menu. Selecting a backend installs it if needed and makes it the default for dictation. Voxtype loads a base English model that takes up 150MB; change the model with `voxtype setup model` and other settings through `~/.config/voxtype/config.toml`. Superwhisper opens its settings to choose cloud or local processing, and OPR keeps the application updated.
|
||||
|
||||
Once installed, you dictate by holding down `F9` or by toggling with `Super + Ctrl + X`, and the dictated text will appear in the focused input area.
|
||||
Once installed, you dictate by holding down `Right Alt` or `F9`, or by toggling with `Super + Ctrl + X`, and the dictated text will appear in the focused input area. These shortcuts are enabled only after selecting a backend. Right Alt also starts dictation when used in a modifier chord; use Left Alt for those chords. On keyboard layouts where Right Alt is AltGr, use `F9` to keep AltGr available for typing.
|
||||
|
||||
The same shortcuts can use an installed Superwhisper instead. Select your backend through Setup > Defaults > Dictation. The backend installer configures it and saves the selection. Run `omarchy default dictation` to see which one is selected. The selection is saved in `~/.config/omarchy/defaults/dictation`. Dictation requires an explicit selection.
|
||||
|
||||
Omarchy loads the selected backend's desktop integration automatically. No Superwhisper configuration is needed in `~/.config/hypr/`.
|
||||
|
||||
Custom bindings and scripts can use `omarchy dictation start`, `omarchy dictation stop`, and `omarchy dictation toggle`. The selected backend handles the recording and transcription. Selecting Superwhisper disables its native hold shortcut and sets its native toggle to `Alt + Space`, keeping it separate from Omarchy's recording keys. Superwhisper requires at least one native recording shortcut, and its cancellation shortcut stays available.
|
||||
|
||||
Additional backends can integrate without changing Omarchy. Provide an executable named `omarchy-dictation-<backend>` on PATH that accepts `start`, `stop`, and `toggle` and returns promptly, with a nonzero status on failure. Its installer configures the provider and calls `omarchy-dictation-use <backend>` after successful setup. This stops the previous backend and saves the selection atomically; `stop` must succeed when the provider is idle or unavailable. Backend names use lowercase letters, digits, and hyphens. Provide `omarchy-install-dictation-<backend>` for setup; `omarchy default dictation <backend>` launches it. Add a Defaults menu entry through an Omarchy menu extension to call that selector. Optional desktop integration can live in `${XDG_CONFIG_HOME:-$HOME/.config}/<backend>/shortcuts.lua`.
|
||||
@@ -0,0 +1,9 @@
|
||||
echo "Preserve Voxtype as the default for existing dictation users"
|
||||
|
||||
backend_config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
|
||||
if [[ ! -e $backend_config ]] && omarchy-pkg-present voxtype-bin; then
|
||||
mkdir -p "${backend_config%/*}"
|
||||
temporary=$(mktemp "$backend_config.XXXXXX")
|
||||
printf '%s\n' voxtype > "$temporary"
|
||||
mv -f "$temporary" "$backend_config"
|
||||
fi
|
||||
@@ -1,38 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs.Ui
|
||||
|
||||
BarIndicator {
|
||||
id: root
|
||||
|
||||
property string state: "idle"
|
||||
property string icon: ""
|
||||
|
||||
active: state === "recording"
|
||||
activeText: icon
|
||||
inactiveText: ""
|
||||
activeTooltipText: state
|
||||
inactiveTooltipText: "Dictate"
|
||||
|
||||
function update(raw) {
|
||||
var data = extractData(raw)
|
||||
|
||||
state = String(data.alt || data.class || "idle")
|
||||
if (state === "recording") icon = ""
|
||||
else if (state === "transcribing") icon = ""
|
||||
else icon = ""
|
||||
}
|
||||
|
||||
Process {
|
||||
command: ["bash", "-c", "omarchy-voxtype-status"]
|
||||
running: true
|
||||
stdout: SplitParser {
|
||||
onRead: function(data) { root.update(data) }
|
||||
}
|
||||
}
|
||||
|
||||
onPressed: function() {
|
||||
if (!root.bar) return
|
||||
root.bar.run("omarchy-voxtype-config")
|
||||
}
|
||||
}
|
||||
@@ -31,11 +31,6 @@
|
||||
"label": "Passwordless sudo",
|
||||
"description": "Warning while passwordless root access is enabled"
|
||||
},
|
||||
{
|
||||
"value": "Dictation",
|
||||
"label": "Dictation",
|
||||
"description": "Voice typing status"
|
||||
},
|
||||
{
|
||||
"value": "ScreenRecording",
|
||||
"label": "Screen recording",
|
||||
|
||||
@@ -8,7 +8,7 @@ BarWidget {
|
||||
id: root
|
||||
moduleName: "omarchy.indicators"
|
||||
|
||||
readonly property var defaultIndicatorEntries: [ "PasswordlessSudo", "Dictation", "ScreenRecording", "Reminder", "NightLight", "Dnd", "StayAwake" ]
|
||||
readonly property var defaultIndicatorEntries: [ "PasswordlessSudo", "ScreenRecording", "Reminder", "NightLight", "Dnd", "StayAwake" ]
|
||||
readonly property var indicatorEntries: indicatorEntriesFromSettings(settings)
|
||||
property var activeIndicatorIds: []
|
||||
property var indicatorActiveStates: ({})
|
||||
|
||||
@@ -398,6 +398,7 @@ var GUARD_READERS = [
|
||||
"omarchy-default-browser",
|
||||
"omarchy-default-editor",
|
||||
"omarchy-default-terminal",
|
||||
"omarchy-default-dictation",
|
||||
"omarchy-dns"
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh"
|
||||
|
||||
test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
export HOME="$test_tmp/home" XDG_CONFIG_HOME="$test_tmp/config" OMARCHY_PATH="$ROOT"
|
||||
export DICTATION_LOG="$test_tmp/calls" DICTATION_INSTALLED="voxtype superwhisper"
|
||||
mkdir -p "$test_tmp/bin" "$XDG_CONFIG_HOME/omarchy/defaults"
|
||||
export PATH="$test_tmp/bin:$ROOT/bin:$PATH"
|
||||
config="$XDG_CONFIG_HOME/omarchy/defaults/dictation"
|
||||
|
||||
cat > "$test_tmp/bin/omarchy-cmd-present" <<'SH'
|
||||
#!/bin/bash
|
||||
case "$1" in
|
||||
omarchy-dictation-*|omarchy-install-dictation-*) command -v "$1" >/dev/null ;;
|
||||
*) [[ " $DICTATION_INSTALLED " == *" $1 "* ]] ;;
|
||||
esac
|
||||
SH
|
||||
cat > "$test_tmp/bin/omarchy-cmd-missing" <<'SH'
|
||||
#!/bin/bash
|
||||
! omarchy-cmd-present "$1"
|
||||
SH
|
||||
cat > "$test_tmp/bin/omarchy-launch-floating-terminal-with-presentation" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "$*" > "$DICTATION_INSTALL_LOG"
|
||||
SH
|
||||
export DICTATION_INSTALL_LOG="$test_tmp/install"
|
||||
for backend in voxtype superwhisper; do
|
||||
cat > "$test_tmp/bin/$backend" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s %s\n' "${0##*/}" "$*" >> "$DICTATION_LOG"
|
||||
if [[ $1 == "status" ]]; then echo "${VOXTYPE_STATUS:-idle}"; exit 0; fi
|
||||
if [[ -n ${FAIL_SETUP_STEP:-} && $* == "$FAIL_SETUP_STEP" ]]; then exit 7; fi
|
||||
exit "${DICTATION_EXIT:-0}"
|
||||
SH
|
||||
done
|
||||
for command in omarchy-pkg-add omarchy-notification-send hyprctl omarchy-restart-shell; do
|
||||
cat > "$test_tmp/bin/$command" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s %s\n' "${0##*/}" "$*" >> "$DICTATION_LOG"
|
||||
exit "${SETUP_EXIT:-0}"
|
||||
SH
|
||||
done
|
||||
cat > "$test_tmp/bin/systemctl" <<'SH'
|
||||
#!/bin/bash
|
||||
if [[ $* == "--user is-active --quiet superwhisper.service" ]]; then
|
||||
[[ ${DICTATION_SERVICE_ACTIVE:-1} == 1 ]]
|
||||
else
|
||||
printf '%s %s\n' "${0##*/}" "$*" >> "$DICTATION_LOG"
|
||||
exit "${SERVICE_EXIT:-0}"
|
||||
fi
|
||||
SH
|
||||
cat > "$test_tmp/bin/omarchy-hw-vulkan" <<'SH'
|
||||
#!/bin/bash
|
||||
exit 1
|
||||
SH
|
||||
cat > "$test_tmp/bin/gum" <<'SH'
|
||||
#!/bin/bash
|
||||
exit "${CONFIRM_EXIT:-0}"
|
||||
SH
|
||||
cat > "$test_tmp/bin/bash" <<'SH'
|
||||
#!/bin/bash
|
||||
if [[ $1 == "/usr/share/superwhisper/setup-user" ]]; then
|
||||
printf '%s\n' setup-user >> "$DICTATION_LOG"
|
||||
exit "${SETUP_EXIT:-0}"
|
||||
else
|
||||
exec /bin/bash "$@"
|
||||
fi
|
||||
SH
|
||||
chmod +x "$test_tmp/bin/"*
|
||||
|
||||
if omarchy-dictation start 2> "$test_tmp/error"; then
|
||||
fail "unset backend must fail even when providers are installed"
|
||||
fi
|
||||
[[ ! -e $DICTATION_LOG ]] || fail "recording never configures or installs a backend"
|
||||
pass "dictation requires explicit configuration"
|
||||
omarchy-default-dictation superwhisper
|
||||
[[ $(cat "$DICTATION_INSTALL_LOG") == "omarchy-install-dictation-superwhisper" ]] || fail "Defaults launches backend installer"
|
||||
[[ ! -e $config ]] || fail "Defaults does not select before setup succeeds"
|
||||
if omarchy-default-dictation unknown > "$test_tmp/output" 2>&1; then fail "missing installer must fail"; fi
|
||||
if omarchy-default-dictation '../invalid' > "$test_tmp/output" 2>&1; then fail "invalid backend must fail"; fi
|
||||
pass "Defaults delegates setup without prematurely selecting a backend"
|
||||
|
||||
|
||||
for backend in voxtype superwhisper; do
|
||||
printf '%s\n' "$backend" > "$config"
|
||||
[[ $(omarchy-default-dictation) == "$backend" ]] || fail "configured backend is readable"
|
||||
: > "$DICTATION_LOG"
|
||||
for action in start stop toggle; do omarchy dictation "$action"; done
|
||||
if [[ $backend == "voxtype" ]]; then
|
||||
expected=$'voxtype record start\nvoxtype status\nvoxtype record stop\nvoxtype record toggle'
|
||||
else
|
||||
expected=$'superwhisper start\nsuperwhisper stop\nsuperwhisper record'
|
||||
fi
|
||||
[[ $(cat "$DICTATION_LOG") == "$expected" ]] || fail "adapter dispatches recording for $backend"
|
||||
done
|
||||
pass "built-in adapters implement start stop and toggle"
|
||||
|
||||
cat > "$test_tmp/bin/omarchy-dictation-future-backend" <<'SH'
|
||||
#!/bin/bash
|
||||
[[ ! -e /proc/$$/fd/9 ]] || exit 90
|
||||
printf '%s\n' "$1" >> "$DICTATION_LOG"
|
||||
exit "${DICTATION_EXIT:-0}"
|
||||
SH
|
||||
chmod +x "$test_tmp/bin/omarchy-dictation-future-backend"
|
||||
cat > "$test_tmp/bin/omarchy-install-dictation-future-backend" <<'SH'
|
||||
#!/bin/bash
|
||||
exit 0
|
||||
SH
|
||||
chmod +x "$test_tmp/bin/omarchy-install-dictation-future-backend"
|
||||
omarchy-default-dictation future-backend
|
||||
[[ $(cat "$DICTATION_INSTALL_LOG") == "omarchy-install-dictation-future-backend" ]] || fail "third backend installer requires no selector changes"
|
||||
printf '%s\n' future-backend > "$config"
|
||||
: > "$DICTATION_LOG"
|
||||
for action in start stop toggle; do omarchy-dictation "$action"; done
|
||||
[[ $(cat "$DICTATION_LOG") == $'start\nstop\ntoggle' ]] || fail "third backend requires no dispatcher changes"
|
||||
result=0
|
||||
DICTATION_EXIT=7 omarchy-dictation start || result=$?
|
||||
(( result == 7 )) || fail "backend failures propagate"
|
||||
rm "$test_tmp/bin/omarchy-dictation-future-backend"
|
||||
if omarchy-dictation start 2> "$test_tmp/error"; then fail "missing adapter must fail"; fi
|
||||
printf '%s\n' '../invalid' > "$config"
|
||||
if omarchy-dictation start 2> "$test_tmp/error"; then fail "invalid backend must fail"; fi
|
||||
pass "external adapters follow the same protocol and errors propagate"
|
||||
|
||||
printf '%s\n' voxtype > "$config"
|
||||
if SETUP_EXIT=1 omarchy-install-dictation-superwhisper > "$test_tmp/output" 2>&1; then fail "failed installation must fail"; fi
|
||||
[[ $(cat "$config") == "voxtype" ]] || fail "failed setup must preserve selection"
|
||||
if DICTATION_EXIT=7 omarchy-install-dictation-superwhisper > "$test_tmp/output" 2>&1; then fail "failed shortcut setup must fail"; fi
|
||||
[[ $(cat "$config") == "voxtype" ]] || fail "failed shortcuts must preserve selection"
|
||||
omarchy-install-dictation-superwhisper
|
||||
[[ $(omarchy-default-dictation) == "superwhisper" ]] || fail "installer selects Superwhisper"
|
||||
pass "Superwhisper installer owns setup and saves selection only after success"
|
||||
|
||||
DICTATION_INSTALLED=superwhisper
|
||||
CONFIRM_EXIT=1 omarchy-install-dictation-voxtype
|
||||
[[ $(cat "$config") == "superwhisper" ]] || fail "cancelled installation preserves selection"
|
||||
omarchy-install-dictation-voxtype
|
||||
[[ $(omarchy-default-dictation) == "voxtype" ]] || fail "installer selects Voxtype"
|
||||
DICTATION_INSTALLED="voxtype superwhisper"
|
||||
: > "$DICTATION_LOG"
|
||||
omarchy-install-dictation-voxtype
|
||||
grep -q 'voxtype setup --download --no-post-install' "$DICTATION_LOG" || fail "installed Voxtype retries model setup"
|
||||
printf '%s\n' 'custom model configuration' > "$XDG_CONFIG_HOME/voxtype/config.toml"
|
||||
printf '%s\n' superwhisper > "$config"
|
||||
for step in 'setup --download --no-post-install' 'setup systemd'; do
|
||||
if FAIL_SETUP_STEP="$step" omarchy-install-dictation-voxtype > "$test_tmp/output" 2>&1; then fail "incomplete setup must fail"; fi
|
||||
[[ $(cat "$config") == "superwhisper" ]] || fail "incomplete setup preserves selection"
|
||||
done
|
||||
if SERVICE_EXIT=1 omarchy-install-dictation-voxtype > "$test_tmp/output" 2>&1; then fail "service failure must fail"; fi
|
||||
[[ $(cat "$config") == "superwhisper" ]] || fail "service failure preserves selection"
|
||||
omarchy-install-dictation-voxtype
|
||||
[[ $(cat "$config") == "voxtype" ]] || fail "retry finishes installed Voxtype setup"
|
||||
[[ $(cat "$XDG_CONFIG_HOME/voxtype/config.toml") == "custom model configuration" ]] || fail "retry preserves custom configuration"
|
||||
pass "Voxtype retries incomplete model and service setup without replacing configuration"
|
||||
|
||||
: > "$DICTATION_LOG"
|
||||
if DICTATION_EXIT=7 omarchy-dictation-use superwhisper > "$test_tmp/output" 2>&1; then fail "failed stop must prevent switching"; fi
|
||||
[[ $(cat "$config") == "voxtype" ]] || fail "failed stop preserves selection"
|
||||
omarchy-dictation-use superwhisper
|
||||
[[ $(cat "$DICTATION_LOG") == $'voxtype status\nvoxtype record stop\nvoxtype status\nvoxtype record stop' ]] || fail "switch stops the previous backend"
|
||||
[[ $(cat "$config") == "superwhisper" ]] || fail "successful stop permits switching"
|
||||
: > "$DICTATION_LOG"
|
||||
omarchy-dictation-use superwhisper
|
||||
[[ ! -s $DICTATION_LOG ]] || fail "reselecting does not interrupt recording"
|
||||
printf '%s\n' voxtype > "$config"
|
||||
VOXTYPE_STATUS=stopped omarchy-dictation-use superwhisper
|
||||
[[ $(cat "$config") == "superwhisper" ]] || fail "a stopped Voxtype daemon permits switching"
|
||||
printf '%s\n' superwhisper > "$config"
|
||||
DICTATION_SERVICE_ACTIVE=0 omarchy-dictation-use voxtype
|
||||
[[ $(cat "$config") == "voxtype" ]] || fail "a stopped Superwhisper service permits switching"
|
||||
printf '%s\n' removed-backend > "$config"
|
||||
omarchy-dictation-use voxtype
|
||||
[[ $(cat "$config") == "voxtype" ]] || fail "a removed adapter permits recovery"
|
||||
pass "switching stops recordings, preserves failed stops, and recovers unavailable providers"
|
||||
|
||||
# Readers may run throughout a switch, but must never observe a truncated file.
|
||||
(
|
||||
for ((i = 0; i < 200; i++)); do
|
||||
selection=$(cat "$config")
|
||||
[[ $selection == "voxtype" || $selection == "superwhisper" ]] || exit 1
|
||||
done
|
||||
) &
|
||||
reader=$!
|
||||
for ((i = 0; i < 20; i++)); do
|
||||
omarchy-dictation-use voxtype
|
||||
omarchy-dictation-use superwhisper
|
||||
done
|
||||
wait "$reader" || fail "concurrent readers always see a complete selection"
|
||||
printf '%s\n' voxtype > "$config"
|
||||
pass "selection updates are atomic for concurrent readers"
|
||||
|
||||
lua <<'LUA'
|
||||
local root = os.getenv("ROOT")
|
||||
local configured, bindings = false, {}
|
||||
o = {
|
||||
shell_succeeds = function(command)
|
||||
assert(command == "omarchy-default-dictation")
|
||||
return configured
|
||||
end,
|
||||
bind = function(...) table.insert(bindings, {...}) end,
|
||||
}
|
||||
dofile(root .. "/default/hypr/bindings/dictation.lua")
|
||||
assert(#bindings == 0, "unconfigured dictation leaves application keys available")
|
||||
configured = true
|
||||
dofile(root .. "/default/hypr/bindings/dictation.lua")
|
||||
assert(#bindings == 5, "any configured backend receives the same bindings")
|
||||
LUA
|
||||
pass "dictation shortcuts require a selection without limiting backend names"
|
||||
|
||||
lua <<'LUA'
|
||||
local root = os.getenv("ROOT")
|
||||
package.path = root .. "/?.lua;" .. package.path
|
||||
package.loaded["default.hypr.paths"] = { config_home = "/config" }
|
||||
local selected, available, loaded = "superwhisper", true, {}
|
||||
local missing = false
|
||||
o = { cmd_missing = function() return missing end }
|
||||
local real_open, real_popen, real_dofile = io.open, io.popen, dofile
|
||||
io.popen = function(command)
|
||||
assert(command == "omarchy-default-dictation 2>/dev/null")
|
||||
return { read = function() return selected end, close = function() end }
|
||||
end
|
||||
io.open = function(path)
|
||||
assert(path == "/config/" .. selected .. "/shortcuts.lua")
|
||||
if available then return { close = function() end } end
|
||||
end
|
||||
dofile = function(path) table.insert(loaded, path) end
|
||||
local function load_backend()
|
||||
package.loaded["default.hypr.dictation-backend"] = nil
|
||||
require("default.hypr.dictation-backend")
|
||||
end
|
||||
load_backend()
|
||||
assert(loaded[1] == "/config/superwhisper/shortcuts.lua")
|
||||
selected, available = "voxtype", false
|
||||
load_backend()
|
||||
assert(#loaded == 1, "a backend with no integration should need no config")
|
||||
selected, available = "future-backend", true
|
||||
load_backend()
|
||||
assert(loaded[2] == "/config/future-backend/shortcuts.lua", "integration must not be limited to named providers")
|
||||
missing = true
|
||||
load_backend()
|
||||
assert(#loaded == 2, "an uninstalled backend must not load a stale bridge")
|
||||
missing = false
|
||||
dofile = function() error("broken generated bridge") end
|
||||
load_backend()
|
||||
assert(#loaded == 2, "a broken bridge must not abort the desktop configuration")
|
||||
dofile = function(path) table.insert(loaded, path) end
|
||||
selected = "../../outside"
|
||||
load_backend()
|
||||
assert(#loaded == 2, "a backend must not escape the config directory")
|
||||
selected = nil
|
||||
load_backend()
|
||||
assert(#loaded == 2, "no installed backend must leave configuration usable")
|
||||
io.open, io.popen, dofile = real_open, real_popen, real_dofile
|
||||
LUA
|
||||
pass "desktop integration follows the selected backend without personal Hyprland config"
|
||||
|
||||
cat > "$test_tmp/bin/systemctl" <<'SH'
|
||||
#!/bin/bash
|
||||
exit 0
|
||||
SH
|
||||
cat > "$test_tmp/bin/omarchy-pkg-drop" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "$*" >> "$DICTATION_LOG"
|
||||
exit "${REMOVE_EXIT:-0}"
|
||||
SH
|
||||
chmod +x "$test_tmp/bin/systemctl" "$test_tmp/bin/omarchy-pkg-drop"
|
||||
omarchy-remove-dictation-voxtype
|
||||
[[ ! -e $config ]] || fail "removing Voxtype clears selection"
|
||||
if omarchy-default-dictation 2> "$test_tmp/error"; then fail "removal must not auto-select another backend"; fi
|
||||
pass "removal clears selection without automatic fallback"
|
||||
|
||||
cat > "$test_tmp/bin/omarchy-shell" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "$*" >> "$DICTATION_LOG"
|
||||
SH
|
||||
chmod +x "$test_tmp/bin/omarchy-shell"
|
||||
export XDG_DATA_HOME="$test_tmp/data"
|
||||
mkdir -p "$HOME/.local/bin" "$XDG_DATA_HOME/superwhisper/app" "$XDG_DATA_HOME/superwhisper/models" "$XDG_CONFIG_HOME/omarchy/plugins" "$HOME/.agents/skills" "$HOME/.claude/skills"
|
||||
ln -s /usr/bin/superwhisper "$HOME/.local/bin/superwhisper"
|
||||
ln -s /opt/superwhisper "$XDG_DATA_HOME/superwhisper/app/current"
|
||||
ln -s /opt/superwhisper/assets/omarchy-plugin/superwhisper-panel "$XDG_CONFIG_HOME/omarchy/plugins/superwhisper-panel"
|
||||
ln -s /opt/superwhisper/assets/agent-skill/superwhisper "$HOME/.agents/skills/superwhisper"
|
||||
ln -s /custom/skill "$HOME/.claude/skills/superwhisper"
|
||||
printf '%s\n' retained > "$XDG_DATA_HOME/superwhisper/models/model"
|
||||
printf '%s\n' superwhisper > "$config"
|
||||
if REMOVE_EXIT=1 omarchy-remove-dictation-superwhisper > "$test_tmp/output" 2>&1; then
|
||||
fail "failed package removal must fail"
|
||||
fi
|
||||
[[ -L $HOME/.local/bin/superwhisper && $(cat "$config") == "superwhisper" ]] || fail "failed removal preserves selection and links"
|
||||
omarchy-remove-dictation-superwhisper
|
||||
[[ ! -e $config ]] || fail "Superwhisper removal clears its selection"
|
||||
for link in "$HOME/.local/bin/superwhisper" "$XDG_DATA_HOME/superwhisper/app/current" "$XDG_CONFIG_HOME/omarchy/plugins/superwhisper-panel" "$HOME/.agents/skills/superwhisper"; do
|
||||
[[ ! -L $link ]] || fail "Superwhisper removal unlinks packaged integration"
|
||||
done
|
||||
[[ $(readlink "$HOME/.claude/skills/superwhisper") == "/custom/skill" ]] || fail "custom skills are preserved"
|
||||
[[ -f $XDG_DATA_HOME/superwhisper/models/model ]] || fail "downloaded models are retained"
|
||||
printf '%s\n' voxtype > "$config"
|
||||
omarchy-remove-dictation-superwhisper
|
||||
[[ $(cat "$config") == "voxtype" ]] || fail "removing Superwhisper preserves another selected backend"
|
||||
pass "Superwhisper removal clears its selection and owned links while preserving user data and other backends"
|
||||
|
||||
cat > "$test_tmp/bin/omarchy-pkg-present" <<'SH'
|
||||
#!/bin/bash
|
||||
[[ ${VOXTYPE_PACKAGE:-0} == 1 && $1 == "voxtype-bin" ]]
|
||||
SH
|
||||
chmod +x "$test_tmp/bin/omarchy-pkg-present"
|
||||
rm "$config"
|
||||
VOXTYPE_PACKAGE=0 /bin/bash -euo pipefail "$ROOT/migrations/1791479273.sh"
|
||||
[[ ! -e $config ]] || fail "migration does not select an uninstalled backend"
|
||||
VOXTYPE_PACKAGE=1 /bin/bash -euo pipefail "$ROOT/migrations/1791479273.sh"
|
||||
[[ $(cat "$config") == "voxtype" ]] || fail "existing Voxtype users keep dictation after upgrade"
|
||||
printf '%s\n' future-backend > "$config"
|
||||
VOXTYPE_PACKAGE=1 /bin/bash -euo pipefail "$ROOT/migrations/1791479273.sh"
|
||||
[[ $(cat "$config") == "future-backend" ]] || fail "upgrade preserves explicit backend selections"
|
||||
pass "one-time upgrade preserves existing Voxtype without runtime autodetection"
|
||||
@@ -198,16 +198,6 @@ ShellRoot {
|
||||
root.assertTrue(root.commandCount("omarchy-capture-screenrecording --stop-recording") === 1, "Screen Recording left click stops active recording")
|
||||
}
|
||||
|
||||
var dictation = root.createIndicator("Dictation")
|
||||
if (dictation) {
|
||||
dictation.moduleName = "Dictation"
|
||||
root.injectBar(dictation)
|
||||
dictation.triggerPress(Qt.LeftButton)
|
||||
dictation.triggerPress(Qt.RightButton)
|
||||
root.assertTrue(root.commandCount("omarchy-voxtype-config") === 2, "Dictation clicks run config command")
|
||||
root.assertTrue(root.commandCount("omarchy-voxtype-model") === 0, "Dictation clicks do not run model command")
|
||||
}
|
||||
|
||||
var stayAwake = root.createIndicator("StayAwake")
|
||||
if (stayAwake) {
|
||||
stayAwake.moduleName = "StayAwake"
|
||||
|
||||
@@ -147,8 +147,11 @@ trap 'rm -rf "$tmpdir"' EXIT
|
||||
home="$tmpdir/home"
|
||||
stub_bin="$tmpdir/bin"
|
||||
mkdir -p "$home" "$stub_bin"
|
||||
touch "$stub_bin/voxtype"
|
||||
chmod +x "$stub_bin/voxtype"
|
||||
cat > "$stub_bin/omarchy-default-dictation" <<'SH'
|
||||
#!/bin/bash
|
||||
echo voxtype
|
||||
SH
|
||||
chmod +x "$stub_bin/omarchy-default-dictation"
|
||||
|
||||
bindings=$(PATH="$stub_bin:$PATH" list_bindings "$home")
|
||||
[[ -n $bindings ]] || fail "default bindings load for the conflict check"
|
||||
|
||||
@@ -144,36 +144,29 @@ no_bindings_output=$(run_omarchy_bindings "$no_bindings_home" 'omarchy_default_b
|
||||
[[ -z $no_bindings_output ]] || fail "default binding variable disables all Omarchy bindings" "$no_bindings_output"
|
||||
pass "default binding variable disables all Omarchy bindings"
|
||||
|
||||
voxtype_home="$tmpdir/voxtype-home"
|
||||
voxtype_bin="$tmpdir/voxtype-bin"
|
||||
mkdir -p "$voxtype_home" "$voxtype_bin"
|
||||
touch "$voxtype_bin/voxtype"
|
||||
chmod +x "$voxtype_bin/voxtype"
|
||||
voxtype_output=$(PATH="$voxtype_bin:$PATH" run_omarchy_bindings "$voxtype_home")
|
||||
grep -Fq $'SUPER + CTRL + X Toggle dictation' <<<"$voxtype_output" ||
|
||||
fail "installed Voxtype enables its toggle binding"
|
||||
grep -Fq $'F9 Start dictation (push-to-talk)' <<<"$voxtype_output" ||
|
||||
fail "installed Voxtype enables its push-to-talk binding"
|
||||
grep -Fq $'F9 Stop dictation (push-to-talk)' <<<"$voxtype_output" ||
|
||||
fail "installed Voxtype enables its release binding"
|
||||
pass "installed Voxtype conditionally enables dictation bindings"
|
||||
|
||||
voxtype_without_execute_output=$(PATH="$voxtype_bin:$PATH" run_omarchy_bindings \
|
||||
"$voxtype_home" 'os.execute = function() return nil, "No child processes", 10 end')
|
||||
grep -Fq $'SUPER + CTRL + X Toggle dictation' <<<"$voxtype_without_execute_output" ||
|
||||
fail "Voxtype detection does not require spawning a subprocess"
|
||||
pass "installed Voxtype detection works without os.execute"
|
||||
|
||||
dictation_home="$tmpdir/dictation-home"
|
||||
missing_bin="$tmpdir/missing-bin"
|
||||
mkdir -p "$missing_bin"
|
||||
mkdir -p "$dictation_home" "$missing_bin"
|
||||
ln -s "$(command -v lua)" "$missing_bin/lua"
|
||||
ln -s "$(command -v lspci)" "$missing_bin/lspci"
|
||||
ln -s "$(command -v sort)" "$missing_bin/sort"
|
||||
missing_voxtype_output=$(PATH="$missing_bin" run_omarchy_bindings "$voxtype_home")
|
||||
if grep -Fq $'SUPER + CTRL + X Toggle dictation' <<<"$missing_voxtype_output"; then
|
||||
fail "missing Voxtype skips its bindings"
|
||||
fi
|
||||
pass "missing Voxtype skips dictation bindings"
|
||||
dictation_output=$(PATH="$missing_bin" run_omarchy_bindings "$dictation_home")
|
||||
if grep -Fq 'dictation' <<<"$dictation_output"; then fail "unconfigured dictation leaves application shortcuts available"; fi
|
||||
cat > "$missing_bin/omarchy-default-dictation" <<'SH'
|
||||
#!/bin/bash
|
||||
echo future-backend
|
||||
SH
|
||||
chmod +x "$missing_bin/omarchy-default-dictation"
|
||||
dictation_output=$(PATH="$missing_bin" run_omarchy_bindings "$dictation_home")
|
||||
for binding in \
|
||||
$'SUPER + CTRL + X\tToggle dictation' \
|
||||
$'F9\tStart dictation (push-to-talk)' \
|
||||
$'F9\tStop dictation (push-to-talk)' \
|
||||
$'ALT + Alt_R\tStart dictation (push-to-talk)' \
|
||||
$'ALT + Alt_R\tStop dictation (push-to-talk)'; do
|
||||
grep -Fq "$binding" <<<"$dictation_output" || fail "dictation bindings load for any selected backend" "$binding"
|
||||
done
|
||||
pass "dictation bindings require a selection without hardcoding backends"
|
||||
|
||||
# Lazydocker is optional on new installs, while existing installs keep the hotkey.
|
||||
lazydocker_bin="$tmpdir/lazydocker-bin"
|
||||
|
||||
@@ -71,7 +71,7 @@ const fs = require('fs')
|
||||
const defaultItems = menu.parseMenuJsonc(fs.readFileSync(path.join(root, 'default/omarchy/omarchy-menu.jsonc'), 'utf8'))
|
||||
const guardText = defaultItems.map(item => `${item.when}\n${item.checked}\n${item.disabled}`).join('\n')
|
||||
const repeated = [...new Set(
|
||||
(guardText.match(/\$\((omarchy-[a-z0-9-]+)\)/g) || []).map(match => match.slice(2, -1))
|
||||
(guardText.match(/\$\((omarchy-[a-z0-9-]+(?: [a-z0-9-]+)*)\)/g) || []).map(match => match.slice(2, -1))
|
||||
)].filter(command => guardText.split(`$(${command})`).length > 2)
|
||||
assertDeepEqual(
|
||||
repeated.filter(command => !menu.guardReaders.includes(command)),
|
||||
|
||||
@@ -325,6 +325,7 @@ assertDeepEqual(
|
||||
[
|
||||
'remove.package',
|
||||
'remove.ai',
|
||||
'remove.dictation',
|
||||
'remove.service',
|
||||
'remove.development',
|
||||
'remove.theme',
|
||||
@@ -336,7 +337,7 @@ assertDeepEqual(
|
||||
'remove.preinstalls',
|
||||
'remove.security'
|
||||
],
|
||||
'menu orders Remove categories like their Install counterparts, followed by Remove-only categories'
|
||||
'menu keeps the Remove category order'
|
||||
)
|
||||
assert(
|
||||
defaultById['setup.security.passwordless-sudo'].action.includes('omarchy-sudo-passwordless'),
|
||||
|
||||
@@ -9,7 +9,7 @@ const read = name => fs.readFileSync(path.join(root, name), 'utf8')
|
||||
const indicator = read('shell/plugins/bar/indicators/PasswordlessSudo.qml')
|
||||
const widget = read('shell/plugins/bar/widgets/Indicators.qml')
|
||||
const manifest = JSON.parse(read('shell/plugins/bar/widgets/Indicators.manifest.json'))
|
||||
assert(widget.match(/defaultIndicatorEntries: \[ "PasswordlessSudo", "Dictation"/), 'passwordless sudo is included in the default indicator tray')
|
||||
assert(widget.match(/defaultIndicatorEntries: \[ "PasswordlessSudo", "ScreenRecording"/), 'passwordless sudo is included in the default indicator tray')
|
||||
assert(manifest.barWidget.schema.find(field => field.key === 'items').options.some(option => option.value === 'PasswordlessSudo'), 'passwordless sudo is configurable alongside Night Light')
|
||||
assert(indicator.includes('useActiveColor: true') && indicator.includes('activeColor: Commons.Color.urgent'), 'active passwordless sudo uses the theme danger color')
|
||||
assertEqual(indicator.match(/activeText: "([^"]+)"/)[1], indicator.match(/inactiveText: "([^"]+)"/)[1], 'sudo keeps the same icon in both states')
|
||||
|
||||
@@ -45,8 +45,8 @@ run_invitation_hook
|
||||
[[ -f $test_home/.local/state/omarchy/done/voxtype-install-invitation ]] || fail "Voxtype invitation records completion"
|
||||
[[ -f $hook_path ]] || fail "Voxtype invitation keeps its hook installed"
|
||||
[[ $(grep -c '^notification$' "$log_file") -eq 1 ]] || fail "Voxtype invitation sends one notification"
|
||||
grep -qx 'exec:omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install' "$log_file" ||
|
||||
fail "Voxtype invitation attaches the installer to the notification"
|
||||
grep -qx 'exec:omarchy-menu summon setup.default.dictation' "$log_file" ||
|
||||
fail "dictation invitation opens the backend installation choices"
|
||||
grep -q '^systemd-run:' "$log_file" && fail "Voxtype invitation needs no unit to hold an unanswered toast"
|
||||
|
||||
HOME="$test_home" PATH="$test_bin:$ROOT/bin:$PATH" TEST_LOG="$log_file" bash "$hook_path"
|
||||
|
||||
Reference in new issue
Block a user