Skip to content

Add double-click Back gesture and per-app SDK control over it - #1

Merged
m4l3vich merged 6 commits into
masterfrom
feature/back-on-double-click
Jul 28, 2026
Merged

m4l3vich merged 6 commits into
masterfrom
feature/back-on-double-click

Conversation

@NovaStream2030

@NovaStream2030 NovaStream2030 commented Jul 27, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Settings > Controls now lets the user pick Hold or Double-click as the CENTER "Back" trigger (device-wide preference), implemented in jpp_keypad_core.
  • Native apps can additionally call jpp_sdk_set_back_gesture_enabled() to suppress delivery of the gesture entirely, or jpp_sdk_set_force_hold_back_gesture() to force Hold semantics for themselves regardless of the system-wide preference — for apps that overload CENTER as a primary action button (e.g. a "fire" button) so accidental long-holds/double-taps aren't misread as Back.
  • testapp_native exercises the new jpp_sdk_set_back_gesture_enabled call from its System test menu.
  • Docs updated per project convention: AGENTS.md (keypad core, settings screen, SDK bridge, conventions) and docs/sdk-reference.md (new Key input entries).

Test plan

  • idf.py build via Docker — clean build, verified on hardware (ESP32-C6)
  • Flashed and confirmed default Hold-mode behavior unchanged (instant OK, 700ms hold = Back)
  • Verified Settings > Controls toggle to Double-click: OK deferred correctly, two rapid clicks fire Back with no OK
  • Reviewer: confirm jpp_sdk_set_back_gesture_enabled/jpp_sdk_set_force_hold_back_gesture symbol table entries are picked up correctly by any downstream native app builds

Settings > Controls now lets the user pick Hold or Double-click as the CENTER Back trigger; native apps can additionally suppress delivery (jpp_sdk_set_back_gesture_enabled) or force Hold semantics for themselves regardless of the system preference (jpp_sdk_set_force_hold_back_gesture), so apps that overload CENTER as a primary action button aren't disrupted by accidental long-holds or double-taps.
@NovaStream2030
NovaStream2030 force-pushed the feature/back-on-double-click branch from 4f4dd97 to ccb3be3 Compare July 27, 2026 13:14
@NovaStream2030

Copy link
Copy Markdown
Member Author

@copilot review it

Copilot AI commented Jul 27, 2026

Copy link
Copy Markdown

@copilot review it

I reviewed the current PR changes (including keypad behavior, SDK bridge calls, app_main handling, settings persistence, and symbol exports) and didn’t find any high-confidence bugs or security issues. I also reran the host-side checks (python3 -m pytest tests), and they pass.

NovaStream2030 and others added 5 commits July 27, 2026 20:33
set_back_gesture_enabled and set_force_hold_back_gesture were native-C
only; add the jppsdk bindings (qstr + module table entries) so
MicroPython apps get the same control, and update the docs and
testapp_mp accordingly.
Resolves the stale base: apps/testapp_native and apps/testapp_mp were
removed from this repo by the apps-repo-split and now live in the
sibling jppdos-apps repo, so the PR's edits to them are dropped here
and will be re-applied there against the final SDK surface.
Builds on NovaStream2030's double-click Back gesture, keeping the feature
and the Settings > Controls preference intact while moving where the
decision is made.

jpp_keypad_core goes back to being a pure detector: it no longer knows
what "Back" is. It always reports CENTER_SHORT / CENTER_LONG /
CENTER_DOUBLE and carries a single timing knob, detect_double_click,
replacing the back_gesture policy enum in jpp_keypad_config_t. Two
consequences fall out: a hold is now detected in both modes (previously
selecting Double-click meant a hold produced no event at all), and a
short click still in flight when the mode changes is flushed rather than
stranded or replayed.

keypad_task in main/app_main.c becomes the one place that knows both the
user's preference and what the foreground app wants, and turns raw
gestures into a Back action, a raw app key, or nothing.

On the SDK side, jpp_sdk_set_back_gesture_enabled and
jpp_sdk_set_force_hold_back_gesture are replaced by a single
jpp_sdk_claim_center(ctx, mask). Both setters described a mechanism the
app had to reason about — one suppressed delivery, the other overrode
the detector — and neither let an app write code that was blind to the
user's setting. A claim describes intent instead:

  claim nothing  -> JPP_SDK_KEY_CENTER + JPP_SDK_KEY_BACK, with the
                    firmware deciding which gesture is Back
  claim anything -> the claimed gestures arrive as CENTER_HOLD/_DOUBLE,
                    BACK stops being delivered, and the app owns its exit

Claiming only HOLD also keeps the short click instant, because nothing
then needs to tell a double-click apart — which is what the force-hold
setter existed to achieve, now as a consequence rather than a flag.

JPP_SDK_KEY_BACK is an alias of the existing JPP_SDK_KEY_CENTER_LONG, so
apps and already-built binaries are unaffected. The new key values and
the center_claim field are appended, never inserted: native apps are
separately-built ELFs loaded from SD and read jpp_sdk_context_t fields
directly (apps/demoscene and apps/games both read canvas_fullscreen), so
inserting into the struct would silently shift every offset they were
compiled against.

Also keeps NovaStream2030's jpp_sdk_confirm symtab fix, which is
unrelated to this feature but a real one: without it any native app
calling jpp_sdk_confirm dies at launch with UNRESOLVED_SYM.
jpp_keypad_poll() takes one ADC sample and derives its clock from
sample_index * poll_interval_ms, so the firmware source compiles natively
and can be stepped a poll at a time. tests/keypad_harness.py builds it
with cc and drives it over ctypes; tests/test_keypad.py covers the timing
that is easy to get wrong and impossible to eyeball:

  - a short click is released immediately when nothing needs to tell a
    double-click apart, and withheld for double_click_ms when something does
  - a second click inside the window reports CENTER_DOUBLE and neither
    click leaks an OK; outside the window they stay two separate clicks
  - a hold reports CENTER_LONG exactly once, in both modes
  - a click already withheld when the mode changes is flushed rather than
    dropped or replayed
  - direction keys are untouched throughout

Both of the behaviours this branch changed are covered by a test that
fails if the change is reverted, checked by mutating the source and
re-running.
…rule

Rewrites the Key input section of docs/sdk-reference.md around
claim_center, documents JPP_SDK_KEY_BACK / _CENTER_HOLD / _CENTER_DOUBLE
and the CENTER_CLAIM_* constants, and leads with the property that
matters to app authors: an app that claims nothing never reads the user's
Back preference.

AGENTS.md gets the reworked jpp_keypad_core and jpp_sdk_bridge rows, plus
two conventions that did not exist before and cost this branch time:

  - CENTER gesture policy lives only in keypad_task(), because it is the
    only code that sees both the user preference and the app's claim.
    Says explicitly not to push it back into the detector or into
    jpp_ui_normalize_action().
  - SDK-visible types are append-only. Native apps are separately-built
    ELFs that read jpp_sdk_context_t fields directly, so a field inserted
    mid-struct shifts offsets in already-deployed .bin files with no
    load-time error. This is what the original PR did with its two bools.

Drops the reference to "DOOM-lite" as the reference user for the removed
calls — no such app exists in BUILTIN APPS or under apps/.

Also records the keypad harness in the tests row as the pattern to copy
for other pure jpp_core state machines, and adds a user-facing CHANGELOG
entry for both halves of the feature.
@m4l3vich

m4l3vich commented Jul 28, 2026 •

Copy link
Copy Markdown
Collaborator

(The following was written by Claude Opus 5)
Thanks @NovaStream2030 — the double-click Back gesture and the Settings > Controls preference are both keepers, and they're in this branch unchanged in behaviour. I've pushed three commits on top of your two (merging master in rather than rebasing, so your commits stay intact) that rework where the decision gets made. Summary of what changed and why:

The SDK surface

jpp_sdk_set_back_gesture_enabled and jpp_sdk_set_force_hold_back_gesture are replaced by one call:

jpp_sdk_claim_center(ctx, JPP_SDK_CENTER_CLAIM_HOLD);

Both setters described a mechanism the app author had to reason about — one suppressed delivery, the other overrode the detector — and with either of them an app still had to think about what the user's Back preference was doing to it. A claim describes intent instead:

  • claim nothing (the default) → you get JPP_SDK_KEY_CENTER and JPP_SDK_KEY_BACK, and the firmware decides which physical gesture is Back. App code never reads the setting.
  • claim anything → the claimed gestures arrive as JPP_SDK_KEY_CENTER_HOLD / _DOUBLE, JPP_SDK_KEY_BACK stops being delivered, and the app owns its exit.

Claiming only HOLD also keeps the short click instant, because nothing then needs to tell a double-click apart — which is exactly what set_force_hold_back_gesture existed to achieve, now as a consequence rather than a flag.

The layering

jpp_keypad_core goes back to being a detector. It no longer knows what "Back" is: back_gesture is gone from jpp_keypad_config_t, replaced by a single detect_double_click timing knob. All the policy now lives in keypad_task(), which is the only code that can see both the user preference and the app's claim. This is what made the per-app override necessary in the first place — the old shape had app_main.c mutating a config copy every poll to reach back into the detector.

Two bugs fall out of the restructure:

  • A hold produced no event at all in Double-click mode. Hold is now always detected; it just isn't Back when the user picked double-click.
  • ok_pending outlived a mode change. jpp_keypad_check_pending_ok() ran unconditionally, so a click withheld in Double-click mode would still fire after switching to Hold. A withheld click is now flushed on the next poll instead of stranded or replayed.

One thing worth flagging for next time

The two bools were inserted into the middle of jpp_sdk_context_t, before key_queue and canvas. Native apps are separately-built ELFs loaded from SD and some read struct fields directly — apps/demoscene/src/demoscene.c:616 and apps/games/src/games_gfx.c:172 both read canvas_fullscreen — so that shifts every offset an already-deployed .bin was compiled against, silently, with no load-time error. The new center_claim field is appended at the tail, and the new key enum values are appended too; JPP_SDK_KEY_BACK is an alias of JPP_SDK_KEY_CENTER_LONG so existing apps and binaries are untouched. I've written this up as an explicit convention in AGENTS.md since nothing stated it before.

Also in these commits

  • Host-side tests (tests/test_keypad.py). jpp_keypad_poll() is pure — one sample in, events out, clock derived from sample_index * poll_interval_ms — so tests/keypad_harness.py compiles the real jpp_keypad_core.c with cc and drives it over ctypes, one 20 ms poll at a time. Covers the deferred click, the double-click window, hold in both modes, and the mode-change flush. I checked both behaviours this branch changed by mutating the source and confirming the matching test fails.
  • Rebased onto the apps-repo-split. The branch was based on a master that predated it, so it wasn't mergeable. Your testapp_native / testapp_mp edits are dropped here because those apps now live in jppdos-apps — they need re-doing there against claim_center, which also settles the unchecked item in your test plan about downstream native builds.
  • Kept your jpp_sdk_confirm symtab fix. Unrelated to this feature but a real bug — without it any native app calling jpp_sdk_confirm dies at launch with UNRESOLVED_SYM.
  • Docs updated per the project convention: docs/sdk-reference.md, AGENTS.md, and a user-facing CHANGELOG.md entry.
  • Dropped the AGENTS.md line naming "DOOM-lite" as the reference user for the two calls — there's no such app in the repo.

Verification

  • docker compose run --rm build idf.py build — clean, 0x16f20 (~92 KB, 5%) free in the app partition, no change to the headroom
  • python3 -m pytest tests — 17 passed
  • Docs site builds clean via the jppd-docs image

Not yet verified on hardware — worth a pass on a real unit before this lands, particularly the double-click feel with the 300 ms window and hold-to-pause in a claiming app.

@m4l3vich
m4l3vich merged commit cafc554 into master Jul 28, 2026
4 checks passed
@github-actions
github-actions Bot deleted the feature/back-on-double-click branch September 3, 2026 15:14
@m4l3vich
m4l3vich restored the feature/back-on-double-click branch September 3, 2026 16:09
@github-actions
github-actions Bot deleted the feature/back-on-double-click branch September 3, 2026 16:10
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.

3 participants