diff --git a/bin/omarchy-hw-x86-64-v3 b/bin/omarchy-hw-x86-64-v3 new file mode 100755 index 00000000000..3b9a744438a --- /dev/null +++ b/bin/omarchy-hw-x86-64-v3 @@ -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 diff --git a/bin/omarchy-pkg-aur-add b/bin/omarchy-pkg-aur-add index 7a152be7fe8..0e228097b37 100755 --- a/bin/omarchy-pkg-aur-add +++ b/bin/omarchy-pkg-aur-add @@ -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= +# +# 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 diff --git a/bin/omarchy-voxtype-install b/bin/omarchy-voxtype-install index 1fd19f9de6d..f52b5b54fbd 100755 --- a/bin/omarchy-voxtype-install +++ b/bin/omarchy-voxtype-install @@ -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 diff --git a/bin/omarchy-voxtype-remove b/bin/omarchy-voxtype-remove index 08634e60e25..16872e3f4ee 100755 --- a/bin/omarchy-voxtype-remove +++ b/bin/omarchy-voxtype-remove @@ -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 diff --git a/default/omarchy/omarchy-menu.jsonc b/default/omarchy/omarchy-menu.jsonc index 59582db7345..808749e4fae 100644 --- a/default/omarchy/omarchy-menu.jsonc +++ b/default/omarchy/omarchy-menu.jsonc @@ -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\""}, @@ -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"}, diff --git a/manual/11-text-extraction-dictation.md b/manual/11-text-extraction-dictation.md index 2d222a67631..15e2cba7446 100644 --- a/manual/11-text-extraction-dictation.md +++ b/manual/11-text-extraction-dictation.md @@ -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. diff --git a/test/shell.d/hw-x86-64-v3-test.sh b/test/shell.d/hw-x86-64-v3-test.sh new file mode 100755 index 00000000000..deef39bb6d0 --- /dev/null +++ b/test/shell.d/hw-x86-64-v3-test.sh @@ -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" diff --git a/test/shell.d/pkg-aur-add-test.sh b/test/shell.d/pkg-aur-add-test.sh new file mode 100755 index 00000000000..29db8904ae7 --- /dev/null +++ b/test/shell.d/pkg-aur-add-test.sh @@ -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" <"$test_bin/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" <>"\$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" diff --git a/test/shell.d/voxtype-install-test.sh b/test/shell.d/voxtype-install-test.sh new file mode 100755 index 00000000000..fcae9299c7b --- /dev/null +++ b/test/shell.d/voxtype-install-test.sh @@ -0,0 +1,190 @@ +#!/bin/bash + +source "$(dirname "$0")/base-test.sh" + +test_home=$(mktemp -d) +test_bin=$(mktemp -d) +test_omarchy_path=$(mktemp -d) +log_file=$(mktemp) +confirm_queue=$(mktemp) + +cleanup() { + rm -rf "$test_home" "$test_bin" "$test_omarchy_path" + rm -f "$log_file" "$confirm_queue" +} +trap cleanup EXIT + +mkdir -p "$test_omarchy_path/default/voxtype" +echo "# stub config" >"$test_omarchy_path/default/voxtype/config.toml" + +# gum answers confirm prompts in order from confirm_queue (one "yes"/"no" per +# line), and no-ops for every other subcommand (style, etc.). +cat >"$test_bin/gum" <<'EOF' +#!/bin/bash +if [[ $1 == "confirm" ]]; then + echo "confirm:$2" >>"$TEST_LOG" + answer=$(head -n1 "$CONFIRM_QUEUE") + sed -i '1d' "$CONFIRM_QUEUE" + [[ $answer == "yes" ]] + exit $? +fi +exit 0 +EOF +chmod +x "$test_bin/gum" + +for cmd in omarchy-pkg-add omarchy-pkg-aur-add omarchy-pkg-drop omarchy-restart-shell omarchy-notification-send omarchy-voxtype-remove; do + cat >"$test_bin/$cmd" <>"\$TEST_LOG" +EOF + chmod +x "$test_bin/$cmd" +done + +# The drop of the conflicting prebuilt binary is the one stub a case needs to +# fail, so it answers an environment variable the way the AUR probe does. +cat >"$test_bin/omarchy-pkg-drop" <<'EOF' +#!/bin/bash +echo "omarchy-pkg-drop:$*" >>"$TEST_LOG" +exit "${PKG_DROP_EXIT:-0}" +EOF +chmod +x "$test_bin/omarchy-pkg-drop" + +cat >"$test_bin/hyprctl" <<'EOF' +#!/bin/bash +exit 0 +EOF +chmod +x "$test_bin/hyprctl" + +cat >"$test_bin/omarchy-hw-vulkan" <<'EOF' +#!/bin/bash +exit 1 +EOF +chmod +x "$test_bin/omarchy-hw-vulkan" + +cat >"$test_bin/omarchy-pkg-aur-accessible" <<'EOF' +#!/bin/bash +exit "${AUR_ACCESSIBLE:-0}" +EOF +chmod +x "$test_bin/omarchy-pkg-aur-accessible" + +# The installer branches its copy on the machine type, so both branches have to +# be reachable from an x86_64 test host. +cat >"$test_bin/uname" <<'EOF' +#!/bin/bash +if [[ $1 == "-m" ]]; then + echo "${TEST_UNAME_M:-x86_64}" +else + exec /usr/bin/uname "$@" +fi +EOF +chmod +x "$test_bin/uname" + +write_hw_x86_64_v3() { + cat >"$test_bin/omarchy-hw-x86-64-v3" <"$test_bin/voxtype" <>"\$TEST_LOG" +if [[ \$1 == "setup" && \$2 == "--download" ]]; then + exit $setup_exit +fi +exit 0 +EOF + chmod +x "$test_bin/voxtype" +} + +run_install() { + local answers="$1" + : >"$log_file" + printf '%s\n' $answers >"$confirm_queue" + HOME="$test_home" OMARCHY_PATH="$test_omarchy_path" PATH="$test_bin:$ROOT/bin:$PATH" \ + TEST_LOG="$log_file" CONFIRM_QUEUE="$confirm_queue" AUR_ACCESSIBLE="${2:-0}" \ + TEST_UNAME_M="${3:-x86_64}" PKG_DROP_EXIT="${4:-0}" \ + bash "$ROOT/bin/omarchy-voxtype-install" + install_status=$? + return $install_status +} + +# The installer's exit status is what the menu and any caller act on, so every +# path asserts it rather than only the commands it logged along the way. +assert_status() { + (( install_status == $1 )) || fail "$2 (exited $install_status, expected $1)" +} + +# A CPU with the x86-64-v3 baseline installs the prebuilt binary as before. +write_hw_x86_64_v3 0 +write_voxtype_stub 0 +run_install "yes" +assert_status 0 "a successful install on a v3-capable CPU succeeds" +grep -qx 'omarchy-pkg-add:wtype' "$log_file" || fail "v3-capable CPU installs wtype" +grep -qx 'omarchy-pkg-add:voxtype-bin' "$log_file" || fail "v3-capable CPU installs voxtype-bin" +grep -q '^omarchy-notification-send:' "$log_file" || fail "v3-capable CPU install sends the ready notification" +grep -q '^omarchy-pkg-aur-add:' "$log_file" && fail "v3-capable CPU install does not touch the AUR" +grep -q '^omarchy-pkg-drop:' "$log_file" && fail "v3-capable CPU install does not drop anything" + +# A CPU missing the baseline, with the user declining the source build, +# installs nothing. +write_hw_x86_64_v3 1 +run_install "yes no" +assert_status 0 "declining the source build is not an error" +grep -q '^omarchy-pkg-add:' "$log_file" && fail "declining the source build installs no packages at all, wtype included" +grep -q '^omarchy-pkg-aur-add:' "$log_file" && fail "declining the source build does not touch the AUR" + +# A CPU missing the baseline, with the user accepting the source build, +# installs the AUR voxtype package instead of voxtype-bin. +write_hw_x86_64_v3 1 +write_voxtype_stub 0 +run_install "yes yes" 0 +assert_status 0 "a successful source build succeeds" +grep -qx 'omarchy-pkg-add:wtype' "$log_file" || fail "accepting the source build installs wtype" +grep -qx 'omarchy-pkg-aur-add:aur/voxtype' "$log_file" || fail "accepting the source build installs voxtype via the AUR" +grep -q '^omarchy-pkg-add:voxtype-bin' "$log_file" && fail "accepting the source build does not install voxtype-bin" +# voxtype-bin conflicts with voxtype, so it has to go before the build's output +# can be installed over it. +drop_line=$(grep -n '^omarchy-pkg-drop:voxtype-bin$' "$log_file" | cut -d: -f1) +aur_line=$(grep -n '^omarchy-pkg-aur-add:aur/voxtype$' "$log_file" | cut -d: -f1) +if [[ -z $drop_line ]] || (( drop_line > aur_line )); then + fail "the conflicting prebuilt binary is dropped before the source build installs" +fi + +# If the AUR is unreachable, the source-build offer fails cleanly. +write_hw_x86_64_v3 1 +run_install "yes yes" 1 +assert_status 1 "an unreachable AUR fails the install" +grep -q '^omarchy-pkg-aur-add:' "$log_file" && fail "an unreachable AUR does not attempt the source install" +grep -q '^omarchy-pkg-add:' "$log_file" && fail "an unreachable AUR leaves no packages behind" + +# If the conflicting prebuilt binary cannot be dropped, the build's output could +# not be installed over it, so the build is not started at all. +write_hw_x86_64_v3 1 +run_install "yes yes" 0 x86_64 1 +assert_status 1 "a prebuilt binary that cannot be dropped fails the install" +grep -q '^omarchy-pkg-aur-add:' "$log_file" && fail "a prebuilt binary that cannot be dropped does not start the source build" + +# On a non-x86_64 machine nothing is missing and the source build is native, so +# neither message may leak into the other architecture's branch. +write_hw_x86_64_v3 1 +run_install "yes no" 0 x86_64 +grep -qxF 'confirm:Build Voxtype from source instead? This can take 20+ minutes and may result in noticeably slower dictation on this CPU.' "$log_file" || + fail "an x86_64 CPU missing the baseline is warned the source build may be slower" + +run_install "yes no" 0 aarch64 +grep -qxF 'confirm:Build Voxtype from source instead? This can take 20+ minutes.' "$log_file" || + fail "a non-x86_64 machine is not warned about a slowdown that would not happen" + +# A failure partway through setup rolls back via omarchy-voxtype-remove +# instead of leaving a half-installed package behind. +write_hw_x86_64_v3 0 +write_voxtype_stub 1 +run_install "yes" +assert_status 1 "a failed setup step fails the install" +grep -q '^omarchy-voxtype-remove:' "$log_file" || fail "a failed setup step triggers a rollback" + +pass "Voxtype install gates on CPU capability and rolls back on failure"