Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions bin/omarchy-hw-x86-64-v3
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash

# omarchy:summary=Detect whether the CPU supports the x86-64-v3 baseline (AVX2, FMA, BMI1, BMI2, F16C, MOVBE, LZCNT).

machine=${OMARCHY_UNAME_M:-$(uname -m)}
[[ $machine == "x86_64" ]] || exit 1

cpuinfo=${OMARCHY_CPUINFO_PATH:-/proc/cpuinfo}
flags=" $(grep -m1 "^flags" "$cpuinfo" 2>/dev/null | cut -d: -f2) "

# LZCNT is reported as "abm" in /proc/cpuinfo on both Intel and AMD.
for flag in avx2 fma bmi1 bmi2 f16c movbe abm; do
[[ $flags == *" $flag "* ]] || exit 1
done
30 changes: 27 additions & 3 deletions bin/omarchy-pkg-aur-add
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,39 @@

# omarchy:summary=Add the named packages to the system from the AUR if they're missing. Returns false if it couldn't be done.
# omarchy:args=<packages...>
#
# Each package may be given as a plain name or as "aur/name" to force
# resolution from the AUR even when a same-named package is also provided
# by a sync repo (yay's --noconfirm silently prefers the repo provider in
# that case, which defeats callers that specifically want the AUR build).

if omarchy-pkg-missing "$@"; then
# pacman -Q resolves provides: with voxtype-bin installed, "pacman -Q voxtype"
# answers "voxtype-bin" and exits 0. A prefixed target is therefore checked by
# the exact name pacman prints back, or the provider the prefix exists to
# bypass satisfies both checks below and nothing is ever built.
installed() {
local name=${1#*/}

if [[ $1 == */* ]]; then
[[ $(pacman -Qq "$name" 2>/dev/null) == "$name" ]]
else
pacman -Q "$name" &>/dev/null
fi
}

missing=0
for pkg in "$@"; do
installed "$pkg" || missing=1
done

if (( missing )); then
yay -S --noconfirm --needed "$@" || exit 1
fi

for pkg in "$@"; do
# Secondary check to handle states where pacman doesn't actually register an error
if ! pacman -Q "$pkg" &>/dev/null; then
echo -e "\033[31mError: Package '$pkg' did not install\033[0m" >&2
if ! installed "$pkg"; then
echo -e "\033[31mError: Package '${pkg#*/}' did not install\033[0m" >&2
exit 1
fi
done
Expand Down
80 changes: 67 additions & 13 deletions bin/omarchy-voxtype-install
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,78 @@
# 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
voxtype_pkg=voxtype-bin
if ! omarchy-hw-x86-64-v3; then
if [[ $(uname -m) == "x86_64" ]]; then
gum style --foreground 3 \
"Your CPU is missing instructions (AVX2/x86-64-v3) that the prebuilt Voxtype binary requires."
build_prompt="Build Voxtype from source instead? This can take 20+ minutes and may result in noticeably slower dictation on this CPU."
else
gum style --foreground 3 \
"There's no prebuilt Voxtype binary for your CPU architecture."
build_prompt="Build Voxtype from source instead? This can take 20+ minutes."
fi

# Setup voxtype
mkdir -p ~/.config/voxtype
cp "$OMARCHY_PATH/default/voxtype/config.toml" ~/.config/voxtype/
if gum confirm "$build_prompt"; then
if ! omarchy-pkg-aur-accessible; then
gum style --foreground 1 "The AUR is unreachable right now, so Voxtype can't be built from source. Try again later."
exit 1
fi
voxtype_pkg=voxtype
else
echo "Skipping Voxtype install."
exit 0
fi
fi

# wtype is only useful once Voxtype is actually going to be installed, so it
# waits until after the CPU decision: declining the build now installs nothing.
omarchy-pkg-add wtype || exit 1

if [[ $voxtype_pkg == "voxtype-bin" ]]; then
omarchy-pkg-add voxtype-bin || exit 1
else
# voxtype-bin conflicts with voxtype, and pacman answers no to the conflict
# prompt under --noconfirm, so a machine still carrying the prebuilt binary
# would build for 20 minutes and then fail to install what it built.
if ! omarchy-pkg-drop voxtype-bin; then
gum style --foreground 1 \
"Couldn't remove the prebuilt Voxtype binary, and the source build can't install over it, so nothing was built. Dictation is unchanged."
exit 1
fi

voxtype setup --download --no-post-install
if omarchy-hw-vulkan; then
voxtype setup gpu --enable || true
if ! omarchy-pkg-aur-add aur/voxtype; then
gum style --foreground 1 \
"Building Voxtype from source failed, so dictation isn't installed. Any prebuilt binary that was here has been removed, since it couldn't run on this CPU either. Re-run Install > Dictation to try again."
exit 1
fi
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
# Setup voxtype
install_voxtype() {
mkdir -p ~/.config/voxtype
cp "$OMARCHY_PATH/default/voxtype/config.toml" ~/.config/voxtype/ || return 1

voxtype setup --download --no-post-install || return 1
if omarchy-hw-vulkan; then
voxtype setup gpu --enable || true
fi
voxtype setup systemd || return 1
}

# Reloading and restarting the shell only picks up the new bindings; both can
# fail for reasons that have nothing to do with Voxtype (a locked session, a
# slow shell), so they sit outside the rollback and never undo a good install.
if install_voxtype; then
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
else
gum style --foreground 1 "Voxtype setup failed. Rolling back the install."
omarchy-voxtype-remove
exit 1
fi
fi
6 changes: 4 additions & 2 deletions bin/omarchy-voxtype-remove
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ if omarchy-cmd-present voxtype; then
systemctl --user disable --now voxtype.service 2>/dev/null || true
systemctl --user daemon-reload

# Remove packages and configs
omarchy-pkg-drop voxtype-bin
# Remove packages and configs. voxtype-bin is the prebuilt binary; voxtype
# is the cargo-built fallback offered on CPUs without the AVX2/x86-64-v3
# instructions voxtype-bin requires. Only one of the two is ever installed.
omarchy-pkg-drop voxtype-bin voxtype
rm -rf ~/.config/voxtype
rm -rf ~/.local/share/voxtype
hyprctl reload >/dev/null
Expand Down
4 changes: 2 additions & 2 deletions default/omarchy/omarchy-menu.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@
"install.terminal.ghostty": {"icon":"","label":"Ghostty","disabled":"omarchy-pkg-present ghostty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal ghostty'"},
"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.dictation": {"icon":"","label":"Dictation","disabled":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install"},
"install.ai.dictation": {"icon":"","label":"Dictation","disabled":"omarchy-cmd-present voxtype","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.lm-studio": {"icon":"","iconFont":"omarchy","label":"LM Studio","disabled":"omarchy-pkg-present lmstudio-bin","action":"omarchy-install-app 'LM Studio' lmstudio-bin"},
"install.ai.ollama": {"icon":"","iconFont":"omarchy","label":"Ollama","disabled":"omarchy-cmd-present ollama","action":"if omarchy-cmd-present nvidia-smi; then ollama_pkg=ollama-cuda; elif omarchy-cmd-present rocminfo; then ollama_pkg=ollama-rocm; else ollama_pkg=ollama; fi; omarchy-install-app Ollama \"$ollama_pkg\""},
Expand Down Expand Up @@ -301,7 +301,7 @@
"remove.service.dropbox": {"icon":"","label":"Dropbox","when":"omarchy-pkg-present dropbox","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-service-dropbox"},
"remove.service.tailscale": {"icon":"","label":"Tailscale","when":"omarchy-pkg-present tailscale","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-service-tailscale"},
"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.dictation": {"icon":"","label":"Dictation","when":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-remove"},
"remove.ai.dictation": {"icon":"","label":"Dictation","when":"omarchy-cmd-present voxtype","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-remove"},
"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"},
Expand Down
2 changes: 2 additions & 0 deletions manual/11-text-extraction-dictation.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ This is very helpful for grabbing addresses out of image footers or phone number

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`.

The prebuilt Voxtype binary is x86_64 only and requires the x86-64-v3 instruction baseline (AVX2, FMA, BMI1/2, F16C, MOVBE, LZCNT — Haswell-era or newer, roughly 2013+). On an older x86_64 CPU, or on any non-x86_64 machine (e.g. Apple Silicon), the installer offers to build Voxtype from source via the AUR instead; on an older x86_64 CPU this takes 20+ minutes and may run noticeably slower, while on other architectures it's a native build with no expected slowdown.

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.
55 changes: 55 additions & 0 deletions test/shell.d/hw-x86-64-v3-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/bash

set -euo pipefail

source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

tmp_dir=$(mktemp -d)
trap 'rm -rf "$tmp_dir"' EXIT

write_cpuinfo_flags() {
printf 'flags\t\t: %s\n' "$1" >"$tmp_dir/cpuinfo"
}

hw_x86_64_v3() {
OMARCHY_CPUINFO_PATH="$tmp_dir/cpuinfo" OMARCHY_UNAME_M="${OMARCHY_UNAME_M:-x86_64}" "$ROOT/bin/omarchy-hw-x86-64-v3"
}

assert_detects() {
local description="$1"

hw_x86_64_v3 || fail "$description"
pass "$description"
}

assert_rejects() {
local description="$1"

if hw_x86_64_v3; then
fail "$description"
fi
pass "$description"
}

# Ivy Bridge (e.g. i7-3520M): has AVX but not AVX2/BMI/FMA.
write_cpuinfo_flags "fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc aperfmperf tsc_known_freq pni pclmulqdq est ssse3 fma cx16 sse4_1 sse4_2 popcnt aes xsave avx f16c rdrand hypervisor lahf_lm"
assert_rejects "Ivy Bridge without AVX2/BMI1/BMI2 does not satisfy x86-64-v3"

# Haswell and newer: full x86-64-v3 baseline.
write_cpuinfo_flags "fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm abm avx2 bmi1 bmi2"
assert_detects "Haswell with the full x86-64-v3 baseline satisfies it"

write_cpuinfo_flags "fpu vme de pse tsc msr avx avx2"
assert_rejects "AVX2 alone without FMA/BMI1/BMI2/F16C/MOVBE does not satisfy x86-64-v3"

# LZCNT is part of the x86-64-v3 baseline and Voxtype's binaries are built with
# target-cpu=haswell, so a CPU (or a CPUID mask) carrying every other flag but
# not this one can still take a SIGILL.
write_cpuinfo_flags "fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm avx2 bmi1 bmi2"
assert_rejects "the full baseline without LZCNT (abm) does not satisfy x86-64-v3"

: >"$tmp_dir/cpuinfo"
assert_rejects "empty cpuinfo does not satisfy x86-64-v3"

write_cpuinfo_flags "fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm abm avx2 bmi1 bmi2"
OMARCHY_UNAME_M="aarch64" assert_rejects "aarch64 is rejected even with x86-64-v3 flags present"
112 changes: 112 additions & 0 deletions test/shell.d/pkg-aur-add-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/bin/bash

set -euo pipefail

source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

test_bin=$(mktemp -d)
state_dir=$(mktemp -d)
log_file=$(mktemp)
trap 'rm -rf "$test_bin" "$state_dir"; rm -f "$log_file"' EXIT

# "Installed" state is marker files in $state_dir: the file name is the real
# package name and its contents are the names that package provides. Like the
# real pacman, -Q and -Qq answer for a provided name too, printing the real
# package that provides it.
cat >"$test_bin/pacman" <<EOF
#!/bin/bash
if [[ \$1 == "-Q" || \$1 == "-Qq" ]]; then
for marker in "$state_dir"/*; do
[[ -e \$marker ]] || continue
real=\${marker##*/}
if [[ \$real == "\$2" ]] || grep -qw -- "\$2" "\$marker"; then
[[ \$1 == "-Qq" ]] && echo "\$real" || echo "\$real 1-1"
exit 0
fi
done
exit 1
fi
exit 0
EOF
chmod +x "$test_bin/pacman"

# Default yay: "installs" whatever it's given by dropping a marker file for
# each target's bare (unprefixed) name.
write_yay_installs() {
cat >"$test_bin/yay" <<EOF
#!/bin/bash
echo "yay:\$*" >>"\$TEST_LOG"
for target in "\${@:3}"; do
touch "$state_dir/\${target#*/}"
done
exit 0
EOF
chmod +x "$test_bin/yay"
}

# yay that succeeds but installs nothing -- simulates a sync-repo provider
# silently satisfying the transaction instead of the intended AUR package.
write_yay_noop() {
cat >"$test_bin/yay" <<EOF
#!/bin/bash
echo "yay:\$*" >>"\$TEST_LOG"
exit 0
EOF
chmod +x "$test_bin/yay"
}

aur_add() {
PATH="$test_bin:$ROOT/bin:$PATH" TEST_LOG="$log_file" \
bash "$ROOT/bin/omarchy-pkg-aur-add" "$@"
}

run_aur_add() {
: >"$log_file"
rm -f "$state_dir"/*
aur_add "$@"
}

# Same, but starting from a machine where $1 is installed and provides $2.
run_aur_add_with_provider() {
local provider="$1" provided="$2"
shift 2
: >"$log_file"
rm -f "$state_dir"/*
echo "$provided" >"$state_dir/$provider"
aur_add "$@"
}

# An "aur/name" target is passed through to yay as-is (to force AUR
# resolution over a same-named sync-repo provider), but the installed-check
# and error reporting use the bare package name pacman actually knows.
write_yay_installs
run_aur_add "aur/voxtype" || fail "aur/-prefixed target succeeds once pacman reports the bare name installed"
grep -qx 'yay:-S --noconfirm --needed aur/voxtype' "$log_file" || fail "yay is invoked with the full aur/name target"
pass "aur/-prefixed target installs and verifies via the bare package name"

# A plain name (no prefix) still works as before.
run_aur_add "jq" || fail "unprefixed target still succeeds"
pass "unprefixed target behaves as before"

# If pacman never reports the bare name as installed, the call fails even
# though yay exited 0 -- guards against a repo provider silently satisfying
# the sync transaction instead of the intended AUR package.
write_yay_noop
if run_aur_add "aur/voxtype"; then
fail "missing bare package name after install is treated as failure"
fi
pass "missing bare package name after install is treated as failure"

# The provider a prefixed target exists to bypass must not count as the target:
# "pacman -Q voxtype" answers "voxtype-bin" when that package provides it, so a
# machine already carrying the prebuilt binary would otherwise skip the build
# and then accept the binary it was trying to replace.
if run_aur_add_with_provider "voxtype-bin" "voxtype" "aur/voxtype"; then
fail "a package that only provides the target is not treated as the target"
fi
grep -qx 'yay:-S --noconfirm --needed aur/voxtype' "$log_file" || fail "an installed provider does not skip the AUR build"

write_yay_installs
run_aur_add_with_provider "voxtype-bin" "voxtype" "aur/voxtype" ||
fail "the real package installing over a provider succeeds"
pass "an installed provider neither skips nor satisfies a prefixed target"
Loading