Skip to content

fw: button lock with key-combo Back+Down - #1838

Open
unki wants to merge 10 commits into
coredevices:mainfrom
unki:feature/button-lock-with-key-combo
Open

fw: button lock with key-combo Back+Down#1838
unki wants to merge 10 commits into
coredevices:mainfrom
unki:feature/button-lock-with-key-combo

Conversation

@unki

@unki unki commented Aug 4, 2026

Copy link
Copy Markdown

fw: add Button Lock (hold Back+Down to lock all input)

Summary

Adds an 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 SimpleDialogs 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:

%%{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
Loading

Unlocking:

%%{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
Loading

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. 0efaea7 fw/shell: add button lock hold duration preference
  2. 291a8f5 fw/shell/normal: add button lock service
  3. a03d132 fw/apps/settings: add Button Lock option to System settings
  4. 6531af4 tests/fw/shell/normal: add button lock unit test
  5. 4e69c5d fw/shell/normal: raise button lock popups above notifications

🤖 Generated with Claude Code

@unki
unki requested review from gmarull and jplexer as code owners August 4, 2026 19:14
@unki

unki commented Aug 4, 2026

Copy link
Copy Markdown
Author

I've gone with Back + Down because I'm already used to similar button combinations on other watches. I couldn't find that combination being used or reserved for anything else, though. It should be easy to change later if needed. Or even make it configurable.

bemyak and others added 6 commits August 10, 2026 12:14
An interrupted update leaves the chip in boot mode, where it stops
answering CHIP_ID_REG read. Force-updating in case this happens.

Signed-off-by: Sergei Gureev <git@bemyak.net>
Add a buttonLockHoldMs preference storing how long the Back+Down combo
must be held to toggle the upcoming button lock feature. A value of 0
(the default) disables the feature. The handler accepts only the values
offered by the Settings UI (0/1/2/3/5/10 seconds).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Andreas Unterkircher <unki@netshadow.net>
Holding Back+Down for the configured duration locks all button input;
holding the combo again unlocks. While locked, button events are masked
from every task in the kernel event loop; on touch-capable boards the
touch sensor is disabled too, restored to the persisted touch pref on
unlock. Lock/unlock gives a vibe pulse and a brief popup, and pressing
a button while locked shows an unlock hint.

The module delivers a button UP only if its DOWN was delivered, keeping
click recognizers balanced, and cancels the back-button force-quit
timer when the combo becomes pending so holding the combo inside an app
does not force-quit it. The first combo button may still perform its
normal press action, matching quick launch combo behavior. Locked state
is RAM-only: a reboot always unlocks. The ISR-level hardware reset
combo and the back-quickpress coredump remain functional while locked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Andreas Unterkircher <unki@netshadow.net>
New row in Settings > System opening an option menu with the button
lock hold duration: Off (default), 1, 2, 3, 5 or 10 seconds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Andreas Unterkircher <unki@netshadow.net>
Cover combo detection, configurable hold duration, abort on early
release or third button, input swallowing and hint popup while locked,
touch pref restoration on unlock, single toggle per continuous hold and
the timer-fire-after-release race.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Andreas Unterkircher <unki@netshadow.net>
With the buttons locked, an incoming notification (push message,
calendar alert, ...) stays on top of the watchface and pressing a
button gives no visible reaction - neither the "Hold Back + Down to
unlock" hint nor, after performing the unlock combo, the "Buttons
Unlocked" confirmation. Only the vibe hints that the lock reacted
at all.

Cause: the button lock popups were pushed on the ModalPriorityGeneric
window stack. Modal windows composite by priority (Discreet < Generic <
Phone < Notification < Alert < Voice < Critical < Alarm), and
notification windows live at ModalPriorityNotification, so the lock
dialogs were created correctly but sat invisibly underneath the
notification until their 1.8 s timeout popped them again.

Fix: push the popups at ModalPriorityAlert instead. The priority was
chosen deliberately:

- Above Notification and Phone, so the hint and lock/unlock feedback
  are visible in exactly the scenarios where the lock's behavior is
  otherwise inexplicable (notification or incoming call on screen).
- Same level as the crash and low-battery dialogs: within one stack the
  newest window is on top, so a lock toast briefly overlays them and
  reveals them again when it times out - nothing is dismissed or lost.
- Deliberately below Critical (BT pairing requests) and Alarm (alarm
  ring): those must never be masked by a lock toast, and going higher
  would buy nothing - a higher-priority window simply covers our
  dialog, which times out silently underneath. In critical-battery
  mode the modal floor is raised to ModalPriorityAlarm anyway, so no
  popup of ours shows there regardless of this choice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Andreas Unterkircher <unki@netshadow.net>
@unki
unki force-pushed the feature/button-lock-with-key-combo branch from b289db7 to e4a7d09 Compare August 12, 2026 19:01
loxK and others added 2 commits August 14, 2026 12:42
touch_nav_dispatch() runs on the app task, which is unprivileged for
third-party apps (prv_init_from_info_common sets is_unprivileged on
every app loaded from its PebbleProcessInfo header). Its first
statement read the touch service's nav gates directly, taking the
kernel-owned mutex over kernel-owned statics, so any app that opted
into touch navigation faulted on its first touch:

  PC 0x120ffa68 -> touch_nav_enabled    services/touch/touch.c:86
  LR 0x12142e83 -> touch_nav_dispatch   recognizer/touch_nav.c:459

Add sys_touch_nav_enabled() and sys_touch_app_nav_active(), mirroring
the existing sys_touch_service_is_enabled(), and call those instead.

The recognizers had the same problem one level down: tap, pan, swipe
and the menu_layer double-tap window all called rtc_get_ticks(), which
reaches xTaskGetTickCount(). Route those nine call sites through the
existing sys_get_ticks().

Built-in system apps are privileged and never hit either path, which
is why the system UI scrolled by touch while third-party apps died.

This also affected the documented recognizer route:
window_attach_recognizer was unusable because
recognizer_manager_handle_touch_event() is only reached from
touch_nav.c:496 and :527, both downstream of the fault.

Verified on QEMU pebble-emery and on obelix hardware: tap, touch
scroll and double-tap in a third-party app that calls
app_touch_navigation_enable(true).

Fixes coredevices#1865

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Laurent Dinclaux <laurent@gecka.nc>
Signed-off-by: Joshua Jun <lets@throw.rocks>
@HobbitJack

Copy link
Copy Markdown

Would it make sense to have this as a Quick Launch action and simply introduce a Back+Down quick launch?

@unki

unki commented Aug 24, 2026

Copy link
Copy Markdown
Author

Hi @HobbitJack

Would it make sense to have this as a Quick Launch action and simply introduce a Back+Down quick launch?

Basically yes, feasible. But I guess only for the locking half.

  • Unlock still needs a kernel-side detector. While locked, all button events are swallowed before Quick Launch ever runs. And once that detector exists, it can also handle locking for free. Quick Launch would be a second mechanism on top, not a replacement.
  • To Quick Launch specifically:
    • only works on the watchface. With the current approach you can lock from inside any app. With Quick Launch you'd have to navigate back first - and those Back presses still act in the app (might be an undesired action)
    • is an app launcher (every slot launches an AppInstallId). It would need to be extended for system actions like button lock, or we'd add a stub "Button Lock" system app.
    • combos fire after a fixed 400 ms. That could be too twitchy for disabling all button and touch inputs. Currently, the configurable 1-10 s hold exists to prevent accidental locking. So Quick Launch timing potentially would need special-casing too.

unki added 2 commits August 25, 2026 21:10
…-with-key-combo

Signed-off-by: Andreas Unterkircher <unki@netshadow.net>

# Conflicts:
#	src/fw/shell/normal/prefs.c
#	src/fw/shell/normal/prefs_values.h.inc
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.

4 participants