10 Commits

Author SHA1 Message Date
rootiest ac1a061b11 feat: add CMD_BATTERY host-side handling for keyboard battery level
Implements the host half of the HID_CMD_BATTERY (0x44) protocol added to
the Q5 Max firmware. The keyboard pushes its battery percentage every
5 minutes when in wireless transport mode; the host can also query
immediately at startup.

Changes across four files:

protocol.rs
- Add CMD_BATTERY = 0x44 command constant
- Add BATT_OFF_LEVEL payload offset and BATT_UNAVAILABLE (0xFF) sentinel
- Add Packet::battery_query() constructor (FLAG_QUERY → keyboard responds)

battery.rs (new)
- Monitor thread receives battery levels via mpsc channel from the bridge
- Writes current level to $XDG_RUNTIME_DIR/qmk-battery (fallback /tmp)
  as a plain ASCII integer; readable with cat for scripts/status bars
- Fires notify-send at two downward threshold crossings:
    ≤ 20%  → normal-urgency "Battery low" notification
    ≤ 10%  → critical-urgency "Battery critical" notification
- Each threshold resets when battery recovers above it (charging detected)
- BATT_UNAVAILABLE (0xFF) responses are silently skipped

bridge.rs
- Handle CMD_BATTERY packets received from keyboards
- Forward the level byte to the battery monitor channel (when enabled)

main.rs
- Add --no-battery-notify flag (discard battery packets, skip file/notify)
- Create battery mpsc channel; spawn battery::monitor thread
- Send Packet::battery_query() to each keyboard on startup for an
  immediate reading rather than waiting for the first 5-minute push
2026-04-15 03:38:10 -04:00
rootiest d4a820f41c chore: implement rust-specific ignore list 2026-04-10 16:54:38 -04:00
rootiest 4e33e67e96 fix(volume): log initial volume level at INFO on startup
Volume changes were only logged at DEBUG (requires -v), leaving the
volume monitor completely silent at the default log level. The first
reading is now logged at INFO so users can confirm the monitor is
running and see the current level without needing verbose output.
2026-04-10 16:47:25 -04:00
rootiest bd4facf1c4 feat: implement active-app monitor via KWin scripting and D-Bus
Replaces the no-op stub with a working implementation for KDE6 Wayland.
Wayland's privacy model prevents querying focused-window state from outside
the compositor, so the monitor injects a KWin JavaScript snippet at runtime
that subscribes to workspace.windowActivated and calls back via D-Bus.

On startup:
- Writes the KWin script to a NamedTempFile
- Registers dev.rootiest.qmk_host on the session bus at /AppMonitor
- Loads the script via org.kde.kwin.Scripting.loadScript(), then calls
  start() to activate it (loadScript alone does not execute the script)

On each window-activation event:
- Truncates app_id to 27 UTF-8 bytes at a char boundary
- Skips no-op updates when the active app has not changed
- Broadcasts CMD_ACTIVE_APP (0x43) to all connected keyboards

Depends on new crates: zbus v4 (blocking D-Bus) and tempfile v3.
2026-04-10 16:47:21 -04:00
rootiest 4b3b0095a0 feat: add --no-volume, --no-brightness, --no-active-app disable flags
Each flag prevents the corresponding monitor thread from being spawned,
saving the polling overhead and any associated system calls for users
who don't need that feature:

  --no-volume      skip pactl polling (CMD_VOLUME)
  --no-brightness  skip /sys/class/backlight polling (CMD_BRIGHTNESS)
  --no-active-app  skip the active-app monitor (CMD_ACTIVE_APP, stub today)

A disabled monitor logs a single info message at startup so the user
knows it was intentionally skipped rather than silently missing.
2026-04-10 15:57:16 -04:00
rootiest 12cc668724 chore: fix unused import and suppress dead_code for reserved protocol items
Remove `warn` from keyboard.rs imports — it was never called there.

Add #![allow(dead_code)] to protocol.rs since that module intentionally
defines the full shared protocol up-front, including constants and
constructors reserved for future features (numpad, active-app, ACK,
FLAG_RESPONSE). Suppressing at the module level is cleaner than
annotating each item individually.
2026-04-10 15:04:47 -04:00
rootiest 635a223fd5 feat: add -v/-vv verbosity flags and trace-level hex dumps
Add clap CLI argument parsing with three log-level controls:
  -q/--quiet  errors only
  (default)   info — connections and value changes
  -v          debug — every packet's cmd/src/flags logged on TX and RX
  -vv         trace — full 32-byte hex dump of every packet on the wire

RUST_LOG env var continues to work as a fine-grained override for any
of the above levels.

Also expand the TX log line to include src and flags fields to match
the RX line, making the two symmetric.
2026-04-10 14:56:12 -04:00
rootiest e83bc02563 fix(volume): average all channels instead of returning the first
Previously returned the first channel percentage found, which is
incorrect when channels are unbalanced. Average across all channels
matches what OS volume sliders display and avoids a misleading reading
in edge cases such as hard-panned mono (e.g. left=100%, right=0%
would have incorrectly reported 100% rather than 50%).

Add a test covering the unbalanced case.
2026-04-10 14:52:58 -04:00
rootiest aadb0a4a57 fix(brightness): use actual_brightness and prefer internal panel device
Switch from reading `brightness` (software-requested) to `actual_brightness`
(hardware-reported), which is the correct file on AMD GPU backlights where
the two values can diverge.

Replace the first-found device selection with a scored preference so that
when multiple backlight devices are present (e.g. amdgpu_bl2 and nvidia_0)
the internal laptop panel is chosen rather than a dGPU output that may
report a fixed ceiling value. Priority: amdgpu/intel (3) > acpi/platform
(2) > unknown (1) > nvidia (0).
2026-04-10 14:48:47 -04:00
rootiest d00c4aa371 feat: initial qmk-host Raw HID bridge implementation
Add a Rust daemon that connects to QMK keyboards over their Raw HID
interface (VID 0x3434, usage page 0xFF60 / usage 0x0061) and provides:

- Bi-directional layer sync: CMD_LAYER_SYNC (0x40) packets are bridged
  between all connected keyboards so their active layers stay in sync.
  On connect, a FLAG_QUERY packet requests the current state from each
  keyboard. A second keyboard (numpad) is stubbed and ready to enable.

- Volume monitoring: polls `pactl get-sink-volume @DEFAULT_SINK@` every
  2 s and pushes CMD_VOLUME (0x41) packets on change. Compatible with
  both PulseAudio and PipeWire on KDE6/Wayland.

- Brightness monitoring: polls /sys/class/backlight on the same interval
  and pushes CMD_BRIGHTNESS (0x42) packets on change.

- Active-app stub: CMD_ACTIVE_APP (0x43) is reserved with detailed notes
  on KDE6/Wayland implementation approaches (KWin D-Bus, foreign
  toplevel protocol) for a future commit.

Each keyboard runs a single combined read/write IO thread using a 10 ms
read timeout to interleave HID reads and queued writes without needing
two file handles. Protocol constants in src/protocol.rs mirror
hid_protocol.h in the QMK firmware exactly.

Licensed under GPL-3.0-or-later.
2026-04-10 14:42:09 -04:00