Add Wi-Fi QR sharing to network panel (#6463)
* Add Wi-Fi QR sharing to network panel * Refine Wi-Fi QR sharing * Use QR glyph for Wi-Fi sharing * Add click-to-reveal password to the Wi-Fi share card Scanning the QR is the fast path, but the person typing on a laptop needs the actual password. A dimmed "Show password" hint under the QR toggles the secret in place. The password stays out of the shell until asked for: a click runs the new omarchy-network-password helper (a private pipe, never an argument), and closing the card drops it again. Open and enterprise networks never show the control. The card loses its Close button -- Escape and clicking outside already cover it -- and now sizes itself to its content. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Keep a dismissed Wi-Fi share card closed and make Escape reliable Closing the card mid-generation killed the helper, but its buffered stdout still arrived and repopulated the matrix, reopening the card the user just closed. Both collectors now honor qrExpectedStop, and the flag survives onExited because exit and stream-finished have no guaranteed order. The password fetch gets the same treatment so a reveal in flight during dismissal can't stash the secret into a closed card's state. The content's focus was claimed while the window was still unmapped, so Escape could land nowhere. Re-acquire it after mapping, the way KeyboardPanel does. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Share WEP networks correctly and restore the QR quiet zone NetworkManager models WEP as key-mgmt "none" plus a wep-key, so the QR helper encoded WEP networks as open -- a QR that scans fine and then silently fails to join. Encode them as T:WEP and let the password helper print the key. Also widen qrencode's margin from 2 to the spec's 4-module quiet zone; the card surround is dark, so that white border is all a scanner gets. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
David Heinemeier Hansson
parent
1ea910f662
commit
a79d1dc8da
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
mkdir -p "$tmp/bin"
|
||||
|
||||
cat >"$tmp/bin/nmcli" <<'EOF'
|
||||
#!/bin/bash
|
||||
if [[ $* == *GENERAL.CON-UUID* ]]; then
|
||||
echo test-uuid
|
||||
else
|
||||
printf '%s' "$PW_NMCLI_FIELDS"
|
||||
fi
|
||||
EOF
|
||||
chmod +x "$tmp/bin/nmcli"
|
||||
|
||||
run_failure_case() {
|
||||
local description=$1 fields=$2 expected_error=$3
|
||||
local error
|
||||
|
||||
export PW_NMCLI_FIELDS=$fields
|
||||
if PATH="$tmp/bin:$PATH" "$ROOT/bin/omarchy-network-password" wlan0 >"$tmp/output" 2>"$tmp/error"; then
|
||||
fail "$description" "helper unexpectedly succeeded"
|
||||
fi
|
||||
error=$(<"$tmp/error")
|
||||
[[ $error == "$expected_error" ]] || fail "$description" "expected: $expected_error\nactual: $error"
|
||||
pass "$description"
|
||||
}
|
||||
|
||||
# The password comes back raw -- no QR escaping -- because it is shown to a
|
||||
# human, not embedded in a WIFI: payload.
|
||||
export PW_NMCLI_FIELDS=$'wpa-psk\np,a:ss;word\\42\n'
|
||||
output=$(PATH="$tmp/bin:$PATH" "$ROOT/bin/omarchy-network-password" wlan0)
|
||||
[[ $output == 'p,a:ss;word\42' ]] || fail "network password helper prints the raw password" "expected: p,a:ss;word\\42\nactual: $output"
|
||||
pass "network password helper prints the raw password"
|
||||
|
||||
run_failure_case \
|
||||
"network password helper refuses open networks" \
|
||||
$'none\n\n' \
|
||||
"This network has no password"
|
||||
|
||||
# WEP looks like an open network (key-mgmt "none") but carries a wep-key.
|
||||
export PW_NMCLI_FIELDS=$'none\n\nwep-secret\n'
|
||||
output=$(PATH="$tmp/bin:$PATH" "$ROOT/bin/omarchy-network-password" wlan0)
|
||||
[[ $output == "wep-secret" ]] || fail "network password helper prints WEP keys" "expected: wep-secret\nactual: $output"
|
||||
pass "network password helper prints WEP keys"
|
||||
|
||||
run_failure_case \
|
||||
"network password helper refuses enterprise networks" \
|
||||
$'wpa-eap\nsecret\n' \
|
||||
"Enterprise Wi-Fi has no shareable password"
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
mkdir -p "$tmp/bin"
|
||||
|
||||
cat >"$tmp/bin/nmcli" <<'EOF'
|
||||
#!/bin/bash
|
||||
if [[ $* == *GENERAL.CON-UUID* ]]; then
|
||||
echo test-uuid
|
||||
else
|
||||
printf '%s' "$QR_NMCLI_FIELDS"
|
||||
fi
|
||||
EOF
|
||||
|
||||
cat >"$tmp/bin/qrencode" <<'EOF'
|
||||
#!/bin/bash
|
||||
for arg in "$@"; do
|
||||
[[ $arg != WIFI:* ]] || exit 97
|
||||
done
|
||||
payload=$(</dev/stdin)
|
||||
printf '%s' "$payload" >"$QR_PAYLOAD_FILE"
|
||||
printf '## \n ## \n ##\n'
|
||||
EOF
|
||||
chmod +x "$tmp/bin/nmcli" "$tmp/bin/qrencode"
|
||||
|
||||
run_success_case() {
|
||||
local description=$1 fields=$2 expected_payload=$3
|
||||
local expected output payload
|
||||
|
||||
export QR_NMCLI_FIELDS=$fields
|
||||
export QR_PAYLOAD_FILE="$tmp/payload"
|
||||
output=$(PATH="$tmp/bin:$PATH" "$ROOT/bin/omarchy-network-qr" wlan0)
|
||||
expected=$'100\n010\n001'
|
||||
[[ $output == "$expected" ]] || fail "$description emits a compact module matrix" "expected: $expected\nactual: $output"
|
||||
|
||||
payload=$(<"$QR_PAYLOAD_FILE")
|
||||
[[ $payload == "$expected_payload" ]] || fail "$description generates the Wi-Fi payload" "expected: $expected_payload\nactual: $payload"
|
||||
pass "$description"
|
||||
}
|
||||
|
||||
run_success_case \
|
||||
"network QR helper escapes WPA credentials through stdin" \
|
||||
$'Cafe;Guest\\5G\nwpa-psk\np,a:ss;word\\42\nno\n' \
|
||||
'WIFI:T:WPA;S:Cafe\;Guest\\5G;P:p\,a\:ss\;word\\42;;'
|
||||
|
||||
run_success_case \
|
||||
"network QR helper supports open networks" \
|
||||
$'Cafe Open\nnone\n\nno\n' \
|
||||
'WIFI:T:nopass;S:Cafe Open;P:;;'
|
||||
|
||||
run_success_case \
|
||||
"network QR helper marks hidden networks" \
|
||||
$'Hidden Network\nwpa-psk\nsecret\nyes\n' \
|
||||
'WIFI:T:WPA;S:Hidden Network;P:secret;H:true;;'
|
||||
|
||||
# NetworkManager models WEP as key-mgmt "none" plus a wep-key, which must not
|
||||
# be mistaken for an open network.
|
||||
run_success_case \
|
||||
"network QR helper encodes WEP networks" \
|
||||
$'Old Router\nnone\n\nno\nwep-secret\n' \
|
||||
'WIFI:T:WEP;S:Old Router;P:wep-secret;;'
|
||||
|
||||
export QR_NMCLI_FIELDS=$'Enterprise\nwpa-eap\nsecret\nno\n'
|
||||
export QR_PAYLOAD_FILE="$tmp/enterprise-payload"
|
||||
if PATH="$tmp/bin:$PATH" "$ROOT/bin/omarchy-network-qr" wlan0 >"$tmp/enterprise-output" 2>"$tmp/enterprise-error"; then
|
||||
fail "network QR helper rejects enterprise networks" "helper unexpectedly succeeded"
|
||||
fi
|
||||
enterprise_error=$(<"$tmp/enterprise-error")
|
||||
expected_error="Enterprise Wi-Fi cannot be shared with a password QR code"
|
||||
[[ $enterprise_error == "$expected_error" ]] || fail "network QR helper rejects enterprise networks" "expected: $expected_error\nactual: $enterprise_error"
|
||||
[[ ! -e $QR_PAYLOAD_FILE ]] || fail "network QR helper rejects enterprise networks" "qrencode unexpectedly ran"
|
||||
pass "network QR helper rejects enterprise networks"
|
||||
@@ -90,6 +90,15 @@ assertDeepEqual(rows.map(row => row.ssid), ['Connected', 'Known', 'Open'], 'netw
|
||||
assertEqual(network.wifiSectionTitle(rows, 0), 'KNOWN NETWORKS', 'network labels known wifi section')
|
||||
assertEqual(network.wifiSectionTitle(rows, 2), 'OTHER NETWORKS', 'network labels other wifi section')
|
||||
|
||||
assertDeepEqual(
|
||||
network.parseQrMatrix('010\n111\n010\n'),
|
||||
{ rows: ['010', '111', '010'], size: 3 },
|
||||
'network parses a square QR matrix'
|
||||
)
|
||||
assertDeepEqual(network.parseQrMatrix('01\n111\n'), { rows: [], size: 0 }, 'network rejects ragged QR rows')
|
||||
assertDeepEqual(network.parseQrMatrix('010\n101\n'), { rows: [], size: 0 }, 'network rejects a non-square QR matrix')
|
||||
assertDeepEqual(network.parseQrMatrix('010\n1x1\n010\n'), { rows: [], size: 0 }, 'network rejects invalid QR modules')
|
||||
|
||||
const reasons = { NoSecrets: 1, WifiAuthTimeout: 2, WifiNetworkLost: 3, WifiClientDisconnected: 4, WifiClientFailed: 5 }
|
||||
assertEqual(network.networkFailureReason(1, reasons), 'Passphrase required', 'network maps missing passphrase failures')
|
||||
assertEqual(network.networkFailureReason(2, reasons), 'Wrong password', 'network maps auth timeout failures')
|
||||
@@ -118,7 +127,6 @@ assertDeepEqual(
|
||||
|
||||
|
||||
|
||||
assertEqual(network.headerDetail({ type: 'wifi', freq: '5745' }), '5ghz', 'network header shows the wifi band when the toggle is hidden')
|
||||
assertEqual(network.headerDetail({ type: 'wifi', freq: '5745' }, true), '', 'network header drops the wifi band when the toggle shows it')
|
||||
assertEqual(network.headerDetail({ type: 'ethernet', speed: '100' }, true), '100mbit', 'network header keeps ethernet speed regardless of the band toggle')
|
||||
assertEqual(network.headerDetail({ type: 'wifi', freq: '5745' }), '', 'network keeps wifi band state out of the hero')
|
||||
assertEqual(network.headerDetail({ type: 'ethernet', speed: '100' }), '100mbit', 'network keeps ethernet speed in the hero')
|
||||
JS
|
||||
|
||||
Reference in New Issue
Block a user