# fw: add Button Lock (hold Back+Down to lock all input)
## Summary
Adds a…n opt-in **Button Lock** feature: holding **Back + Down** for a
configurable duration locks all button input (and the touchscreen on
touch-capable boards); holding the same combo again unlocks. This prevents
accidental actions — scrolling notifications away, launching apps, dismissing
alarms — while the watch is worn during sports, sleep, or when it brushes
against a sleeve.
The feature is off by default and enabled via **Settings → System → Button
Lock**, where the hold duration is chosen from Off / 1 / 2 / 3 / 5 / 10
seconds.
## User experience
- Hold Back+Down for the configured time → short vibe + "Buttons Locked"
popup. All input is now ignored.
- Press any button while locked → brief hint popup (backlight still works):
```
┌───────────────────────┐
│ │
│ Hold Back + Down │
│ to unlock │
│ │
│ (dismisses │
│ after ~2 seconds) │
└───────────────────────┘
```
- Hold Back+Down again → double vibe + "Buttons Unlocked" popup, input works
again.
```
┌───────────────────────┐ ┌───────────────────────┐
│ │ │ │
│ Buttons Locked │ │ Buttons Unlocked │
│ │ │ │
└───────────────────────┘ └───────────────────────┘
on lock (short vibe) on unlock (double vibe)
```
The popups are plain text `SimpleDialog`s on the `ModalPriorityAlert` modal
stack with a 1.8 s timeout (matching ActionToggle's result timeout), pushed
from KernelMain. Alert outranks notifications and the incoming-call UI, so
the feedback stays visible when one of those is on screen, while remaining
below BT pairing requests and alarms, which must never be hidden by a lock
toast.
## Lock process
**Locking:**
```mermaid
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '18px', 'lineColor': '#8a8a8a', 'textColor': '#333333', 'edgeLabelBackground': '#e8e8e8', 'noteBkgColor': '#fff3c4', 'noteTextColor': '#5c4a00', 'noteBorderColor': '#d4b106'}}}%%
stateDiagram-v2
direction LR
classDef unlocked fill:#a5d6a7,stroke:#2e7d32,color:#1b5e20
classDef pending fill:#ffe082,stroke:#f57f17,color:#7a4f01
classDef locked fill:#ef9a9a,stroke:#c62828,color:#7f1d1d
state "Combo pending" as Pending
[*] --> Unlocked
Unlocked --> Pending : Back+Down both held (feature enabled)
Pending --> Unlocked : released early / third button pressed
Pending --> Locked : hold timer fires (short vibe + "Buttons Locked")
Locked --> Locked : any button press → swallowed, hint popup
note right of Pending
force-quit timer cancelled,
watchface click recognizers reset,
hold timer armed (1-10 s),
further DOWNs swallowed
end note
note right of Locked
button events masked from every task,
touch sensor disabled,
backlight and HW reset combo still work
end note
class Unlocked unlocked
class Pending pending
class Locked locked
```
**Unlocking:**
```mermaid
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '18px', 'lineColor': '#8a8a8a', 'textColor': '#333333', 'edgeLabelBackground': '#e8e8e8', 'noteBkgColor': '#fff3c4', 'noteTextColor': '#5c4a00', 'noteBorderColor': '#d4b106'}}}%%
stateDiagram-v2
direction LR
classDef unlocked fill:#a5d6a7,stroke:#2e7d32,color:#1b5e20
classDef pending fill:#ffe082,stroke:#f57f17,color:#7a4f01
classDef locked fill:#ef9a9a,stroke:#c62828,color:#7f1d1d
state "Combo pending" as PendingU
Locked --> PendingU : Back+Down both held
PendingU --> Locked : released early / third button pressed
PendingU --> Unlocked : hold timer fires (double vibe + "Buttons Unlocked")
note right of PendingU
same 1-10 s hold timer;
works with a notification
or call on screen
end note
note right of Unlocked
touch restored to the
persisted user pref
end note
class Unlocked unlocked
class PendingU pending
class Locked locked
```
A continuous hold toggles exactly once; the combo must be fully released
before it can trigger again.
## Implementation
- **`src/fw/shell/normal/button_lock.c` (new)** — self-contained state
machine, patterned after the PRF `getting_started_button_combo` monitor:
a held-buttons bitset plus a `new_timer` for the hold, toggling on
KernelMain via `launcher_task_add_callback()`.
- **Kernel hook** — every button event passes through
`launcher_handle_button_event()`; the module is consulted first and, when
it swallows an event, the full `task_mask` is set so no task (app, modal,
watchface, event-service subscribers) sees it.
- **Recognizer consistency** — a button UP is delivered iff its DOWN was
delivered, so click recognizers never see an unbalanced press. When the
combo forms on the watchface, the click manager is reset (same as the
existing Quick Launch combos) so the first button's 400 ms long-click can't
fire mid-hold. The back-button force-quit timer is cancelled when the combo
becomes pending, so locking inside an app doesn't force-quit it.
- **Touch** (touch-capable boards, e.g. Pebble Time 2) — locking calls the
existing `touch_service_set_globally_enabled(false)` kill switch; unlocking
restores the *persisted* touch preference rather than blindly enabling. A
phone-side write of the touch pref while locked only updates the pref, so
it cannot re-enable the sensor mid-lock.
- **Pref** — single `buttonLockHoldMs` shell pref (`0` = disabled, default);
the handler rejects values not offered by the UI. Not added to the phone
sync whitelist for now.
- **Safety** — locked state is RAM-only (reboot always unlocks), PRF/SDK
shells get inert stubs, and the hardware reset combo is handled at ISR
level in the button driver, below anything the lock can mask.
## Testing
- New clar suite `tests/fw/shell/normal/test_button_lock.c` (11 cases):
combo detection, configurable hold duration, abort on early release or a
third button, swallowing + hint popup while locked, touch restoration to
the persisted pref, exactly-one-toggle per continuous hold, and the
timer-fires-after-release race.
- Full `./pbl test` suite passes.
- Verified end-to-end in QEMU (`qemu_gabbro`): settings round-trip, lock,
hint popup on keypress while locked, unlock, normal input afterwards.
- Firmware builds for `obelix@pvt` (normal) and `qemu_gabbro`; the PRF link
failures for `obelix@pvt` are pre-existing on `main` and unchanged by this
series.
## Commits
1. 0efaea7a `fw/shell: add button lock hold duration preference`
2. 291a8f5a `fw/shell/normal: add button lock service`
3. a03d1327 `fw/apps/settings: add Button Lock option to System settings`
4. 6531af45 `tests/fw/shell/normal: add button lock unit test`
5. 4e69c5d7 `fw/shell/normal: raise button lock popups above notifications`
🤖 Generated with [Claude Code](https://claude.com/claude-code)