Skip to content

Turn the keyboard backlight off on idle and make its thresholds configurable - #340

Open
DataKnox wants to merge 4 commits into
omacom:quattrofrom
DataKnox:keyboard-backlight-service
Open

Turn the keyboard backlight off on idle and make its thresholds configurable#340
DataKnox wants to merge 4 commits into
omacom:quattrofrom
DataKnox:keyboard-backlight-service

Conversation

@DataKnox

@DataKnox DataKnox commented Sep 4, 2026

Copy link
Copy Markdown

Summary

Follow-up to #303, as proposed in the review thread here: this extends the merged ambient light loop rather than replacing it. Three additions, all inside the existing command, which stays the one writer of the LED.

  • Idle-off and wake. --idle and --active record the idle state under $XDG_RUNTIME_DIR and prod the loop with USR1, so the keys go dark the moment the compositor reports IDLE_SECONDS without a key press or trackpad touch, and come back at the first input. A new first-party service, omarchy.keyboard-backlight (shell/plugins/services/keyboard-backlight/), relays the shell's IdleMonitor into those two calls. No new systemd unit. A level set by hand while idle survives the return from idle, and a fast idle → active flip while a call is still running is re-sent rather than dropped.
  • Config file. ~/.config/omarchy/keyboard-backlight.conf sets DARK_LUX, BRIGHT_LUX, IDLE_SECONDS, and POLL_SECONDS. Only whole-number assignments to those keys are read, so the file is settings rather than code. The shell asks the command for --idle-seconds instead of parsing the file a second time.
  • Hysteresis on the off edge. Once bright light has turned the keys off, they stay off until the room is dark enough for a clearly visible level again (10% of the ramp) instead of flickering at 1–2%. The band is a slice of the ramp, so it follows whatever thresholds are configured. Drive keyboard backlight from the ambient light sensor #303's pause-until-lux-moves override handling is kept as is.

Also from the review: the *kbd_backlight* glob and find_als name filter are #303's, the manual override now uses #303's pause behaviour, the QML no longer parses the config itself, and run() can no longer overwrite a running process's command. author stays Omarchy to match the other first-party service manifests.

Testing

  • test/shell.d/brightness-keyboard-auto-test.sh keeps every existing assertion (now under a private $HOME, so a developer's own conf cannot leak into the defaults) and adds config parsing, the fallback for an inverted threshold pair, and a run of the real loop against a fake sensor and LED with a long poll, so every change is the signal path doing the work: idle off, wake, bright-room off, hysteresis just under the threshold, relight, and a hand-set level surviving idle.
  • ./test/shell: all pass except the two pre-existing omarchy-pkgs checkout tests. ./test/cli passes, omarchy commands --check is clean at 458.
  • M2 MacBook Pro 13" (apple,j493, Hyprland 0.56.2, omarchy 4.0.2-2): loop run from the checkout against the real sensor and LED, service loaded as a plugin. Keys lit at 22 lux, went dark 10 s after the last input, came back on the first touch, repeatedly; --idle / --active from a terminal switch the LED within a second.

Note: on 4.0.2-2 the unit itself never reaches /usr/lib/systemd/user/ on aarch64 (upstream's omarchy-settings PKGBUILD installs user units from a fixed list), so the migration's wants symlink dangles and the loop does not run on updated Macs. That is a packaging fix in a separate PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_015BSgNp56iTuhMy7XLHsRC2

@scottjones

Copy link
Copy Markdown
Collaborator

@DataKnox — heads up before you put more time into this: #303 is the same feature and is close to landing. It adds the same two paths (bin/omarchy-brightness-keyboard-auto, test/shell.d/brightness-keyboard-auto-test.sh) and edits the same manual page, so whichever merges first makes the other a hard conflict on every file. Flagging now rather than after you've done more work — this is a collision of timing, not of quality.

The good news is the overlap is smaller than it looks, and the parts of yours that don't overlap are the parts I most want. #303 has no idle handling at all. Yours has the three things it's missing:

One more, which I only found by checking the hardware — your find_als() is the better sensor selector. On an M2 Max there are two AOP IIO devices adjacent to each other:

iio:device0  name=aop-sensors-las   in_angl_raw          <- lid angle
iio:device1  name=aop-sensors-als   in_illuminance_input <- ambient light

Your name == *als* filter skips the lid-angle sensor deliberately. #303 ignores the name and lands on the right device only because device0 happens not to expose illuminance. I'm asking #303 to adopt your check.

So what I'd like to propose: let #303 land, and rebase this as a follow-up that extends its script rather than replacing it. Your diff drops from ~336 lines to maybe 80, the review gets much easier, and the ideas above survive — which they won't if this stays a competing implementation and loses the race.

Two things worth fixing wherever this code ends up:

LED is hardcoded to /sys/class/leds/kbd_backlight. Everything else in the repo globs *kbd_backlight*, because non-Apple laptops use asus::kbd_backlight, dell::kbd_backlight, smc::kbd_backlight. Correct on Apple Silicon — I confirmed the node is exactly kbd_backlight here — but the manual section you add doesn't scope itself to Apple, so on other hardware it's silently inert.

Manual override has no visible effect in a lit room. adopt_manual_change records the new level as preferred, then apply() immediately writes 0 back because dark is still 0. So raising the keys by hand in a bright room does nothing the user can see. #303's pause-until-lux-moves handles this case, and it's probably the piece worth taking from it rather than the other way round.

Smaller: manifest.json says "author": "Omarchy" on a contributed plugin; configProc shells out to bash -c source ... to read one integer, which duplicates config parsing across QML and bash; and run() overwrites idleProc.command while it may still be running, so a fast idle→active flip can drop an event.

Nice work on the hardware testing — the idle behavior is the thing I want in Omarchy and I'd rather it arrive as your follow-up than get lost to a merge race.

…gurable

The ambient light loop from omacom#303 lights the keys in the dark and dims them
as the room brightens, but leaves them lit on an untouched laptop, and its
thresholds are constants in the script. Three additions, all inside the
existing command, which stays the one writer of the LED:

- --idle and --active record the idle state under $XDG_RUNTIME_DIR and prod
  the loop with USR1, so the keys go dark the moment the compositor reports
  IDLE_SECONDS without input and come back at the first key press or
  trackpad touch. A new first-party shell service, omarchy.keyboard-backlight,
  relays the compositor's idle notifier into those two calls; a fast
  idle -> active flip while a call is still running is re-sent, not dropped.
  A level set by hand while idle survives the return from idle.
- ~/.config/omarchy/keyboard-backlight.conf sets DARK_LUX, BRIGHT_LUX,
  IDLE_SECONDS, and POLL_SECONDS. Only whole-number assignments to those keys
  are read, so the file is settings rather than code, and the shell asks the
  command for IDLE_SECONDS rather than parsing the file a second time.
- Hysteresis on the off edge: once bright light has turned the keys off, they
  stay off until the room is dark enough for a clearly visible level again
  instead of flickering at 1-2%. The band is a slice of the ramp, so it
  follows the configured thresholds.

The test drives the real loop against a fake sensor and LED, with long polls
so every change is the signal path doing the work.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015BSgNp56iTuhMy7XLHsRC2
@DataKnox
DataKnox force-pushed the keyboard-backlight-service branch from 81cbf1f to 7cdcbc8 Compare September 9, 2026 01:21
@DataKnox DataKnox changed the title Add macOS-style keyboard backlight service for Apple Silicon Turn the keyboard backlight off on idle and make its thresholds configurable Sep 9, 2026
@malik-na

malik-na commented Sep 9, 2026

Copy link
Copy Markdown
Member

The feature itself is well tested. ARM CI is failing on shared Hyprtoolkit recipe drift (0.5.4-6 installed, 0.5.4-5.1 expected); align the recipe pin, declare the settings-unit packaging dependency, and rerun.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants