Bedtime. /Input Handling

Input Handling

Button and encoder input

Input Callback

Use the on_input callback in the app descriptor:

void my_input(os_app_t* app, uint8_t btn, KeyStateEnum state) {
    if(btn == BTN1 && state == KEY_STATE_PRESSED) {
        // BTN1 pressed
    }
}

BTN4 and BTN5 HOLD are the firmware's on a tapp that DECLARES a type and never reach on_input there; their presses do (and those two PRESS cells are the tapp's to label, see ui_hints_set()). By role:

Button IDs

idCommon use
BTN1Select
BTN2-
BTN3-
BTN4system: quick hints / main menu (HOLD) on a standalone; press goes to the tapp
BTN5system: close hints / main menu (HOLD) on a standalone; press goes to the tapp
BTN_ENCODERthe encoder turned - NOT a button

There is no encoder push button. BTN_ENCODER fires when the encoder is turned, and its state is always KEY_STATE_RELEASED - never branch on it. Either ignore the event and poll os_controls_encoder_get_delta() from tick(), or treat it as "something moved" and read the delta in response. hint_press(btn) / hint_hold(btn) name a button's cells.

int32_t os_controls_encoder_get_delta(void)

Get encoder rotation delta since last call

Returns — Signed delta (positive = clockwise, negative = counter-clockwise)

int32_t diff = os_controls_encoder_get_delta();
if (diff != 0) {
    model->value += diff;
}
void os_controls_enable_shutdown(bool state)

Take (or give back) the BTN5 hold-to-power-off gesture

boolstatetrue = BTN5 hold powers the device off (the default), false = the app owns it

A hold on BTN5 switches the device off after ~3.2s, from anywhere - and from ~1s the hint cell in BTN5's column turns into a filling power glyph. That is the right default, but it collides with an app that maps BTN5 holds to something of its own: hold "right" in a game for three seconds and the device powers down mid-play.

Pass false to take the button, true to give it back. Take it only if the app actually uses BTN5 holds - while the app holds it, the user cannot switch the device off without leaving the app first, so leaving must remain possible.

Restored automatically when the image closes, INCLUDING when init() returns false (which never runs deinit()) and when the tapp faults. The app should still give it back explicitly; the sweep is a safety net, not the contract.

static bool app_init(os_app_t* app) {
    os_controls_enable_shutdown(false); // BTN4/BTN5 holds are our D-pad
    return true;
}
static void app_deinit(os_app_t* app) {
    os_controls_enable_shutdown(true);
}