diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-21 17:48:47 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-21 17:48:47 -0400 |
| commit | 09f4d205fe463faf676f95e798d08e8bf498be96 (patch) | |
| tree | ac60b2aa4d8350d5ab3c0e6f76361daf70d1d702 /dotfiles | |
| parent | eee30be993c6ff79a5e7fa5f37d6ba368dc0c3d9 (diff) | |
| download | archsetup-09f4d205fe463faf676f95e798d08e8bf498be96.tar.gz archsetup-09f4d205fe463faf676f95e798d08e8bf498be96.zip | |
feat(hyprland): add airplane-mode waybar toggle
I added a laptop-only waybar button that drops the machine into a low-power state and restores it on a second click. Engaging turns wifi off, sets the CPU energy-performance preference to power, dims the backlight to 35%, and stops network-only services (tailscale, proton-vpn, avahi, cups, wsdd, geoclue, sshd, fail2ban, syncthing). Bluetooth is left alone so earbuds keep working.
Disengaging replays the state recorded when airplane mode was engaged rather than writing hardcoded defaults. A lever already in its low-power position is left untouched: wifi that was already off stays off, and a service that was already stopped isn't restarted.
The indicator hides itself on machines with no battery, so desktops never show the button. State lives in $XDG_RUNTIME_DIR/airplane-state, and the bar refreshes the moment the toggle fires via a realtime signal.
Diffstat (limited to 'dotfiles')
| -rw-r--r-- | dotfiles/hyprland/.config/waybar/config | 8 | ||||
| -rw-r--r-- | dotfiles/hyprland/.config/waybar/style.css | 6 | ||||
| -rwxr-xr-x | dotfiles/hyprland/.local/bin/airplane-mode | 110 | ||||
| -rwxr-xr-x | dotfiles/hyprland/.local/bin/waybar-airplane | 33 |
4 files changed, 157 insertions, 0 deletions
diff --git a/dotfiles/hyprland/.config/waybar/config b/dotfiles/hyprland/.config/waybar/config index 2ae43fe..97fb3b7 100644 --- a/dotfiles/hyprland/.config/waybar/config +++ b/dotfiles/hyprland/.config/waybar/config @@ -18,6 +18,7 @@ "custom/netspeed", "pulseaudio", "custom/touchpad", + "custom/airplane", "idle_inhibitor", "custom/pocketbook", "tray", @@ -149,6 +150,13 @@ "on-click": "toggle-touchpad" }, + "custom/airplane": { + "exec": "waybar-airplane", + "return-type": "json", + "signal": 10, + "on-click": "airplane-mode" + }, + "idle_inhibitor": { "format": "<span size='large'>{icon}</span>", "format-icons": { diff --git a/dotfiles/hyprland/.config/waybar/style.css b/dotfiles/hyprland/.config/waybar/style.css index 3a849c5..cd158d0 100644 --- a/dotfiles/hyprland/.config/waybar/style.css +++ b/dotfiles/hyprland/.config/waybar/style.css @@ -77,6 +77,7 @@ window#waybar { #custom-worldclock, #custom-layout, #custom-touchpad, +#custom-airplane, #window { padding: 0.45rem; margin: 0.3rem; @@ -98,6 +99,7 @@ window#waybar { #pulseaudio:hover, #sysmonitor:hover, #custom-touchpad:hover, +#custom-airplane:hover, #custom-layout:hover { background-color: #474544; border-radius: 1rem; @@ -112,6 +114,10 @@ window#waybar { color: #d47c59; } +#custom-airplane.active { + color: #d7af5f; +} + #temperature.warning { color: #d7af5f; } diff --git a/dotfiles/hyprland/.local/bin/airplane-mode b/dotfiles/hyprland/.local/bin/airplane-mode new file mode 100755 index 0000000..4f5ed9c --- /dev/null +++ b/dotfiles/hyprland/.local/bin/airplane-mode @@ -0,0 +1,110 @@ +#!/bin/sh +# airplane-mode — toggle a low-power "airplane" state for a laptop. +# +# Engage: record the current state of each lever, then apply low-power values: +# - wifi off (nmcli; bluetooth is left alone, on purpose — earbuds) +# - CPU energy-performance preference -> power (intel_pstate, via sysfs) +# - display brightness dimmed +# - stop network-only services (VPNs, sync, discovery, inbound SSH) +# Disengage: read the recorded state and restore exactly what was there. A +# lever already in its low-power position before engaging (e.g. wifi already +# off, a service already stopped) is left untouched on disengage. +# +# State lives at $XDG_RUNTIME_DIR/airplane-state as key=value lines. The +# waybar-airplane indicator reads `mode` from it. +# +# Env knobs (defaults are the real system; tests override them): +# AIRPLANE_EPP_GLOB glob of EPP sysfs files +# AIRPLANE_BRIGHTNESS_LOW brightnessctl target while engaged +# AIRPLANE_SYSTEM_SERVICES system units to stop (sudo) +# AIRPLANE_USER_SERVICES --user units to stop + +STATE_FILE="${XDG_RUNTIME_DIR:-/tmp}/airplane-state" +SUDO="${AIRPLANE_SUDO:-sudo}" +EPP_GLOB="${AIRPLANE_EPP_GLOB:-/sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference}" +BRIGHTNESS_LOW="${AIRPLANE_BRIGHTNESS_LOW:-35%}" +SYSTEM_SERVICES="${AIRPLANE_SYSTEM_SERVICES:-tailscaled.service proton.VPN.service avahi-daemon.service cups.service wsdd.service geoclue.service sshd.service fail2ban.service}" +USER_SERVICES="${AIRPLANE_USER_SERVICES:-syncthing.service}" + +read_key() { sed -n "s/^$1=//p" "$STATE_FILE" 2>/dev/null | head -n1; } + +set_epp() { + # Write $1 to every EPP file. Needs root; glob expands inside the subshell. + $SUDO sh -c "for f in $EPP_GLOB; do [ -e \"\$f\" ] && echo $1 > \"\$f\"; done" 2>/dev/null +} + +first_epp() { + for f in $EPP_GLOB; do + [ -e "$f" ] && { cat "$f"; return; } + done +} + +engage() { + # --- record current state --- + wifi_was=$(nmcli radio wifi 2>/dev/null) + epp_was=$(first_epp) + bright_was=$(brightnessctl get 2>/dev/null) + + stopped_system="" + for s in $SYSTEM_SERVICES; do + if systemctl is-active --quiet "$s" 2>/dev/null; then + $SUDO systemctl stop "$s" 2>/dev/null && stopped_system="$stopped_system $s" + fi + done + stopped_user="" + for s in $USER_SERVICES; do + if systemctl --user is-active --quiet "$s" 2>/dev/null; then + systemctl --user stop "$s" 2>/dev/null && stopped_user="$stopped_user $s" + fi + done + + # --- apply low-power settings --- + nmcli radio wifi off 2>/dev/null + set_epp power + brightnessctl set "$BRIGHTNESS_LOW" >/dev/null 2>&1 + + # --- persist what we recorded --- + { + echo "mode=on" + echo "wifi=$wifi_was" + echo "epp=$epp_was" + echo "brightness=$bright_was" + echo "stopped_system=$stopped_system" + echo "stopped_user=$stopped_user" + } > "$STATE_FILE" + + notify info "Airplane mode" "ON — wifi off, low power" 2>/dev/null +} + +disengage() { + wifi_was=$(read_key wifi) + epp_was=$(read_key epp) + bright_was=$(read_key brightness) + stopped_system=$(read_key stopped_system) + stopped_user=$(read_key stopped_user) + + # Only restore a lever that was NOT already in its low-power state. + [ "$wifi_was" = "enabled" ] && nmcli radio wifi on 2>/dev/null + [ -n "$epp_was" ] && set_epp "$epp_was" + [ -n "$bright_was" ] && brightnessctl set "$bright_was" >/dev/null 2>&1 + + for s in $stopped_system; do + $SUDO systemctl start "$s" 2>/dev/null + done + for s in $stopped_user; do + systemctl --user start "$s" 2>/dev/null + done + + echo "mode=off" > "$STATE_FILE" + notify info "Airplane mode" "OFF — settings restored" 2>/dev/null +} + +case "$(read_key mode)" in + on) disengage ;; + *) engage ;; +esac + +# Refresh the waybar indicator immediately (custom/airplane listens on signal 10). +pkill -RTMIN+10 waybar 2>/dev/null + +exit 0 diff --git a/dotfiles/hyprland/.local/bin/waybar-airplane b/dotfiles/hyprland/.local/bin/waybar-airplane new file mode 100755 index 0000000..21f869c --- /dev/null +++ b/dotfiles/hyprland/.local/bin/waybar-airplane @@ -0,0 +1,33 @@ +#!/bin/sh +# Airplane-mode indicator for waybar. +# Reads the state file the airplane-mode toggle maintains; emits one JSON line +# (text + tooltip + class) for the custom/airplane module. The file holds +# key=value lines; only `mode` (on/off) matters here. Anything other than +# "on" reads as off, so a missing or garbled state file fails safe (airplane +# mode shown as inactive — i.e. radios assumed on). +# +# Laptop-only: airplane mode is meaningless on a desktop, so the module hides +# itself (emits nothing → waybar drops it) on machines with no battery. + +STATE_FILE="${XDG_RUNTIME_DIR:-/tmp}/airplane-state" +PS_DIR="${AIRPLANE_POWER_SUPPLY_DIR:-/sys/class/power_supply}" + +# Laptop check: a battery present means this is a portable machine. +is_laptop() { + for b in "$PS_DIR"/BAT*; do + [ -e "$b" ] && return 0 + done + return 1 +} + +is_laptop || exit 0 + +mode=$(sed -n 's/^mode=//p' "$STATE_FILE" 2>/dev/null | head -n1) + +# Same clear plane glyph in both states; the class drives the color (gold when +# engaged, default gray when not) so there's no slash to obscure the wings. +if [ "$mode" = "on" ]; then + echo "{\"text\": \"<span size='large'></span>\", \"tooltip\": \"Airplane mode ON — wifi off, low power\", \"class\": \"active\"}" +else + echo "{\"text\": \"<span size='large'></span>\", \"tooltip\": \"Airplane mode OFF\", \"class\": \"inactive\"}" +fi |
