Merge remote-tracking branch 'origin/quattro' into unified-input-method-setup

This commit is contained in:
David Heinemeier Hansson committed 2026-10-11 07:16:39 +09:00
commit b673d9e942
6 files changed
+152 -12

No files matched your search

+2 -3
View File
@@ -630,15 +630,14 @@ confirm_reboot() {
# XKB settings for the form's keymaps that kbd-model-map lacks, as its rows for
# the nearest keymap give them, so the desktop types what the console does:
# kbd's cz is QWERTY, bg-cp1251 phonetic and azerty French AZERTY. Non-Latin
# layouts keep a US layout to switch to, so the shared password stays typeable.
# kbd's cz is QWERTY and bg-cp1251 phonetic. Non-Latin layouts keep a US layout
# to switch to, so the shared password stays typeable.
keyboard_xkb_settings() {
local layout variant="" options=terminate:ctrl_alt_bksp
local switch=terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll
case $1 in
colemak) layout=us variant=colemak ;;
azerty) layout=fr ;;
bg-cp1251) layout=bg,us variant=phonetic, options=$switch ;;
cz) layout=cz,us variant=qwerty, options=$switch ;;
de_CH-latin1) layout=ch ;;
+1 -1
View File
@@ -14,7 +14,7 @@ The Bolt startup drop-in sets the persisted mode before hardware enumeration whe
Secure-capable devices require both a stored Bolt record and a saved key for automatic trust. An explicit permanent approval pins the current sysfs directory and key descriptor, seeds an empty kernel key before authorization, and verifies Bolt imported it before saving consent. Automatic reconciliation cannot initialize a missing key and retains its read-only sysfs sandbox. First activation is deferred when a captured secure-capable device lacks a saved Bolt key: the `.pending` marker starts a setup notification, while existing Bolt configuration and services remain untouched. Installation can finish; only explicit setup after safe disconnection clears the pending state. Initial identity capture uses Bolt's numeric device/vendor fallback when descriptive sysfs attributes are absent.
The runtime is Bash, with `busctl --json=short` for Bolt's structured D-Bus replies and `jq` for JSON. The root daemon publishes a root-owned snapshot in `/run/omarchy-thunderbolt-authorization`. The user service polls that snapshot and refuses unavailable, stale, or mismatched generations. It does not infer final policy from presence signals. Requests use opaque tokens and bind the policy-service generation, Bolt bus owner, device path, connection timestamp, sysfs inode, and device identity. The inode distinguishes same-second reconnects. Device text is displayed as untrusted data. Failed delivery retries; lost approval responses retain intent and offer another notification. Watcher restarts preserve unfinished intent only while the exact connection and policy-service incarnation remain unchanged. A changed device or daemon requires a new request.
The runtime is Bash, with `busctl --json=short` for Bolt's structured D-Bus replies and `jq` for JSON. The root daemon publishes a root-owned snapshot in `/run/omarchy-thunderbolt-authorization`. The user service polls that snapshot and refuses unavailable, stale, or mismatched generations. Both loops tick every 2 s without forking. While Linux sees no Thunderbolt controller and nothing is connected or boot-protected, the daemon reconciles Bolt every 10 s instead of every tick, still well inside a snapshot's 20 s lifetime; a controller appearing, a policy change or a boot recovery checkpoint restores the 2 s cadence at once. The user service skips an unchanged, fresh snapshot with no devices, warnings or requests in flight. It does not infer final policy from presence signals. Requests use opaque tokens and bind the policy-service generation, Bolt bus owner, device path, connection timestamp, sysfs inode, and device identity. The inode distinguishes same-second reconnects. Device text is displayed as untrusted data. Failed delivery retries; lost approval responses retain intent and offer another notification. Watcher restarts preserve unfinished intent only while the exact connection and policy-service incarnation remain unchanged. A changed device or daemon requires a new request.
The terminal dialog saves an approval intent; the background watcher submits it through `pkexec` to the fixed installed `/usr/bin/omarchy-thunderbolt-authorization-approve` helper. Polkit grants active local wheel users this individual approval action. The helper validates its arguments and the live connection, then independently verifies authorization and any saved trust before returning success. Root entrypoints use protected Bash startup and fixed installed libraries; they discard caller-supplied D-Bus addresses. A shared lock serializes setup, reconciliation, and approvals. General setup uses a separate administrator command invoked with `sudo` from the terminal. Blanket Bolt management requires administrator authentication while protection is enabled. These controls protect against unsolicited accessories, not malicious software already running as an administrator. Identifiers can be forged; Bolt's secure connection keys provide additional verification on supported hardware.
+39 -2
View File
@@ -13,11 +13,29 @@ TB_DEVICE=org.freedesktop.bolt1.Device
TB_DOMAIN=org.freedesktop.bolt1.Domain
TB_PATH=/org/freedesktop/bolt
TB_SYSFS=/sys/bus/thunderbolt/devices
# How often the daemon re-checks Bolt while there is no controller and no boot
# protection: well inside the 20 s a published snapshot stays valid.
TB_IDLE_RECHECK=10
TB_TRUST_JQ='def trusted($state): $state.trusted[.Uid] == {Uid,Name,Vendor} and
((.Secure | not) or (.Stored and (.Key | IN("have","new"))));'
tb_fail() { printf '%s\n' "$*" >&2; return 1; }
# Sleeps without forking on each tick: a pipe open for reading and writing
# never reaches EOF, so a timed read on it returns only on timeout or a signal.
tb_wait() {
[[ -n ${TB_TICK:-} ]] || exec {TB_TICK}<> <(:)
read -r -t "$1" -u "$TB_TICK" _ || true
}
tb_controller_present() {
local domain
for domain in "$TB_SYSFS"/domain*; do
[[ -e $domain ]] && return 0
done
return 1
}
tb_json_write() {
local target=$1 mode=${2:-600} temporary
temporary=$(mktemp "${target%/*}/.thunderbolt.XXXXXX") || return 1
@@ -287,11 +305,24 @@ tb_reconcile() {
}
tb_daemon() {
local snapshot error
local snapshot error policy idle_policy='' idle_since=0
install -d -m 755 "$TB_RUNTIME"
cat /proc/sys/kernel/random/uuid > "$TB_RUNTIME/generation"
chmod 644 "$TB_RUNTIME/generation"
while [[ -f $TB_MARKER ]]; do
policy=''
[[ -r $TB_STATE ]] && IFS= read -r -d '' policy < "$TB_STATE" || true
# With no controller, no device and no boot protection there is nothing to
# restore or reapply, so Bolt is reconciled every TB_IDLE_RECHECK seconds
# instead of every tick. A controller appearing, a policy change or a boot
# recovery checkpoint brings back the full check at once.
if [[ -n $idle_policy && $policy == "$idle_policy" && ! -e ${TB_STATE%/*}/boot-recovery.json ]] &&
(( EPOCHSECONDS - idle_since >= 0 && EPOCHSECONDS - idle_since < TB_IDLE_RECHECK )) &&
! tb_controller_present; then
tb_wait 2
continue
fi
idle_policy=''
(
flock -x 9
if snapshot=$(tb_reconcile 2> "$TB_RUNTIME/error"); then
@@ -303,7 +334,13 @@ tb_daemon() {
tb_json_write "$TB_RUNTIME/snapshot.json" 644
fi
) 9> "$TB_LOCK"
sleep 2
snapshot=''
[[ -r $TB_RUNTIME/snapshot.json ]] && IFS= read -r -d '' snapshot < "$TB_RUNTIME/snapshot.json" || true
if [[ -n $policy ]] && jq -e '.available == true and .domains == [] and .devices == [] and
.warnings == [] and .error == "" and .boot_protection == false' <<< "$snapshot" >/dev/null 2>&1; then
idle_policy=$policy idle_since=$EPOCHSECONDS
fi
tb_wait 2
done
}
+20 -3
View File
@@ -139,15 +139,28 @@ tb_scan() {
}
tb_watch() {
local watch snapshot warning warned='' failures=0
local watch snapshot warning warned='' failures=0 text generation clean='' clean_generation='' clean_time=0
tb_requests
watch=$(cat /proc/sys/kernel/random/uuid)
while true; do
if [[ -f $TB_PENDING && ! -f $TB_MARKER ]]; then
if [[ $warned != "pending" ]] && tb_setup_notice; then warned=pending; fi
sleep 2
clean=''
tb_wait 2
continue
fi
# The last scan's snapshot, unchanged, still fresh and from the same daemon,
# with no device, warning or request in flight, has nothing to deliver.
text='' generation=''
[[ -r $TB_RUNTIME/snapshot.json ]] && IFS= read -r -d '' text < "$TB_RUNTIME/snapshot.json" || true
[[ -r $TB_RUNTIME/generation ]] && IFS= read -r generation < "$TB_RUNTIME/generation" || true
if [[ -n $clean && ${text%$'\n'} == "$clean" && $generation == "$clean_generation" ]] &&
(( EPOCHSECONDS - clean_time >= 0 && EPOCHSECONDS - clean_time < 20 )) &&
! compgen -G "$TB_REQUESTS/request-*.json" >/dev/null; then
tb_wait 2
continue
fi
clean=''
if snapshot=$(tb_scan "$watch"); then
failures=0
warning=$(jq -c '[.generation,.warnings,.error]' <<< "$snapshot")
@@ -157,6 +170,10 @@ tb_watch() {
fi
else
warned=''
if jq -e '.devices == []' <<< "$snapshot" >/dev/null; then
IFS=' ' read -r clean_generation clean_time <<< "$(jq -r '"\(.generation) \(.time)"' <<< "$snapshot")"
[[ $clean_time =~ ^[0-9]+$ ]] && clean=$snapshot
fi
fi
else
failures=$((failures + 1))
@@ -164,7 +181,7 @@ tb_watch() {
warned=unavailable
fi
fi
sleep 2
tb_wait 2
done
}
-1
View File
@@ -34,7 +34,6 @@ OMARCHY_KEYBOARD_LAYOUTS=$'English (US)|us
English (UK)|uk
English (US, Dvorak)|dvorak
English (US, Colemak)|colemak
Azerbaijani|azerty
Belarusian|by
Belgian|be-latin1
Bulgarian|bg-cp1251
@@ -317,7 +317,7 @@ test_deferred_opt_out_retains_initial_enrollment() {
test_pending_notice_retries_without_claiming_protection() {
touch "$TB_PENDING"
local count=0 notices=0
sleep() { count=$((count + 1)); (( count < 3 )) || exit 99; }
tb_wait() { count=$((count + 1)); (( count < 3 )) || exit 99; }
tb_setup_notice() { notices=$((notices + 1)); (( notices > 1 )) && echo pending >> "$T/notifications"; }
reject tb_watch
[[ $(cat "$T/notifications") == "pending" ]]
@@ -567,7 +567,7 @@ test_trusted_reconnect_no_alert() {
}
test_unavailable_warns_once() {
tb_scan() { return 1; }
sleep() { count=$((count+1)); ((count<4)) || exit 99; }
tb_wait() { count=$((count+1)); ((count<4)) || exit 99; }
local count=0
reject tb_watch
[[ $(cat "$T/notifications") == "attention" ]]
@@ -629,6 +629,94 @@ test_snapshot_freshness_and_disabled_state() {
reject tb_public_snapshot
echo generation-b > "$TB_RUNTIME/generation"; reject tb_public_snapshot
}
test_wait_times_out_without_input() {
local start=$EPOCHSECONDS
TB_TICK='' tb_wait 1
(( EPOCHSECONDS - start >= 1 && EPOCHSECONDS - start < 10 ))
}
test_daemon_rechecks_controller_free_bolt_less_often() {
touch "$TB_MARKER"; TB_IDLE_RECHECK=600
local tick=0 extra='{}'
expect() { [[ $(wc -l < "$T/reconciles") == "$1" ]] || fail "tick $tick: expected $1 reconciles" "$(wc -l < "$T/reconciles")"; }
tb_reconcile() {
echo >> "$T/reconciles"
jq -cn --argjson time "$EPOCHSECONDS" --argjson extra "$extra" \
'{available:true,time:$time,generation:"g",domains:[],devices:[],warnings:[],error:"",boot_protection:false} + $extra'
}
tb_wait() {
tick=$((tick + 1))
case $tick in
1) expect 1 ;;
2) expect 1; mkdir "$TB_SYSFS/domain0" ;; # idle: no reconcile
3) expect 2; rmdir "$TB_SYSFS/domain0" ;; # a controller appeared
4) expect 2; edit_state '.trusted.x={}' ;;
5) expect 3; touch "${TB_STATE%/*}/boot-recovery.json" ;; # the policy changed
6) expect 4; rm "${TB_STATE%/*}/boot-recovery.json" # a recovery checkpoint
extra='{"devices":[{"Uid":"device-1"}]}'; edit_state '.trusted.y={}' ;;
7) expect 5 ;; # devices from here on
8) expect 6; rm "$TB_MARKER" ;; # so every tick reconciles
esac
}
tb_daemon
[[ $tick == 8 ]]
}
test_daemon_rechecks_after_the_idle_interval() {
touch "$TB_MARKER"; TB_IDLE_RECHECK=2
local tick=0
tb_reconcile() {
echo >> "$T/reconciles"
jq -cn --argjson time "$EPOCHSECONDS" '{available:true,time:$time,generation:"g",domains:[],devices:[],warnings:[],error:"",boot_protection:false}'
}
tb_wait() {
tick=$((tick + 1))
case $tick in
1) sleep 2.1 ;;
2) [[ $(wc -l < "$T/reconciles") == 2 ]] || fail "the idle check did not expire"; rm "$TB_MARKER" ;;
esac
}
tb_daemon
}
test_daemon_failed_reconcile_keeps_every_tick() {
touch "$TB_MARKER"; TB_IDLE_RECHECK=600
local tick=0
tb_reconcile() { echo >> "$T/reconciles"; tb_fail 'Bolt unavailable'; }
tb_wait() { tick=$((tick + 1)); (( tick < 3 )) || rm "$TB_MARKER"; }
tb_daemon 2>/dev/null
[[ $(wc -l < "$T/reconciles") == 3 ]]
check '.available==false' "$TB_RUNTIME/snapshot.json"
}
test_watch_skips_only_unchanged_clean_snapshots() {
local tick=0 start=$EPOCHSECONDS
publish() { jq -cnS --argjson time "$1" --arg generation generation-a --argjson devices "${2:-[]}" \
'{available:true,time:$time,generation:$generation,devices:$devices,warnings:[],error:""}' > "$TB_RUNTIME/snapshot.json"; }
expect() { [[ $(wc -l < "$T/scans") == "$1" ]] || fail "tick $tick: expected $1 scans" "$(wc -l < "$T/scans")"; }
tb_scan() { echo >> "$T/scans"; cat "$TB_RUNTIME/snapshot.json"; }
publish "$start"
tb_wait() {
tick=$((tick + 1))
case $tick in
1) expect 1 ;;
2) expect 1; touch "$TB_REQUESTS/request-x.json" ;; # unchanged: skipped
3) expect 2; rm "$TB_REQUESTS/request-x.json" ;; # a request in flight
4) expect 2; publish "$((start - 5))" ;;
5) expect 3; echo generation-b > "$TB_RUNTIME/generation" ;; # a new snapshot
6) expect 4; echo generation-a > "$TB_RUNTIME/generation" # another daemon
publish "$start" '[{"identity":{}}]' ;;
7) expect 5 ;; # devices from here on
8) expect 6; exit 0 ;; # so every tick scans
esac
}
(tb_watch)
}
test_watch_rescans_a_stale_snapshot() {
local tick=0
jq -cnS --argjson time "$((EPOCHSECONDS - 30))" \
'{available:true,time:$time,generation:"generation-a",devices:[],warnings:[],error:""}' > "$TB_RUNTIME/snapshot.json"
tb_scan() { echo >> "$T/scans"; cat "$TB_RUNTIME/snapshot.json"; }
tb_wait() { tick=$((tick + 1)); (( tick < 3 )) || exit 0; }
(tb_watch)
[[ $(wc -l < "$T/scans") == 3 ]]
}
test_invalid_state_not_skipped_by_migration() {
echo '{"enabled":false}' > "$TB_STATE"; reject tb_admin migrate
[[ ! -s $T/units ]]