* Unify input methods with the installer keyboard selection * Preserve inherited input keys and later keyboard changes * Exercise input engines across desktop toolkits * Show input modes in the keyboard indicator * Keep Pinyin setup prompts visible * Remember input selections before an app gains focus * Remove the Japanese input welcome notification * Show explicit choices on notification cards * Suppress the Cloudpinyin first-use prompt * Show the input switching shortcut in keyboard hints * Verify input setup and composition stay quiet * Use Super+I for input switching independently of Fcitx keys * Restore linked input settings and attribute test notifications to Fcitx * Correct the input setup shortcut hint * Keep input tooltips to the name and switching shortcut * Place the input language selector with the right bar widgets * Show input switching only for multiple layouts or methods * Unify input method and keyboard layout switching * Select keyboard layouts and input methods in the Typing menu * Apply typing selections immediately from the menu * Use installer keyboard choices and keep typing selections accurate * Queue typing changes and wait for saved selections * Keep queued typing selections when reopening the menu * Cycle keyboard layouts alongside composition engines * Avoid shadowing Python typing and retain pending mixed input selections * Keep input clicks working through controller and compositor failures * Move the keyboard layout widget to the right side of existing bars Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Map keyboard choices to the layouts the installer sets Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the switching order when changing input methods Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Set input method fonts only after the selection saves Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Restore confirmed picker checks when an immediate change fails Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Route Super+I through the shell's input reader so unfocused choices stick Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Restore the input group when the font preference cannot be written Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop the azerty conversion now that the installer no longer offers it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Apply the owner's keyboard layout once before persisting the input choice A quattro merge kept a second apply_keyboard after the retry loop, which re-ran loadkeys and firstboot, ignored their failure, and overwrote the XKB layout setup supplies for keymaps kbd-model-map lacks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Give the Lao keyboard choice a Lao desktop layout The row used la-latin1, kbd's Latin American Spanish keymap, so Lao installs typed Spanish. There is no Lao console keymap; like Korean, the console stays US and the desktop gets la, which Omarchy keeps behind US as a non-Latin layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Load engines installed after Fcitx started when they are selected The Typing menu refused an engine whose package an update had installed, since Fcitx discovers engines only at startup. Restart input once and wait for the engine; name the package when it really is not installed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cycle input methods back with Super+Shift+I Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop notification action buttons now that the Pinyin prompt is suppressed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cycle input only through the shell's reader Without the shell there is no bar or menu either, so the one-shot cycling path only duplicated the reader's logic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add engines from the command line through the Typing menu's path omarchy setup input <engine> had its own restart, append, select and live-rollback code in configure.py, parallel to the menu's. It now installs the packages and appends through typing_setup, leaving configure.py with install-time seeding and update defaults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep Ctrl+Space out of Fcitx's toggles instead of probing for it The terminal prefix bind read the Fcitx config and profile on every press to avoid fighting a Ctrl+Space toggle. configure --defaults now removes that key from every trigger list, so the bind only checks for a terminal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Run the input-method scripts with the system Python A bare python resolves through PATH, so a mise, pyenv or venv interpreter could run them without the system's PyGObject. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
256 lines
11 KiB
Bash
Executable File
256 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
|
|
|
tmp_dir=$(mktemp -d)
|
|
trap 'rm -r "$tmp_dir"' EXIT
|
|
|
|
# gum reads its scripted answers from GUM_SCRIPT, one "status:output" line per
|
|
# invocation, so a test spells out exactly what the human did at each screen:
|
|
# "0:dhh" answers, "1:" is Esc, "130:" is Ctrl+C. Only the widgets that take a
|
|
# list drain stdin, matching the real ones, so the piped layouts and timezones
|
|
# can be asserted.
|
|
cat >"$tmp_dir/gum" <<'EOF'
|
|
#!/bin/bash
|
|
count=$(($(cat "$GUM_COUNT") + 1))
|
|
printf '%s' "$count" >"$GUM_COUNT"
|
|
printf '%s\n' "$*" >>"$GUM_ARGS"
|
|
|
|
case $1 in
|
|
choose | filter) cat >"$GUM_DIR/stdin.$count" ;;
|
|
esac
|
|
|
|
line=$(sed -n "${count}p" "$GUM_SCRIPT")
|
|
printf '%s\n' "${line#*:}"
|
|
exit "${line%%:*}"
|
|
EOF
|
|
|
|
cat >"$tmp_dir/tzupdate" <<'EOF'
|
|
#!/bin/bash
|
|
[[ -n ${TZ_GUESS:-} ]] || exit 1
|
|
printf '%s\n' "$TZ_GUESS"
|
|
EOF
|
|
|
|
cat >"$tmp_dir/timedatectl" <<'EOF'
|
|
#!/bin/bash
|
|
printf '%s\n' UTC Europe/Copenhagen America/Chicago
|
|
EOF
|
|
|
|
# Calls one prompt bare under `set -euo pipefail` — the shape that makes the
|
|
# status capture load-bearing. A cancelled prompt is a failing assignment, so a
|
|
# regression to a plain `status=$?` kills the shell before the function can
|
|
# return; both cases exit with the same status, and this marker is the only
|
|
# thing that tells them apart.
|
|
cat >"$tmp_dir/driver" <<'EOF'
|
|
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
set -T
|
|
|
|
source "$ROOT/install/provisioning/setup-form.sh"
|
|
|
|
notice() { printf '%s\n' "$1" >>"$NOTICES"; }
|
|
|
|
if [[ -n ${TAKEN_USERS:-} ]]; then
|
|
omarchy_username_taken() { [[ " $TAKEN_USERS " == *" $1 "* ]]; }
|
|
fi
|
|
|
|
trap 'if [[ ${FUNCNAME[0]:-} == "$PROMPT_FN" ]]; then printf "returned\n" >>"$MARKER"; fi' RETURN
|
|
|
|
"$PROMPT_FN"
|
|
|
|
printf 'keyboard=%s\n' "${keyboard:-}"
|
|
printf 'keyboard_label=%s\n' "${keyboard_label:-}"
|
|
printf 'keyboard_input_method=%s\n' "${keyboard_input_method:-}"
|
|
printf 'keyboard_xkb_layout=%s\n' "${keyboard_xkb_layout:-}"
|
|
printf 'username=%s\n' "${username:-}"
|
|
printf 'password=%s\n' "${password:-}"
|
|
printf 'password_confirmation=%s\n' "${password_confirmation:-}"
|
|
printf 'full_name=%s\n' "${full_name:-}"
|
|
printf 'email_address=%s\n' "${email_address:-}"
|
|
printf 'hostname=%s\n' "${hostname:-}"
|
|
printf 'timezone=%s\n' "${timezone:-}"
|
|
EOF
|
|
|
|
chmod +x "$tmp_dir/gum" "$tmp_dir/tzupdate" "$tmp_dir/timedatectl" "$tmp_dir/driver"
|
|
export PATH="$tmp_dir:$PATH"
|
|
export GUM_DIR="$tmp_dir" GUM_SCRIPT="$tmp_dir/script" GUM_ARGS="$tmp_dir/args" GUM_COUNT="$tmp_dir/count"
|
|
export NOTICES="$tmp_dir/notices" MARKER="$tmp_dir/marker"
|
|
|
|
status=0
|
|
|
|
# run_prompt <function> <status:output>... — each response answers one gum screen
|
|
run_prompt() {
|
|
local prompt=$1
|
|
shift
|
|
|
|
printf '%s\n' "$@" >"$GUM_SCRIPT"
|
|
printf '0' >"$GUM_COUNT"
|
|
: >"$GUM_ARGS"
|
|
: >"$NOTICES"
|
|
: >"$MARKER"
|
|
rm -f "$tmp_dir"/stdin.*
|
|
|
|
PROMPT_FN="$prompt" "$tmp_dir/driver" >"$tmp_dir/out" 2>"$tmp_dir/err" && status=0 || status=$?
|
|
}
|
|
|
|
field() { sed -n "s/^$1=//p" "$tmp_dir/out"; }
|
|
|
|
assert_returned() {
|
|
grep -qx returned "$MARKER" ||
|
|
fail "$1" "$(printf 'the prompt never returned; the shell died inside it:\n%s' "$(<"$tmp_dir/err")")"
|
|
}
|
|
|
|
assert_status() {
|
|
((status == $1)) || fail "$2" "expected status: $1
|
|
actual status: $status"
|
|
}
|
|
|
|
assert_notices() {
|
|
local description=$1 expected=$2
|
|
local actual
|
|
actual=$(<"$NOTICES")
|
|
[[ $actual == "$expected" ]] || fail "$description" "expected notices: $expected
|
|
actual notices: $actual"
|
|
}
|
|
|
|
# The contract both callers read, and the ordering the shared list exists to keep
|
|
source "$ROOT/install/provisioning/setup-form.sh"
|
|
|
|
((OMARCHY_FORM_BACK == 1)) || fail "Esc reports status 1"
|
|
((OMARCHY_FORM_SIGNAL == 130)) || fail "Ctrl+C reports status 130"
|
|
[[ $(printf '%s\n' "$OMARCHY_KEYBOARD_LAYOUTS" | head -n 1) == "English (US)|us" ]] ||
|
|
fail "English (US) leads the keyboard layouts so gum choose opens on the default"
|
|
pass "the form publishes the 0/1/130 status contract and leads with English (US)"
|
|
|
|
# Keyboard
|
|
|
|
run_prompt omarchy_prompt_keyboard "0:German"
|
|
assert_status 0 "keyboard prompt succeeds"
|
|
[[ $(field keyboard) == "de" ]] || fail "keyboard prompt resolves the label to a keymap"
|
|
[[ $(field keyboard_label) == "German" ]] || fail "keyboard prompt keeps the label for the summary"
|
|
[[ $(head -n 1 "$tmp_dir/stdin.1") == "English (US)" ]] || fail "keyboard prompt offers English (US) first"
|
|
grep -qF -- '--selected English (US)' "$GUM_ARGS" || fail "keyboard prompt preselects English (US)"
|
|
pass "keyboard prompt maps the chosen label to its keymap"
|
|
|
|
for selection in 'Japanese|jp106|mozc|' 'Japanese (US keyboard)|us|mozc|' 'Korean|us|hangul|kr' 'Lao|us|none|la' 'Chinese (Simplified, Pinyin)|us|pinyin|' 'Chinese (Traditional, Zhuyin)|us|chewing|'; do
|
|
IFS='|' read -r label keymap method layout <<< "$selection"
|
|
run_prompt omarchy_prompt_keyboard "0:$label"
|
|
assert_status 0 "$label can be selected at install"
|
|
[[ $(field keyboard) == "$keymap" && $(field keyboard_input_method) == "$method" && $(field keyboard_xkb_layout) == "$layout" ]] ||
|
|
fail "$label carries a real console keymap and a separate input preference"
|
|
done
|
|
pass "language keyboard choices carry their keymap, input method, and desktop override"
|
|
|
|
keyboard_input_method=hangul keyboard_xkb_layout=kr omarchy_persist_input_selection "$tmp_dir/target"
|
|
[[ $(<"$tmp_dir/target/etc/omarchy/input-method") == $'INPUT_METHOD=hangul\nXKB_LAYOUT=kr' ]] || fail "first-boot setup persists the selection for user finalization"
|
|
pass "first-boot setup persists the selection for user finalization"
|
|
|
|
run_prompt omarchy_prompt_keyboard "1:"
|
|
assert_status "$OMARCHY_FORM_BACK" "keyboard prompt reports Esc as back"
|
|
assert_returned "keyboard prompt survives Esc under set -e"
|
|
|
|
run_prompt omarchy_prompt_keyboard "130:"
|
|
assert_status "$OMARCHY_FORM_SIGNAL" "keyboard prompt reports Ctrl+C as the caller's signal"
|
|
assert_returned "keyboard prompt survives Ctrl+C under set -e"
|
|
pass "keyboard prompt propagates Esc and Ctrl+C without dying under set -e"
|
|
|
|
# Username
|
|
|
|
TAKEN_USERS=dhh run_prompt omarchy_prompt_username "0:Not A Username" "0:root" "0:cups-browsed" "0:dhh" "0:david"
|
|
assert_status 0 "username prompt accepts a valid name"
|
|
[[ $(field username) == "david" ]] || fail "username prompt keeps re-asking until the name is valid"
|
|
assert_notices "username prompt explains each rejection" "Username must be alphanumeric with no spaces
|
|
Username is reserved for system
|
|
Username is reserved for system
|
|
That username already exists on this machine"
|
|
pass "username prompt rejects malformed, reserved, and taken names"
|
|
|
|
run_prompt omarchy_prompt_username "1:"
|
|
assert_status "$OMARCHY_FORM_BACK" "username prompt reports Esc as back"
|
|
assert_returned "username prompt survives Esc under set -e"
|
|
|
|
run_prompt omarchy_prompt_username "130:"
|
|
assert_status "$OMARCHY_FORM_SIGNAL" "username prompt reports Ctrl+C as the caller's signal"
|
|
assert_returned "username prompt survives Ctrl+C under set -e"
|
|
pass "username prompt propagates Esc and Ctrl+C without dying under set -e"
|
|
|
|
# Password
|
|
|
|
run_prompt omarchy_prompt_password "0:one" "0:two" "0:" "0:" "0:s3cret" "0:s3cret"
|
|
assert_status 0 "password prompt accepts a confirmed password"
|
|
[[ $(field password) == "s3cret" ]] || fail "password prompt keeps the confirmed password"
|
|
assert_notices "password prompt explains each rejection" "Passwords didn't match!
|
|
Your password can't be blank!"
|
|
pass "password prompt rejects mismatched and blank passwords"
|
|
|
|
run_prompt omarchy_prompt_password "0:s3cret" "1:"
|
|
assert_status "$OMARCHY_FORM_BACK" "password prompt reports Esc on the confirmation as back"
|
|
assert_returned "password confirmation survives Esc under set -e"
|
|
|
|
run_prompt omarchy_prompt_password "0:s3cret" "130:"
|
|
assert_status "$OMARCHY_FORM_SIGNAL" "password prompt reports Ctrl+C on the confirmation as the caller's signal"
|
|
assert_returned "password confirmation survives Ctrl+C under set -e"
|
|
pass "password confirmation propagates Esc and Ctrl+C without dying under set -e"
|
|
|
|
# Identity — both fields are skippable, so empty is an answer and not a cancel
|
|
|
|
run_prompt omarchy_prompt_identity "0:" "0:"
|
|
assert_status 0 "identity prompt treats empty fields as answers"
|
|
[[ -z $(field full_name) && -z $(field email_address) ]] || fail "identity prompt leaves skipped fields empty"
|
|
pass "identity prompt accepts skipped fields"
|
|
|
|
run_prompt omarchy_prompt_identity "0:David" "1:"
|
|
assert_status "$OMARCHY_FORM_BACK" "identity prompt reports Esc on the email as back"
|
|
assert_returned "identity prompt survives Esc under set -e"
|
|
pass "identity prompt propagates Esc from its second field"
|
|
|
|
# Hostname
|
|
|
|
run_prompt omarchy_prompt_hostname "0:-nope-" "0:workshop"
|
|
assert_status 0 "hostname prompt accepts a valid hostname"
|
|
[[ $(field hostname) == "workshop" ]] || fail "hostname prompt keeps re-asking until the hostname is valid"
|
|
assert_notices "hostname prompt explains the rejection" "Hostname must be 1-63 letters, digits, or dashes, and cannot start or end with a dash"
|
|
|
|
run_prompt omarchy_prompt_hostname "0:"
|
|
assert_status 0 "hostname prompt accepts an empty hostname"
|
|
[[ $(field hostname) == "$OMARCHY_HOSTNAME_DEFAULT" ]] || fail "hostname prompt falls back to the default hostname"
|
|
pass "hostname prompt rejects malformed names and defaults an empty one"
|
|
|
|
run_prompt omarchy_prompt_hostname "1:"
|
|
assert_status "$OMARCHY_FORM_BACK" "hostname prompt reports Esc as back"
|
|
assert_returned "hostname prompt survives Esc under set -e"
|
|
pass "hostname prompt propagates Esc without dying under set -e"
|
|
|
|
# Timezone
|
|
|
|
TZ_GUESS=Europe/Copenhagen run_prompt omarchy_prompt_timezone "0:Europe/Copenhagen"
|
|
assert_status 0 "timezone prompt accepts the geo guess"
|
|
[[ $(field timezone) == "Europe/Copenhagen" ]] || fail "timezone prompt keeps the chosen timezone"
|
|
grep -qF -- '--selected Europe/Copenhagen' "$GUM_ARGS" || fail "timezone prompt preselects the geo guess"
|
|
grep -qF UTC "$tmp_dir/stdin.1" || fail "timezone prompt offers the system timezone list"
|
|
pass "timezone prompt preselects the geo guess when one is available"
|
|
|
|
# An unnetworked first boot has no guess, and the fallback has to survive `set -e`
|
|
run_prompt omarchy_prompt_timezone "0:America/Chicago"
|
|
assert_status 0 "timezone prompt survives a failed geo guess"
|
|
[[ $(field timezone) == "America/Chicago" ]] || fail "timezone prompt keeps the filtered timezone"
|
|
[[ $(head -n 1 "$GUM_ARGS") == filter* ]] || fail "timezone prompt filters when there is no geo guess"
|
|
pass "timezone prompt falls back to filtering when the geo guess fails"
|
|
|
|
run_prompt omarchy_prompt_timezone "0:"
|
|
assert_status 0 "timezone prompt accepts an empty selection"
|
|
[[ $(field timezone) == "UTC" ]] || fail "timezone prompt falls back to UTC"
|
|
pass "timezone prompt falls back to UTC when nothing is selected"
|
|
|
|
run_prompt omarchy_prompt_timezone "1:"
|
|
assert_status "$OMARCHY_FORM_BACK" "timezone prompt reports Esc as back"
|
|
assert_returned "timezone prompt survives Esc under set -e"
|
|
|
|
TZ_GUESS=Europe/Copenhagen run_prompt omarchy_prompt_timezone "130:"
|
|
assert_status "$OMARCHY_FORM_SIGNAL" "timezone prompt reports Ctrl+C as the caller's signal"
|
|
assert_returned "timezone prompt survives Ctrl+C under set -e"
|
|
pass "timezone prompt propagates Esc and Ctrl+C without dying under set -e"
|