The new Button class wrapper handles physical button interactions by passing raw down/up state events directly to JavaScript. However, because it hooks directly into low-level hardware interrupts (PEBBLE_BUTTON_DOWN_EVENT and PEBBLE_BUTTON_UP_EVENT) without neutralizing PebbleOS's native menu-scrolling auto-repeat logic or configuring window click overrides, a continuous physical hold is delivered to JavaScript as a rapid succession of alternating, independent tap and hold cycles.
This makes it impossible to implement reliable long-press versus short-press logic in JavaScript, as the underlying platform forcefully injects synthetic release/re-press cycles into the stream during a single physical hold.
Steps to Reproduce
Instantiate a basic button handler tracking time duration on release:
let buttonDownTime = null;
new Button({
types: ["select"],
onPush(down, type) {
if (down) {
if (buttonDownTime) return;
buttonDownTime = Date.now();
} else {
if (!buttonDownTime) return;
const duration = Date.now() - buttonDownTime;
buttonDownTime = null;
if (duration >= 600) {
console.log("Long Press Detected");
} else {
console.log("Short Press Detected");
}
}
}
});
Press and hold down the button continuously and observe the JavaScript execution path. Instead of firing a single long-press event when the button is held down continuously, the execution logs register an initial short press followed by a long press a few seconds later as the raw event stream cycles through multiple phantom release and press states:
[21:47:02] xsHost.c:155> Short Press Detected
[21:47:04] xsHost.c:155> Long Press Detected
Looking at the underlying C bridge configuration:
if (NULL == getModdableAppState(buttons)) {
EventServiceInfo *i = &getModdableAppState(eventServiceDown);
i->type = PEBBLE_BUTTON_DOWN_EVENT;
i->handler = buttonDownEventHandler;
event_service_client_subscribe(i);
i = &getModdableAppState(eventServiceUp);
i->type = PEBBLE_BUTTON_UP_EVENT;
i->handler = buttonUpEventHandler;
event_service_client_subscribe(i);
}
The bridge subscribes directly to raw EventService interrupts and mirrors them straight to the XS machine context via xsCallFunction2(xsReference(pb->onPush), pb->obj, xsVar(0), xsVar(1));.
Because PebbleOS handles button interactions natively through its higher-level window click configurations (ClickConfigProvider), exposing these raw, unconfigured event interrupts directly to the JavaScript layer introduces artificial state cycles that break standard JS event timing.
The
new Buttonclass wrapper handles physical button interactions by passing raw down/up state events directly to JavaScript. However, because it hooks directly into low-level hardware interrupts (PEBBLE_BUTTON_DOWN_EVENTand PEBBLE_BUTTON_UP_EVENT) without neutralizing PebbleOS's native menu-scrolling auto-repeat logic or configuring window click overrides, a continuous physical hold is delivered to JavaScript as a rapid succession of alternating, independent tap and hold cycles.This makes it impossible to implement reliable long-press versus short-press logic in JavaScript, as the underlying platform forcefully injects synthetic release/re-press cycles into the stream during a single physical hold.
Steps to Reproduce
Instantiate a basic button handler tracking time duration on release:
Press and hold down the button continuously and observe the JavaScript execution path. Instead of firing a single long-press event when the button is held down continuously, the execution logs register an initial short press followed by a long press a few seconds later as the raw event stream cycles through multiple phantom release and press states:
Looking at the underlying C bridge configuration:
The bridge subscribes directly to raw EventService interrupts and mirrors them straight to the XS machine context via
xsCallFunction2(xsReference(pb->onPush), pb->obj, xsVar(0), xsVar(1));.Because PebbleOS handles button interactions natively through its higher-level window click configurations (
ClickConfigProvider), exposing these raw, unconfigured event interrupts directly to the JavaScript layer introduces artificial state cycles that break standard JS event timing.