Missed key release events in group?

I need LV_EVENT_LONG_PRESSED_REPEAT or LV_EVENT_RELEASED to implement accelerating increment/decrement of setting value, from “keyboard” (5-way tactile switch).

But seems those events are generated for ENTER only:

Can this be fixed/workarounded somehow without manual magic with timers?

Currently solved by hacking driver & injecting events from wrapper. But this looks ugly.

bool my_keyboard_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
    static int prev_state;
    // Call original function
    keyboard_read(indev_drv, data);

    // HACK! Send release codes to group, to compensate lack of
    // existing ones
    if (app_data.group)
        // Spy data & inject events before continue
        if (data->state == LV_INDEV_STATE_REL && prev_state == LV_INDEV_STATE_PR)
            lv_obj_t * selected = lv_group_get_focused(app_data.group);
            if (selected)
                lv_event_send(selected, HACK_EVENT_RELEASED, &data->key);

    prev_state = data->state;

    return false;

Yes, they are generated only for ENTER because conceptually ENTER replaces like the touch press and the others LEFT/RIGHT/etc the finger move.

Glad to here the you’ve found a solution!

I think, it’s inconsistent to miss key release events. And inconsistent to flag auto-repeat for enter only (other keys receive ordinal repeats, without chance to understand, is it first or autogenerated).

Would you consideradd “missed” things? At least, “release” events will not harm anyone (and those are enouth for my needs).

I’d like to avoid using described hack, it smells bad :slight_smile:

LittlevGL treats actions on LV_KEY_UP/OWN/NEXT/PREV as “commands” instead of “clicks”. So when you click the LV_KEY_UP button you command the library to increment the value (or so) of the focused object. Therefore it doesn’t make any sens to have long press event from a “command”.

If you need a functionality like this maybe you’d rather choose an LV_INDEV_TYPE_BUTTON input device. Create some transparent buttons and assign their coordinates to the the physical buttons.
These buttons will have long press event.
More info about it

IMO magic with hidden buttons looks even more strange than my hack. I think, current approach push users too much for specific controlling pattern.

If many users are satisfied with your concept of kbd events mapping - that’s ok for them. But this should not be the only choice. And i see no technical reasons why this limitation can not be removed.

The only mandatory thing is “group”, which defines focus change order and can accept some commands to switch that focus.

So, if I understood well, your suggestion is to send LV_EVENT_PRESS/PRESSING/LONG_PRESS/etc e.g. LV_KEY_RIGHTtoo. In this case, you will get the same event on LV_KEY_ENTER and LV_KEY_RIGHT. To get which key triggered the event you need to call lv_indev_get_key(lv_indev_act) in the event callbacks and take action accordingly.

It also seems unintuitive to me.

What is your exact use case? Why do you need long press from other keys too? Please describe it and maybe there is an other modul/tool in LittlevGL to support it.

https://github.com/puzrin/dispenser see settings screen on demo image. Joystick up/down used for navigation, left/right to change value (when exists).

Value change should have acceleration. To implement it, we should know difference between first and repeating pulses.

IMO you push specific usage model instead of providing api to build required ones. I thing, value of “group” is to define list & order of elements, not how they should be used.

I see. My concern is that it’d introduce additional complexity.

Why don’t you save the press timestamp of LEFT/RIGHT and check the difference in LV_EVENT_LONG_PR? It’s not really a hack and should simply work.

  1. Logically, such approach would duplicate autorepeat functionality in indirect way.
  2. My current approach uses trivial state machine with single counter. Adding timer and timings is completely different complexity level + bad isolation (because need to be in sync with system’s autorepeat).

Not necessarily. I suppose now you have a custom design function. Similarly, you can have a custom signal function too to process LV_KEY_LEFT/RIGHT like this:

if(sign == LV_SIGNAL_CONTROL) {
   char c = *((char *)param);
   if(c == LV_KEY_RIGHT) {
       int32_t step = (lv_tick_elaps(press_time) / 1000) + 1;  //Increase step in every sec
       value  += step;

Initial logical problem is to detect 2 conditions:

  • autorepeat [long press] started
  • autorepeat stopped (because acceleration should reset to initial state)

Your example does not seems to resolve those (it tries to do something with increase, but misses reset).

If it matters to start the acceleration intervals when auto-repeat starts just modify the code like this:

       int32_t t_diff = lv_tick_elaps(press_time) - LV_INDEV_DEF_LONG_PRESS_TIME;
       if(t_diff > 0) {
           int32_t step = (lv_tick_elaps(press_time) / 1000) + 1;  //Increase step in every sec
           value  += step;

You should save press_time on every LEFT/RIGHT press therefore lv_tick_elaps(press_time) will give the result relative to the last press.

That’s all doable, but as i said, indirectly duplicates core functionality. If autorepeat cares about timings, upper level should not repeat the same (or similar) - that would be bad isolation between different api layers:

[driver, low level events] => [autorepeater, events] => [accelerator, events]

Accelerator should not analyze timings, because those are “private” attributes of autorepeater.

If you would get all the events for LEFT/RIGHT you would do something similar just save the press time in LV_EVENT_LONG_PRESS (instead of the driver), right?

When all events available, it’s enougth to have counter of repeats from first press. No need to fall into lower level and analyze timings.

See example https://github.com/puzrin/dispenser/blob/dev/src/screen_settings.cpp#L33, it works.

In short - “after 20 repeats increase step x10” and so on.

I see.

I don’t see a good way to keep the current simplicity and the opportunity to get events for other keys as you mentioned. I’ll keep this issue in mind and think about it.

In lack of a better idea I suggest using the hacky “clear the counter in driver” solution.

Hi @kisvegabor and @puzrin
Not sure if it can help but this is the way I use the encoder to have a faster change in an editing mode of a widget like a roller when having a long press:

static bool ex_enc_read(lv_indev_t *indev, lv_indev_data_t* data)
	static uint32_t counter = 0;
	static uint8_t ex_keypad_state = 0;

	uint8_t keypad_state = get_keypad_state();

	if(keypad_state == ex_keypad_state) {
	} else {
		counter = 0;

	uint8_t divider = 4;
	if(counter > 150) {
		divider = 1;
	} else if(counter > 100) {
		divider = 2;
	} else if(counter > 50) {
		divider = 3;

	data->enc_diff = 0;
	data->state = LV_INDEV_STATE_REL;

	if(keypad_state == RIGHT_KEY && counter % divider == 0) {
		data->enc_diff = +1;
	} else if(keypad_state == LEFT_KEY && counter % divider == 0) {
		data->enc_diff = -1;
	} else if(keypad_state == HOME_KEY) {
		data->state = LV_INDEV_STATE_PR;

	ex_keypad_state = keypad_state;
	return false; /*false: no more data to read because we are not buffering*/

PS: As you might guess I don’t have a real encoder and it’s indeed a keypad with left/right and enter buttons.