Skip to content

feat(keyboard): divert backlight keys to bindable slots with display-brightness actions - #1116

Open
zmingxie wants to merge 7 commits into
AprilNEA:masterfrom
zmingxie:feat/backlight-key-slots
Open

zmingxie wants to merge 7 commits into
AprilNEA:masterfrom
zmingxie:feat/backlight-key-slots

Conversation

@zmingxie

@zmingxie zmingxie commented Aug 28, 2026

Copy link
Copy Markdown

Summary

  • Adds KeyBacklightDown/KeyBacklightUp (0x1b04 CIDs 0x00E2/0x00E3) as bindable keyboard key slots, plus BrightnessUp/BrightnessDown actions dispatched as media-key effects — macOS NX brightness events, Linux KEY_BRIGHTNESSUP/DOWN, no Windows key event.
  • Unbound keys are never diverted, so stock firmware backlight behaviour is unchanged unless a user binds them. On the MX Mechanical Mini for Mac these are the only path to display-brightness keys (F1–F3 are Easy-Switch), matching what Options+ ships.

Screenshot

The two new actions in the key action picker (MX Mechanical Mini for Mac). The Key* slots this PR adds are config-only; this panel binds an OS key trigger, and is here to show the actions and their icons.

image

Changes

  • openlogi-core — two Action and two ButtonId variants, appended last; KEYBOARD_KEYS grows to 11; default_binding, to_effect, and two MediaKey variants.
  • openlogi-device — the two CIDs join KEYBOARD_KEY_CIDS, with a test tying it to ButtonId::KEYBOARD_KEYS across the crate boundary.
  • openlogi-inject — macOS NX_KEYTYPE_BRIGHTNESS_UP/DOWN; Linux presses KEY_BRIGHTNESSUP/DOWN and registers both in KEY_CAPABILITIES; Windows logs and skips.
  • openlogi-ipcPROTOCOL_VERSION 31→32. Neither enum rides the bincode wire; the bump follows the v28 precedent and guards GUI/agent config skew.
  • openlogi-desktop — the actions join action_icon_path's exhaustive match.
  • openlogi-ui — 23 locale catalogs, +4 keys each at parity-test positions. The brightness actions name the screen so they cannot be read as the keyboard backlight. Mechanical; safe to skim.
  • Tests — three further unguarded invariants: the Linux keycodes are registered as capabilities, ButtonId names are pinned (they are on-disk map keys), and every 0x1b04 CID is pinned.

Notes for review

Windows: no platform gate here, deliberately. Binding a brightness action on Windows still diverts the CID and suppresses the firmware's backlight adjust while the injector can do nothing. That shape is pre-existing rather than new — openlogi-inject's Windows backend already skips Sleep, RunAppleScript, CustomShortcut, held-shortcut edges, and pixel scroll the same way. Gating only brightness would leave an inconsistency; gating all of them changes five existing actions and belongs in its own PR at the wanted/dispatch boundary rather than in a feature PR. The mitigation already in this change is that wanted diverts only bound keys, so a Windows user loses the firmware function only by deliberately binding that key, and unbinding restores it. Happy to add the narrow gate instead (~10 lines in keyboard_spec_for) if you would rather have it here.

Separately, that filter's Binding::LongPress(_) arm short-circuits without consulting either action, so LongPress(None, None) diverts and does nothing on any platform. Pre-existing; the new test pins the correct case and leaves that one to your decision.

Testing

Rebased onto b9c8fede. Two conflicts, both from upstream moving: master took v31 for Capabilities::dpi_gestures, so this append is renumbered to v32 (const, doc line, and the pinned protocol_version_is_pinned golden). The second was in record_profile/tests.rs — master had independently replaced the hardcoded v29/v30 assertions with derived ones, so I took master's side and that cleanup is no longer part of this PR.

Full local gate on the rebased tip, RUSTFLAGS=-D warnings: cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace (0 failed suites), and the non-GUI rustdoc step. Checklist suites by name: cargo test -p openlogi-ipc --test wire_format (16 passed), cargo test -p openlogi-ui locale, cargo test -p openlogi-desktop i18n.

cargo xtask ci on macOS/aarch64: 9 passed, 0 failed, 3 skipped. Not run here and not claimed green: shell, tests (linux), cargo-deny. The Linux capability test cannot execute on macOS; the Linux clippy --all-targets job compiles it. This PR touches cfg-gated files in openlogi-inject (linux.rs, macos.rs, windows.rs), so the Linux and Windows paths are hand-audited only, not runtime-verified.

Hardware, on an MX Mechanical Mini for Mac over Bluetooth-direct (macOS 26.6): diversion arms (keyboard key capture active keys=3), both directions dispatch including key repeat, display brightness responds, and it survives an agent restart. Mouse gestures still work, covering #1183's gesture.rs rewrite.

Field use: this has been my daily driver since 2026-08-28, roughly three weeks, across agent restarts, sleep/wake cycles, a full OS reboot, an Xcode upgrade, and a reinstall that put a stock release build back over it. One multi-hour outage in that window traced to the capture-recovery retry loop rather than to this change, and has since been fixed upstream by #1321.

Not verified: whether quitting the agent hands these CIDs back to the firmware — the test keyboard's backlight is software-disabled, so that function has no observable effect either way (same restore path as the nine existing slots). Linux and Windows are not runtime-tested, and the picker icons were not re-checked visually.

Fixes #1115

@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

The PR appears safe to merge.

Summary

The PR adds bindable keyboard-backlight controls that can dispatch display-brightness actions while leaving unbound controls under firmware ownership.

  • Adds persisted brightness actions and backlight button identifiers.
  • Maps brightness actions to native macOS and Linux events, with Linux uinput capabilities registered for both keycodes.
  • Updates desktop icons, protocol versioning, localization catalogs, and cross-layer regression tests.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  K[Backlight key CID] --> B{Configured binding?}
  B -->|No| F[Firmware backlight behavior]
  B -->|Yes| A[Brightness action]
  A --> E[Platform media-key effect]
  E --> M[macOS NX event]
  E --> L[Linux uinput brightness key]
  E --> W[Windows logs and skips]
Loading

Reviews (11) · Last reviewed commit: "fix(i18n): name the screen in the bright..."

Comment thread crates/openlogi-core/src/binding/action.rs
Comment thread crates/openlogi-inject/src/inject/linux.rs
@zmingxie
zmingxie force-pushed the feat/backlight-key-slots branch from 12ae295 to 9c78d3d Compare August 31, 2026 21:53
@zmingxie

zmingxie commented Aug 31, 2026

Copy link
Copy Markdown
Author

@AprilNEA I rebased my PR and fixed the merged conflicts. Could you take a look at this when you get a chance? I've been using this locally for the past week, and it's working fine. Thanks!

@davidbudnick davidbudnick added type: feature New feature request area: hidpp HID++ protocol and device feature support platform: all Cross-platform issue labels Sep 3, 2026
@zmingxie
zmingxie force-pushed the feat/backlight-key-slots branch 4 times, most recently from be0cdb2 to de3a496 Compare September 9, 2026 15:20
@zmingxie
zmingxie force-pushed the feat/backlight-key-slots branch 2 times, most recently from 497de8c to fc26307 Compare September 15, 2026 22:30
@zmingxie

Copy link
Copy Markdown
Author

Rebased onto 350e8e6c — clean, no conflicts, and PROTOCOL_VERSION 31 is still free (master's at 30).

I also closed out the Windows question I'd left in the description rather than leaving it on your plate: I'm not gating here. Divert-but-can't-perform already exists on Windows for Sleep, RunAppleScript, CustomShortcut, held shortcuts and pixel scroll, so gating brightness alone would leave an inconsistency, and gating all of them changes five existing actions — that belongs in its own PR, and I'm happy to file an issue for it. Only bound keys are diverted, so a Windows user only loses the firmware backlight function by deliberately binding that key, and unbinding restores it. If you'd rather have the narrow gate, it's ~10 lines in keyboard_spec_for and I'll add it.

Full local gate green on the rebased tip — details in the description. Still running it daily on the MX Mechanical Mini for Mac.

…brightness actions

The MX Mechanical family's backlight up/down keys (0x1b04 CIDs 0x00e2/0x00e3,
reported divertable) were previously invisible to OpenLogi: their firmware task
adjusts the keyboard backlight internally and no event ever reaches the OS, so
neither the OS-hook remapper nor any external tool can rebind them. Logi
Options+ diverts exactly these controls and synthesizes display-brightness
events; users migrating lose that behavior.

Add KeyBacklightDown/KeyBacklightUp slots and BrightnessUp/BrightnessDown
actions (macOS: NX system-defined brightness keys; Linux: KEY_BRIGHTNESSUP/
DOWN via uinput; Windows: debug-logged no-op — display brightness is not an
input event there). Unbound keys are never diverted, so stock backlight
behavior is preserved unless the user opts in. PROTOCOL_VERSION bumps to 30
(appended wire variants, per the v28 HoldShortcut precedent).

Verified live on an MX Mechanical Mini for Mac over Bluetooth-direct on macOS:
diversion arms, presses dispatch, screen brightness responds.
keyboard_spec_for's `wanted` filter is the only thing keeping an unbound
keyboard key native, and nothing tested it. The gate is load-bearing for the
backlight pair in particular: diverting 0x00e2/0x00e3 suppresses the firmware's
internal backlight adjust, so a key reaching `wanted` without the user binding
it would silently cost them backlight control on a key they never touched.

Mirrors the mouse-side precedent in capture_plan's
bound_wheel_tilt_is_diverted_but_an_untouched_one_stays_native, and also covers
Action::None — present in the config as a binding, but not a reason to take a
key away from the firmware.
@zmingxie
zmingxie force-pushed the feat/backlight-key-slots branch from fc26307 to c7f2c3b Compare September 17, 2026 16:13
@greptile-apps

greptile-apps Bot commented Sep 17, 2026

Copy link
Copy Markdown

Want your agent to iterate on Greptile's feedback? Start a greploop in Codex and it will work through the open comments and keep going until this PR reviews clean.

@zmingxie

Copy link
Copy Markdown
Author

Rebased again onto b9c8fede, and correcting my last comment: PROTOCOL_VERSION 31 is no longer free — Capabilities::dpi_gestures took it, so this append is renumbered to v32, including the pinned protocol_version_is_pinned golden.

The other conflict was in record_profile/tests.rs, where you'd already replaced the hardcoded v29/v30 assertions with derived ones. I took master's side, so that cleanup has dropped out of this PR and the diff is a little smaller than before.

Full local gate green on the rebased tip; cargo xtask ci 9 passed, 0 failed, 3 skipped (shell, tests (linux), cargo-deny). Description updated to match.

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

Labels

area: hidpp HID++ protocol and device feature support platform: all Cross-platform issue type: feature New feature request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Divert keyboard Backlight Up/Down keys (CID 0x00E2/0x00E3) and add display-brightness actions

2 participants