diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index 442d22f..a2d8ad8 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -19,6 +19,19 @@ Template: --- +## 2026-07-15 — V2 permanent checks for #104 / #122 +- **Task:** V2 everything VM-checkable from the #104/#122 batch. +- **Did:** Extracted `pkgs/nomarchy-airplane` (overlay). Added + `checks.airplane` (session state machine + menu/keybind/whole-swap + surfaces; mocked nmcli) and `checks.fastfetch-logo` (sextant count + + module flags). Host V2 artifacts stay under `/tmp/nomarchy-v2-batch2` + and `/tmp/nomarchy-v2-122/`. +- **Verified:** **V2** — both new checks green; prior mock round-trip + + kitty screenshots; #107 migrate re-checked; ecosystem keys in HM conf; + flake check --no-build. +- **Pending:** V3 airplane radios (HARDWARE-QUEUE). +- **Next suggestion:** #111 Recovery menu, or #98 with screenshot. + ## 2026-07-15 — #104 airplane mode + #122 fastfetch sextant logo - **Task:** NEXT #104 (Airplane) + #122 (crisper fastfetch logo). - **Did:** (104) `nomarchy-airplane` runtime Wi-Fi+BT kill-switch with prior @@ -29,7 +42,8 @@ Template: bash -n; keybinds-cheatsheet; menu-back; flake check --no-build; HM gen. **V2** fastfetch: real kitty screenshots `/tmp/nomarchy-v2-122/{boreal-sextant,summer-kitty,boreal-block}.png` — - sextant clearly finer than block; no tofu; accents track. + sextant clearly finer than block; no tofu; accents track. (Superseded by + permanent checks entry above.) - **Pending:** V3 airplane on a machine with live radios (HARDWARE-QUEUE). - **Next suggestion:** #111 Recovery menu, or #98 with screenshot. diff --git a/flake.nix b/flake.nix index 75ee1b8..7a3c1c2 100644 --- a/flake.nix +++ b/flake.nix @@ -90,6 +90,7 @@ nomarchy-first-boot = final.callPackage ./pkgs/nomarchy-first-boot { }; nomarchy-what-changed = final.callPackage ./pkgs/nomarchy-what-changed { }; nomarchy-detect-hw = final.callPackage ./pkgs/nomarchy-detect-hw { }; + nomarchy-airplane = final.callPackage ./pkgs/nomarchy-airplane { }; # pull / rebuild / home (+ legacy aliases) — also on HM so a home # switch can refresh a broken system-package nomarchy-pull. # Includes nomarchy-what-changed (generation readability, #82). @@ -521,6 +522,90 @@ assert assertMsg ok ("keybinds-cheatsheet: " + msg); pkgs.runCommand "nomarchy-keybinds-cheatsheet" { } "touch $out"; + # Airplane mode (#104): mocked nmcli round-trip — engage kills + # Wi-Fi, disengage restores prior; already-off Wi-Fi stays off; + # status self-hides while off. Real radios are V3 (HARDWARE-QUEUE). + airplane = pkgs.runCommand "nomarchy-airplane" + { nativeBuildInputs = [ pkgs.bash pkgs.coreutils pkgs.gnugrep ]; } + '' + set -eu + air=${pkgs.nomarchy-airplane}/bin/nomarchy-airplane + # Headless stubs: real nmcli/rfkill need a machine bus; the + # state-machine + Waybar self-hide contract is what we assert + # here. Prior-restore against live radios is V3. + # Shebang must be an absolute store bash — pure sandbox has + # no /usr/bin/env. + mock=$(mktemp -d) + shebang='#!${pkgs.bash}/bin/bash' + for c in nmcli rfkill bluetoothctl notify-send pkill; do + printf '%s\n' "$shebang" 'true' > "$mock/$c" + chmod +x "$mock/$c" + done + # wifi_enabled reads nmcli -t -f WIFI g → report "enabled" + printf '%s\n' "$shebang" \ + 'case "$*" in' \ + '"-t -f WIFI g") echo enabled ;;' \ + '*) true ;;' \ + 'esac' > "$mock/nmcli" + chmod +x "$mock/nmcli" + export PATH="$mock:$PATH" + + rt=$(mktemp -d) + export XDG_RUNTIME_DIR=$rt + [ "$($air is-active)" = off ] + [ -z "$($air status)" ] + $air on + [ "$($air is-active)" = on ] + $air status | grep -q '"class":"on"' + [ -f "$rt/nomarchy-airplane.state" ] + grep -q 'wifi_prior=1' "$rt/nomarchy-airplane.state" + $air off + [ "$($air is-active)" = off ] + [ -z "$($air status)" ] + [ ! -f "$rt/nomarchy-airplane.state" ] + + grep -q 'airplane' ${./modules/home/rofi.nix} + grep -q 'Airplane mode' ${./modules/home/rofi.nix} + grep -q 'nomarchy-menu airplane' ${./modules/home/keybinds.nix} + for t in boreal executive-slate summer-day summer-night; do + grep -q 'custom/airplane' ${./themes}/$t/waybar.jsonc + grep -q 'custom-airplane' ${./themes}/$t/waybar.css + done + + touch $out + ''; + + # Fastfetch logo (#122): baked logo must be sextant-based (not + # the old half/full-block grid). Visual quality is V2 kitty / + # hardware; this asserts the renderer flag actually landed. + fastfetch-logo = + let + logo = pkgs.runCommand "nomarchy-fastfetch-logo-check" + { nativeBuildInputs = [ pkgs.imagemagick pkgs.librsvg pkgs.chafa ]; } + '' + rsvg-convert -w 360 ${./modules/nixos/branding/logo.svg} > logo.png + magick logo.png -fill "#B79BE8" -colorize 100 logo-c.png + chafa --format symbols --symbols sextant --size 24x12 \ + --colors full --polite on logo-c.png > $out + ''; + in + pkgs.runCommand "nomarchy-fastfetch-logo" + { nativeBuildInputs = [ pkgs.python3 ]; } + '' + set -eu + grep -q 'symbols sextant' ${./modules/home/fastfetch.nix} + grep -q 'size 24x12' ${./modules/home/fastfetch.nix} + python3 - <<'PY' + from pathlib import Path + t = Path("${logo}").read_text(encoding="utf-8") + sext = sum(1 for c in t if 0x1FB00 <= ord(c) <= 0x1FBFF) + if sext < 20: + raise SystemExit(f"too few sextant chars: {sext}") + print(f"fastfetch-logo: {sext} sextant chars") + PY + touch $out + ''; + # Installer keyboard no-variant regression (item 91): gum's `(none)` # row is display-only. Exercise it through the real template # patcher, then build the exact us + empty-variant console keymap diff --git a/modules/home/airplane.nix b/modules/home/airplane.nix index 35b30ba..b6edfa3 100644 --- a/modules/home/airplane.nix +++ b/modules/home/airplane.nix @@ -1,129 +1,10 @@ # Airplane mode — runtime kill-switch for Wi-Fi + Bluetooth together. -# Remembers each radio's prior state so disengage restores what you had -# (if Wi-Fi was already off, it stays off). Waybar shows a plane glyph -# only while engaged (status prints nothing otherwise → self-hides). -# -# State lives under $XDG_RUNTIME_DIR (session-scoped): airplane is a -# travel/session gesture, not a rebuild-baked preference. Survives -# within the seat; a reboot clears it and leaves the radios as the -# firmware/NM left them — same as every phone's airplane mode. +# Implementation: pkgs/nomarchy-airplane. Session state under +# $XDG_RUNTIME_DIR; Waybar plane glyph self-hides when off. { config, lib, pkgs, ... }: -let - # signal 11: poke the bar on toggle so the glyph appears/vanishes - # without waiting for the poll interval (same pattern as recording=8, - # updates=9, doctor=10). - waybarSignal = 11; - - nomarchy-airplane = pkgs.writeShellScriptBin "nomarchy-airplane" '' - set -euo pipefail - rt="''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" - state_file="$rt/nomarchy-airplane.state" - PATH=${lib.makeBinPath [ pkgs.networkmanager pkgs.util-linux pkgs.bluez pkgs.coreutils pkgs.gnugrep pkgs.gawk pkgs.libnotify pkgs.procps ]}:$PATH - - poke_bar() { - pkill -RTMIN+${toString waybarSignal} waybar 2>/dev/null || true - } - - wifi_enabled() { - [ "$(nmcli -t -f WIFI g 2>/dev/null || echo disabled)" = "enabled" ] - } - - # Soft-blocked means "we (or something) turned it off in software". - # Hard-blocked is a laptop kill-switch — we never try to fight that. - bt_soft_unblocked() { - rfkill list bluetooth 2>/dev/null | ${pkgs.gnugrep}/bin/grep -q 'Soft blocked: no' - } - - bt_powered() { - bluetoothctl show 2>/dev/null | ${pkgs.gnugrep}/bin/grep -q 'Powered: yes' - } - - # "Bluetooth was usable" = adapter present and not soft-blocked (or - # already powered). Desktops without BT leave this false. - bt_was_on() { - ls /sys/class/bluetooth/hci* >/dev/null 2>&1 || return 1 - bt_powered || bt_soft_unblocked - } - - engaged() { [ -f "$state_file" ]; } - - engage() { - if engaged; then - notify-send -a Nomarchy "Airplane mode" "Already on." - exit 0 - fi - wifi_prior=0 - bt_prior=0 - wifi_enabled && wifi_prior=1 - bt_was_on && bt_prior=1 - - # Wi-Fi via NetworkManager so nm-applet/waybar stay consistent; - # fall back to rfkill if nmcli is missing (shouldn't happen on - # a Nomarchy desktop). - if command -v nmcli >/dev/null 2>&1; then - nmcli radio wifi off 2>/dev/null || true - else - rfkill block wlan 2>/dev/null || true - fi - # Bluetooth: soft-block the radio (covers adapters that ignore - # bluetoothctl) and ask the daemon to power off when present. - rfkill block bluetooth 2>/dev/null || true - bluetoothctl power off >/dev/null 2>&1 || true - - printf 'wifi_prior=%s\nbt_prior=%s\n' "$wifi_prior" "$bt_prior" > "$state_file" - poke_bar - notify-send -a Nomarchy "Airplane mode" "On — Wi-Fi and Bluetooth off." - } - - disengage() { - if ! engaged; then - notify-send -a Nomarchy "Airplane mode" "Already off." - exit 0 - fi - # shellcheck disable=SC1090 - . "$state_file" - wifi_prior=''${wifi_prior:-0} - bt_prior=''${bt_prior:-0} - - if [ "$wifi_prior" = 1 ]; then - if command -v nmcli >/dev/null 2>&1; then - nmcli radio wifi on 2>/dev/null || true - else - rfkill unblock wlan 2>/dev/null || true - fi - fi - if [ "$bt_prior" = 1 ]; then - rfkill unblock bluetooth 2>/dev/null || true - bluetoothctl power on >/dev/null 2>&1 || true - fi - - rm -f "$state_file" - poke_bar - notify-send -a Nomarchy "Airplane mode" "Off — prior radio state restored." - } - - case "''${1:-toggle}" in - status) - # Waybar: plane glyph only while engaged; empty → self-hide. - if engaged; then - printf '{"text":"󰀝","tooltip":"Airplane mode on — Wi-Fi and Bluetooth off (click to restore)","class":"on"}\n' - fi - exit 0 ;; - is-active) - if engaged; then echo on; else echo off; fi ;; - on) engage ;; - off) disengage ;; - toggle) - if engaged; then disengage; else engage; fi ;; - *) - echo "usage: nomarchy-airplane [toggle|status|on|off|is-active]" >&2 - exit 64 ;; - esac - ''; -in { config = { - home.packages = [ nomarchy-airplane ]; + home.packages = [ pkgs.nomarchy-airplane ]; }; } diff --git a/pkgs/nomarchy-airplane/default.nix b/pkgs/nomarchy-airplane/default.nix new file mode 100644 index 0000000..3c53904 --- /dev/null +++ b/pkgs/nomarchy-airplane/default.nix @@ -0,0 +1,124 @@ +{ lib +, writeShellScriptBin +, networkmanager +, util-linux +, bluez +, coreutils +, gnugrep +, gawk +, libnotify +, procps +}: + +# Runtime Wi-Fi + Bluetooth airplane mode (#104). Session-scoped state under XDG +# $XDG_RUNTIME_DIR; Waybar status self-hides when off (signal 11). +writeShellScriptBin "nomarchy-airplane" '' + set -euo pipefail + rt="''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" + state_file="$rt/nomarchy-airplane.state" + # Prefer an existing PATH prefix (test mocks, user wrappers) then the + # closed store tools so a headless check can inject fake nmcli/rfkill. + PATH=$PATH:${lib.makeBinPath [ + networkmanager util-linux bluez coreutils gnugrep gawk libnotify procps + ]} + + poke_bar() { + pkill -RTMIN+11 waybar 2>/dev/null || true + } + + wifi_enabled() { + [ "$(nmcli -t -f WIFI g 2>/dev/null || echo disabled)" = "enabled" ] + } + + # Soft-blocked means "we (or something) turned it off in software". + # Hard-blocked is a laptop kill-switch — we never try to fight that. + bt_soft_unblocked() { + rfkill list bluetooth 2>/dev/null | grep -q 'Soft blocked: no' + } + + bt_powered() { + bluetoothctl show 2>/dev/null | grep -q 'Powered: yes' + } + + # "Bluetooth was usable" = adapter present and not soft-blocked (or + # already powered). Desktops without BT leave this false. + bt_was_on() { + ls /sys/class/bluetooth/hci* >/dev/null 2>&1 || return 1 + bt_powered || bt_soft_unblocked + } + + engaged() { [ -f "$state_file" ]; } + + engage() { + if engaged; then + notify-send -a Nomarchy "Airplane mode" "Already on." 2>/dev/null || true + exit 0 + fi + wifi_prior=0 + bt_prior=0 + wifi_enabled && wifi_prior=1 + bt_was_on && bt_prior=1 + + # Wi-Fi via NetworkManager so nm-applet/waybar stay consistent; + # fall back to rfkill if nmcli is missing (shouldn't happen on + # a Nomarchy desktop). + if command -v nmcli >/dev/null 2>&1; then + nmcli radio wifi off 2>/dev/null || true + else + rfkill block wlan 2>/dev/null || true + fi + # Bluetooth: soft-block the radio (covers adapters that ignore + # bluetoothctl) and ask the daemon to power off when present. + rfkill block bluetooth 2>/dev/null || true + bluetoothctl power off >/dev/null 2>&1 || true + + printf 'wifi_prior=%s\nbt_prior=%s\n' "$wifi_prior" "$bt_prior" > "$state_file" + poke_bar + notify-send -a Nomarchy "Airplane mode" "On — Wi-Fi and Bluetooth off." 2>/dev/null || true + } + + disengage() { + if ! engaged; then + notify-send -a Nomarchy "Airplane mode" "Already off." 2>/dev/null || true + exit 0 + fi + # shellcheck disable=SC1090 + . "$state_file" + wifi_prior=''${wifi_prior:-0} + bt_prior=''${bt_prior:-0} + + if [ "$wifi_prior" = 1 ]; then + if command -v nmcli >/dev/null 2>&1; then + nmcli radio wifi on 2>/dev/null || true + else + rfkill unblock wlan 2>/dev/null || true + fi + fi + if [ "$bt_prior" = 1 ]; then + rfkill unblock bluetooth 2>/dev/null || true + bluetoothctl power on >/dev/null 2>&1 || true + fi + + rm -f "$state_file" + poke_bar + notify-send -a Nomarchy "Airplane mode" "Off — prior radio state restored." 2>/dev/null || true + } + + case "''${1:-toggle}" in + status) + # Waybar: plane glyph only while engaged; empty → self-hide. + if engaged; then + printf '{"text":"󰀝","tooltip":"Airplane mode on — Wi-Fi and Bluetooth off (click to restore)","class":"on"}\n' + fi + exit 0 ;; + is-active) + if engaged; then echo on; else echo off; fi ;; + on) engage ;; + off) disengage ;; + toggle) + if engaged; then disengage; else engage; fi ;; + *) + echo "usage: nomarchy-airplane [toggle|status|on|off|is-active]" >&2 + exit 64 ;; + esac +''