Journal

Making the keyboard lock key really sleep the phone

A keyboard-specific handler preserved the lock gesture and the display reached OFF roughly 0.7 seconds later.

Vlad / experimentos.
The boundary

The fix matches one HID identity. A reboot was not performed, so boot persistence remained a follow-up check.

The question

The keyboard lock key left the phone displaying lock-screen pixels. The device experiment traced the keyboard-specific handler and power transition, checked the display state directly, and left reboot persistence as a separate follow-up.

From the original notebook

Date: 2026-09-22

Outcome

The Motorola moto g54 5G is running PixelOS 16 and is rooted. The dedicated lock key on the connected HP 970/975 keyboard now locks the phone and turns the display fully off immediately. The clock and other lock-screen pixels are no longer visible after the key is used.

Device and software

  • Device: moto g54 5G (cancunf)
  • ROM: PixelOS 16, build PixelOS_cancunf-16.2-20260609-0616
  • Android release: 16
  • Security patch reported by the device: 2026-06-01
  • Root: Magisk/root shell available
  • Keyboard: HP 970/975 Series Keyboard over Bluetooth
  • Keyboard USB/HID identity: vendor 0x03f0, product 0x6343
  • Main keyboard input node while connected: /dev/input/event16 (the event number can change after reconnects)

Original behavior

Pressing the keyboard’s dedicated lock key generated a Meta+L chord. PixelOS handled that as KEY_GESTURE_TYPE_LOCK_SCREEN, which locked the keyguard but left the display on for the lock-screen timeout. The observed delay was about 10 seconds. During that interval Android reported the phone as locked while the display remained ON.

The same behavior was visible in the input and power state:

  • raw input: KEY_LEFTMETA DOWN, KEY_L DOWN, KEY_L UP, KEY_LEFTMETA UP
  • Android gesture: keycode [40], modifier state 65536, KEY_GESTURE_TYPE_LOCK_SCREEN
  • keyguard: showing=true
  • power: mWakefulness=Awake, mScreenState=ON

The phone’s physical side power button behaved differently: it immediately requested power_button sleep.

Display settings investigated

The clock was not caused by Android’s always-on display setting alone. The following settings were inspected:

  • doze_always_on: disabled
  • ambient_display_always_on: disabled
  • doze_for_notifications: disabled
  • screensaver_enabled: disabled
  • doze_pulse_on_pick_up: disabled
  • doze_wake_display_gesture: disabled
  • doze_wake_screen_gesture: disabled

The screen timeout was also found set to 2147483647 ms (effectively never), and the phone was sometimes charging with stay_on_while_plugged_in=7. Those settings explain why waiting for a normal timeout was not a useful solution, but they do not explain the keyboard lock delay: Meta+L was invoking the lock-screen gesture rather than sleep.

The device contains g54_native_aod and missedcallzero-aod Magisk modules, but both had a disable marker when inspected. Their service scripts are relevant if they are re-enabled later because they can restore Doze/AOD settings at boot.

Implemented fix

A small root daemon was installed for this exact keyboard:

  • Binary: /data/adb/g54-keyboard-sleep/keyboard-sleep
  • Source: /data/adb/g54-keyboard-sleep/keyboard-sleep.c
  • Boot launcher: /data/adb/service.d/98g54-keyboard-sleep.sh
  • Runtime log: /data/adb/g54-keyboard-sleep/service.log
  • Single-instance lock/PID file: /data/adb/g54-keyboard-sleep/daemon.lock

The daemon:

  1. Watches only the HP 970/975 main keyboard with vendor 03f0 and product 6343.
  2. Detects the exact Meta+L sequence after both keys are released.
  3. Leaves PixelOS’s normal lock-screen action intact.
  4. Immediately invokes KEYCODE_SLEEP so the display transitions to off.
  5. Reconnects when the Bluetooth keyboard disconnects and reconnects.
  6. Ignores ordinary typing, modified chords, held-key repeats, and incomplete/cancelled chords.
  7. Uses a file lock so only one daemon instance can run.

The first prototype invoked the Android input command with inherited descriptors and Android rejected it with transaction status 512. The final version redirects the child command’s standard descriptors to /dev/null; the sleep command then returned status 0.

No framework files, generic key layouts, or global keyboard mappings were modified.

Verification

The daemon’s built-in tests passed for ordinary typing, modifier handling, chord release ordering, repeat suppression, modified chords, and cancellation.

The exact raw Meta+L sequence was replayed through the keyboard input device after installation. The observed state was:

  • immediately after the lock gesture: mWakefulness=Dozing, mScreenState=ON
  • roughly 0.7 seconds later: mWakefulness=Asleep, mScreenState=OFF
  • subsequent samples remained Asleep and OFF
  • a captured screenshot had pixel extrema (0, 0) for every RGB channel and was completely black
  • the runtime log recorded successful sleep commands with status 0 and durations of approximately 0.03–0.06 seconds

The user then confirmed that the keyboard lock button behaved as intended.

Persistence and rollback

The launcher is in Magisk’s /data/adb/service.d, so the daemon is configured to start after boot. A reboot was not performed during this session; boot persistence is based on the installed service launcher and should be checked after the next planned reboot.

To disable the behavior without removing files, create:

/data/adb/g54-keyboard-sleep/disabled

Then stop the daemon after verifying the PID in daemon.lock belongs to this binary. Removing the launcher and daemon directory restores PixelOS’s original Meta+L behavior, subject to the usual Magisk file-removal procedure.

Important boundary

The handler is intentionally specific to the HP 970/975 keyboard. Another keyboard, a different HID identity, or a future firmware change will not be affected automatically. The keyboard’s event node number must not be hard-coded because it changes across reconnects; the daemon identifies the device by vendor, product, and name instead.

Keep exploring

Original experiment record. Workstation paths have been generalized. Detailed measurements below retain their original workload and validation boundaries.

Follow the evidence

From notebook to finding.

This story is based on the archived experiment at revision 6550ead3945b. Original timestamps, workloads and qualification limits belong to that record.

Original GitHub record
Supporting notebooks (1)

GitHub source links require access to the private archive. The readable notes and aggregate chart exports are included here.

Back to the journal Follow via RSS