feat: airplane mode (#104) + crisper fastfetch logo (#122)
Some checks failed
Check / eval (push) Has been cancelled
Some checks failed
Check / eval (push) Has been cancelled
Airplane: runtime Wi-Fi+Bluetooth kill-switch with prior-state restore, System menu + SUPER+CTRL+R, Waybar plane glyph (self-hides, whole-swap parity). Fastfetch: chafa sextants at 24×12 (was block 20×10) for a finer monogram; V2 kitty screenshots under boreal and summer-night.
This commit is contained in:
129
modules/home/airplane.nix
Normal file
129
modules/home/airplane.nix
Normal file
@@ -0,0 +1,129 @@
|
||||
# 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.
|
||||
{ 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 ];
|
||||
};
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
./yazi.nix # flagship TUI file manager, themed + plugins
|
||||
./osd.nix # swayosd volume/brightness OSD, themed
|
||||
./nightlight.nix # scheduled blue-light filter (hyprsunset), opt-in
|
||||
./airplane.nix # runtime Wi-Fi+BT airplane mode + Waybar glyph (#104)
|
||||
./autotheme.nix # auto day/night theme switch (settings.autoTheme), opt-in
|
||||
./timezone.nix # keep the Waybar clock in step with auto-timezone changes
|
||||
./updates.nix # passive update-awareness indicator + notification, opt-in
|
||||
|
||||
@@ -9,11 +9,15 @@ let
|
||||
cfg = config.nomarchy;
|
||||
c = cfg.theme.colors;
|
||||
|
||||
# #122: sextants (2×3 subcells) give ~6× the detail of half/full blocks.
|
||||
# Size and raster width stepped up together so the mark still fills the
|
||||
# logo column without looking sparse. Diagonals still staircase a little
|
||||
# (the SVG is an angled N); that is a logo-design limit, not chafa's.
|
||||
logo = pkgs.runCommand "nomarchy-fastfetch-logo"
|
||||
{ nativeBuildInputs = [ pkgs.imagemagick pkgs.librsvg pkgs.chafa ]; } ''
|
||||
rsvg-convert -w 220 ${../nixos/branding/logo.svg} > logo.png
|
||||
rsvg-convert -w 360 ${../nixos/branding/logo.svg} > logo.png
|
||||
magick logo.png -fill "${c.accent}" -colorize 100 logo-c.png
|
||||
chafa --format symbols --symbols block --size 20x10 --colors full --polite on logo-c.png > $out
|
||||
chafa --format symbols --symbols sextant --size 24x12 --colors full --polite on logo-c.png > $out
|
||||
'';
|
||||
in
|
||||
{
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
{ mods = "$mod CTRL"; key = "E"; action = "exec, nomarchy-menu emoji"; desc = "Emoji picker"; group = "Menu"; }
|
||||
{ mods = "$mod CTRL"; key = "N"; action = "exec, nomarchy-menu network"; desc = "Network (networkmanager_dmenu)"; group = "Menu"; }
|
||||
{ mods = "$mod CTRL"; key = "B"; action = "exec, nomarchy-menu bluetooth"; desc = "Bluetooth"; group = "Menu"; }
|
||||
{ mods = "$mod CTRL"; key = "R"; action = "exec, nomarchy-menu airplane"; desc = "Airplane mode (Wi-Fi + Bluetooth)"; group = "Menu"; }
|
||||
{ mods = "$mod CTRL"; key = "K"; action = "exec, nomarchy-menu keyboard"; desc = "Keyboard layout"; group = "Menu"; }
|
||||
{ mods = "$mod CTRL"; key = "S"; action = "exec, nomarchy-menu capture"; desc = "Screenshot / capture"; group = "Media"; }
|
||||
{ mods = "$mod CTRL"; key = "P"; action = "exec, nomarchy-menu colorpicker"; desc = "Color picker (→ clipboard)"; group = "Menu"; }
|
||||
|
||||
@@ -864,6 +864,13 @@ ${themeRows}
|
||||
&& exec nomarchy-nightlight toggle
|
||||
notify-send "Night light" "Not available (nomarchy.nightlight off?)."; exit 0 ;;
|
||||
|
||||
airplane)
|
||||
# Runtime Wi-Fi + Bluetooth kill-switch (#104). Priors restored on
|
||||
# disengage; Waybar plane glyph self-hides when off.
|
||||
command -v nomarchy-airplane >/dev/null 2>&1 \
|
||||
&& exec nomarchy-airplane toggle
|
||||
notify-send "Airplane mode" "Not available."; exit 0 ;;
|
||||
|
||||
autotheme)
|
||||
# Look & Feel › Auto theme — automatic day/night theme switch
|
||||
# (settings.autoTheme, applied by the nomarchy-auto-theme timer from
|
||||
@@ -1169,6 +1176,12 @@ ${themeRows}
|
||||
choice=$( {
|
||||
row "Network${menuHint "network"}" network-wireless
|
||||
row "VPN" network-vpn
|
||||
# Airplane mode (#104): runtime Wi-Fi+BT kill-switch. Label
|
||||
# tracks the session flag; tool is always on PATH via airplane.nix.
|
||||
if [ "$(nomarchy-airplane is-active 2>/dev/null)" = on ]
|
||||
then row "Airplane mode (on)${menuHint "airplane"}" network-wireless-offline
|
||||
else row "Airplane mode (off)${menuHint "airplane"}" network-wireless-offline
|
||||
fi
|
||||
command -v blueman-manager >/dev/null 2>&1 \
|
||||
&& row "Bluetooth${menuHint "bluetooth"}" bluetooth
|
||||
[ -e "''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/pulse/native" ] \
|
||||
@@ -1225,6 +1238,7 @@ ${themeRows}
|
||||
"$BACK") exec "$0" ;;
|
||||
*Network*) exec "$0" network ;;
|
||||
*VPN*) exec "$0" vpn ;;
|
||||
*"Airplane mode"*) exec "$0" airplane ;;
|
||||
*Bluetooth*) exec "$0" bluetooth ;;
|
||||
*Audio*) exec "$0" audio ;;
|
||||
*Display*) exec "$0" display ;;
|
||||
@@ -1349,7 +1363,7 @@ ${themeRows}
|
||||
esac ;;
|
||||
|
||||
*)
|
||||
echo "usage: nomarchy-menu [lookfeel|tools|system|power|power-profile|powermgmt|batterylimit|theme|clipboard|calc|files|emoji|web|network|bluetooth|audio|display|display-profile|keyboard|printers|capture|colorpicker|keybinds|ask|dnd|nightlight|autotimezone|autologin|autocommit|vpn|snapshot|doctor|firmware|fingerprint|controlcenter|rollback|whatchanged]" >&2
|
||||
echo "usage: nomarchy-menu [lookfeel|tools|system|power|power-profile|powermgmt|batterylimit|theme|clipboard|calc|files|emoji|web|network|bluetooth|airplane|audio|display|display-profile|keyboard|printers|capture|colorpicker|keybinds|ask|dnd|nightlight|autotimezone|autologin|autocommit|vpn|snapshot|doctor|firmware|fingerprint|controlcenter|rollback|whatchanged]" >&2
|
||||
exit 64 ;;
|
||||
esac
|
||||
'';
|
||||
|
||||
@@ -174,7 +174,7 @@ let
|
||||
# — BACKLOG #63). Click targets are existing nomarchy-menu entry points.
|
||||
modules-left = [ "custom/nomarchy" "hyprland/workspaces" "hyprland/window" ];
|
||||
modules-center = [ "clock" ];
|
||||
modules-right = [ "custom/recording" "idle_inhibitor" "tray" "custom/vpn" "pulseaudio" "custom/powerprofile" "custom/nightlight" ]
|
||||
modules-right = [ "custom/recording" "idle_inhibitor" "tray" "custom/vpn" "custom/airplane" "pulseaudio" "custom/powerprofile" "custom/nightlight" ]
|
||||
++ lib.optional showLanguage "hyprland/language"
|
||||
++ [ "battery" "custom/doctor" "custom/updates" "custom/notification" "custom/powermenu" ];
|
||||
|
||||
@@ -333,6 +333,17 @@ let
|
||||
on-click = "nomarchy-nightlight toggle";
|
||||
};
|
||||
|
||||
# Airplane mode (#104). Self-gates: plane glyph only while engaged
|
||||
# (status prints nothing otherwise). Click toggles Wi-Fi+BT and
|
||||
# restores prior radio state on disengage. signal 11 = instant refresh.
|
||||
"custom/airplane" = {
|
||||
exec = "nomarchy-airplane status";
|
||||
return-type = "json";
|
||||
interval = 5;
|
||||
signal = 11;
|
||||
on-click = "nomarchy-airplane toggle";
|
||||
};
|
||||
|
||||
# Update awareness. Self-gates: hidden unless nomarchy.updates is enabled
|
||||
# AND the periodic check found something (the helper prints nothing then).
|
||||
# signal 9 lets the checker refresh it instantly; click opens the upgrade
|
||||
@@ -449,7 +460,7 @@ let
|
||||
}
|
||||
#custom-nomarchy:hover { color: @accentAlt; }
|
||||
|
||||
#tray, #pulseaudio, #custom-powerprofile, #custom-nightlight, #custom-updates, #custom-vpn, #custom-recording, #idle_inhibitor, #language, #battery, #custom-doctor, #custom-notification, #custom-powermenu {
|
||||
#tray, #pulseaudio, #custom-powerprofile, #custom-nightlight, #custom-airplane, #custom-updates, #custom-vpn, #custom-recording, #idle_inhibitor, #language, #battery, #custom-doctor, #custom-notification, #custom-powermenu {
|
||||
color: alpha(@text, 0.85);
|
||||
padding: 0 10px;
|
||||
}
|
||||
@@ -473,6 +484,7 @@ let
|
||||
|
||||
/* Night-light active → warm tone, matching the filter it represents. */
|
||||
#custom-nightlight.on { color: @warn; }
|
||||
#custom-airplane.on { color: @warn; }
|
||||
|
||||
/* Updates pending → accent, to draw the eye. */
|
||||
#custom-updates.available { color: @accent; }
|
||||
@@ -485,7 +497,7 @@ let
|
||||
(size+2)pt Pango icon span the icon+text modules use — bump their
|
||||
font-size to match, or these glyphs read smaller than the volume /
|
||||
battery / language icons beside them. */
|
||||
#custom-recording, #custom-updates, #custom-vpn, #custom-nightlight, #custom-doctor, #custom-notification, #custom-powermenu { font-size: ${toString (t.fonts.size + 2)}pt; }
|
||||
#custom-recording, #custom-updates, #custom-vpn, #custom-airplane, #custom-nightlight, #custom-doctor, #custom-notification, #custom-powermenu { font-size: ${toString (t.fonts.size + 2)}pt; }
|
||||
|
||||
/* The speedometer + caffeine glyphs render small in their em box —
|
||||
size them up a touch more so they read at a glance. */
|
||||
|
||||
Reference in New Issue
Block a user