Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 58 additions & 7 deletions bin/omarchy-brightness-keyboard
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,66 @@ if [[ -z $device ]]; then
exit 1
fi

STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy"
STATE_FILE="$STATE_DIR/keyboard-brightness"

save_persistent_state() {
local val="$1"
if (( val > 0 )); then
mkdir -p "$STATE_DIR"
echo "$val" > "$STATE_FILE"
fi
}

get_persistent_state() {
if [[ -s "$STATE_FILE" ]]; then
cat "$STATE_FILE" 2>/dev/null || echo 0
else
echo 0
fi
}

current_brightness="$(brightnessctl -d "$device" get 2>/dev/null || echo 0)"
max_brightness="$(brightnessctl -d "$device" max 2>/dev/null || echo 1)"

if [[ $direction == "off" ]]; then
brightnessctl -sd "$device" set 0 >/dev/null
# Guard: Only save state if current brightness is positive so we never overwrite with 0
if (( current_brightness > 0 )); then
save_persistent_state "$current_brightness"
brightnessctl -sd "$device" set 0 >/dev/null 2>&1
else
brightnessctl -d "$device" set 0 >/dev/null 2>&1
fi
exit 0
elif [[ $direction == "restore" ]]; then
brightnessctl -rd "$device" >/dev/null
saved_val=$(get_persistent_state)
runtime_dir="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
brightnessctl_file="$runtime_dir/brightnessctl/leds/$device"


# Check if brightnessctl temporary file has a positive value
if [[ -f "$brightnessctl_file" ]]; then
b_val=$(cat "$brightnessctl_file" 2>/dev/null || echo 0)
if (( b_val > 0 )); then
brightnessctl -rd "$device" >/dev/null 2>&1
exit 0
fi
fi

# If brightnessctl file was missing or zeroed, restore from persistent state
if (( saved_val > 0 )); then
brightnessctl -d "$device" set "$saved_val" >/dev/null 2>&1
exit 0
fi

# Fallback to sensible default (30% of max) if no previous non-zero state is recorded
fallback_step=$(( max_brightness * 30 / 100 ))
(( fallback_step < 1 )) && fallback_step=1
brightnessctl -d "$device" set "$fallback_step" >/dev/null 2>&1
save_persistent_state "$fallback_step"
exit 0
fi

# Get current and max brightness to determine step size.
max_brightness="$(brightnessctl -d "$device" max)"
current_brightness="$(brightnessctl -d "$device" get)"

# Calculate step as 10% of max brightness. Keyboards with many levels (e.g. 512)
# need larger steps; keyboards with few levels (e.g. 3) fall back to step=1.
step=$(( max_brightness / 10 ))
Expand All @@ -53,6 +101,9 @@ else
(( new_brightness < 0 )) && new_brightness=0
fi

# Set the new brightness.
# Set the new brightness and persist it if non-zero
brightnessctl -d "$device" set "$new_brightness" >/dev/null
if (( new_brightness > 0 )); then
save_persistent_state "$new_brightness"
fi
(( no_osd )) || omarchy-osd -i keyboard -p "$(( new_brightness * 100 / max_brightness ))"
35 changes: 26 additions & 9 deletions default/systemd/system-sleep/keyboard-backlight
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,36 @@

# Turn off keyboard backlight before hibernate to prevent hang on power-off.
# The ASUS keyboard controller can block S4 shutdown if LEDs are active.
# On resume (post), restore the previous backlight brightness.

sleep_action=${SYSTEMD_SLEEP_ACTION:-$2}

device=""
for candidate in /sys/class/leds/*kbd_backlight*; do
if [[ -e "$candidate" ]]; then
device="$(basename "$candidate")"
break
fi
done

[[ -n "$device" ]] || exit 0

STATE_DIR="/var/lib/systemd/backlight"
STATE_FILE="$STATE_DIR/keyboard-sleep-state"

if [[ $1 == "pre" && $sleep_action == "hibernate" ]]; then
device=""
for candidate in /sys/class/leds/*kbd_backlight*; do
if [[ -e "$candidate" ]]; then
device="$(basename "$candidate")"
break
current=$(cat "/sys/class/leds/$device/brightness" 2>/dev/null || echo 0)
if (( current > 0 )); then
mkdir -p "$STATE_DIR"
echo "$current" > "$STATE_FILE"
fi
brightnessctl -d "$device" set 0 >/dev/null 2>&1
elif [[ $1 == "post" && $sleep_action == "hibernate" ]]; then
if [[ -s "$STATE_FILE" ]]; then
target=$(cat "$STATE_FILE" 2>/dev/null || echo 0)
if (( target > 0 )); then
brightnessctl -d "$device" set "$target" >/dev/null 2>&1
fi
done

if [[ -n "$device" ]]; then
brightnessctl -d "$device" set 0 >/dev/null 2>&1
fi
fi

115 changes: 115 additions & 0 deletions test/shell.d/brightness-keyboard-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/bin/bash

set -euo pipefail

source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT

mock_bin="$test_tmp/bin"
call_log="$test_tmp/calls"
runtime_dir="$test_tmp/runtime"
state_dir="$test_tmp/state"
mkdir -p "$mock_bin" "$runtime_dir" "$state_dir"

mock_sys_leds="/sys/class/leds"

# Mock brightnessctl
cat >"$mock_bin/brightnessctl" <<'SH'
#!/bin/bash
printf 'brightnessctl %s\n' "$*" >>"$CALL_LOG"

device=""
args=("$@")
for (( i=0; i<${#args[@]}; i++ )); do
if [[ ${args[i]} == "-d" && $(( i + 1 )) -lt ${#args[@]} ]]; then
device="${args[i+1]}"
fi
done

if [[ $* == *" max"* ]]; then
echo "${MOCK_MAX:-1000}"
elif [[ $* == *" get"* ]]; then
echo "${MOCK_CURRENT:-500}"
elif [[ $* == *" -sd "* ]]; then
# Simulate saving current brightness and setting target
echo "${MOCK_CURRENT:-500}" > "$MOCK_SAVED_FILE"
elif [[ $* == *" -rd "* ]]; then
if [[ -s "$MOCK_SAVED_FILE" ]]; then
saved=$(cat "$MOCK_SAVED_FILE")
echo "$saved"
fi
fi
SH

cat >"$mock_bin/omarchy-osd" <<'SH'
#!/bin/bash
printf 'omarchy-osd %s\n' "$*" >>"$CALL_LOG"
SH

chmod +x "$mock_bin"/*

saved_file="$runtime_dir/brightnessctl/leds/:white:kbd_backlight"
mkdir -p "$(dirname "$saved_file")"
export MOCK_SAVED_FILE="$saved_file"


run_kbd() {
CALL_LOG="$call_log" \
XDG_RUNTIME_DIR="$runtime_dir" \
XDG_STATE_HOME="$state_dir" \
PATH="$mock_bin:$ROOT/bin:$PATH" \
"$ROOT/bin/omarchy-brightness-keyboard" "$@"
}

# Test 1: off with positive brightness calls brightnessctl -sd
: >"$call_log"
MOCK_CURRENT=500 MOCK_MAX=1000 run_kbd off
grep -Fq 'brightnessctl -sd :white:kbd_backlight set 0' "$call_log" ||
fail "off saves state when current brightness is positive"
pass "off saves state when current brightness is positive"

# Test 2: off when brightness is already 0 calls brightnessctl -d set 0 (omits -s to protect saved state)
: >"$call_log"
MOCK_CURRENT=0 MOCK_MAX=1000 run_kbd off
grep -Fq 'brightnessctl -d :white:kbd_backlight set 0' "$call_log" ||
fail "off does not overwrite saved state when current brightness is already 0"
! grep -Fq 'brightnessctl -sd' "$call_log" ||
fail "off must not pass -s when current brightness is 0"
pass "off avoids zero-clobbering when backlight is already 0"

# Test 3: up increments by 10% and persists state
: >"$call_log"
MOCK_CURRENT=500 MOCK_MAX=1000 run_kbd up
grep -Fq 'brightnessctl -d :white:kbd_backlight set 600' "$call_log" ||
fail "up steps brightness by 10%"
[[ $(cat "$state_dir/omarchy/keyboard-brightness" 2>/dev/null) == "600" ]] ||
fail "up persists new brightness level"
pass "up steps brightness by 10% and persists target"

# Test 4: down decrements by 10% and persists state
: >"$call_log"
MOCK_CURRENT=500 MOCK_MAX=1000 run_kbd down
grep -Fq 'brightnessctl -d :white:kbd_backlight set 400' "$call_log" ||
fail "down steps brightness down by 10%"
[[ $(cat "$state_dir/omarchy/keyboard-brightness" 2>/dev/null) == "400" ]] ||
fail "down persists new brightness level"
pass "down steps brightness down by 10% and persists target"

# Test 5: restore with persistent state recovers saved target
: >"$call_log"
echo "750" > "$state_dir/omarchy/keyboard-brightness"
rm -f "$saved_file"
MOCK_CURRENT=0 MOCK_MAX=1000 run_kbd restore
grep -Fq 'brightnessctl -d :white:kbd_backlight set 750' "$call_log" ||
fail "restore uses persistent state when run file is absent"
pass "restore recovers target brightness from persistent state"

# Test 6: restore without any saved state falls back to 30% default
: >"$call_log"
rm -f "$state_dir/omarchy/keyboard-brightness" "$saved_file"
MOCK_CURRENT=0 MOCK_MAX=1000 run_kbd restore
grep -Fq 'brightnessctl -d :white:kbd_backlight set 300' "$call_log" ||
fail "restore falls back to 30% of max when no state exists"
pass "restore falls back to 30% when no state exists"