Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite sdl keyboard_handler to return SDL key and release state #236

Closed
wants to merge 2 commits into from
Closed

Rewrite sdl keyboard_handler to return SDL key and release state #236

wants to merge 2 commits into from

Conversation

daviel
Copy link

@daviel daviel commented Sep 16, 2022

Doing it this way made it possible to find out the release state of one or multiple keys. Also you can add listeners for keys beside the LVGL ones by using the SDL-key-constants.

For whatever reason as soon as I add a group to retrieve events from widgets there is still fired a LV_INDEV_STATE_RELEASED immediately after a LV_INDEV_STATE_PRESSED event. Maybe there is some issue in the way events are handled in the LVGL core? In think that indev_keypad_proc in lv_indev.c may be the reason.

Let me know what you think. Maybe I misunderstood something in the LVGL implementation.

driver/SDL/sdl_common.c Outdated Show resolved Hide resolved
@@ -263,6 +261,6 @@ uint32_t keycode_to_ctrl_key(SDL_Keycode sdl_key)
return LV_KEY_PREV;

default:
return '\0';
return sdl_key;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct to return sdl_key here?
Until now we only returned ctrl_key (or LVGL codes)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When there is no way to map the SDL_KEY to a LVGL key we can return the sdl_key. This could be useful if you try to map events to other keys.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But how do you guarantee there are no "collisions"?
Could some sdl_key value be the same as another LV_KEY_*, so that the result is ambiguous?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not look like they are conflicting. It seems the constants for the lv_keys were used from sdl:

lv.KEY.dict
{'UP': 17, 'DOWN': 18, 'RIGHT': 19, 'LEFT': 20, 'ESC': 27, 'DEL': 127, 'BACKSPACE': 8, 'ENTER': 10, 'NEXT': 9, 'PREV': 11, 'HOME': 2, 'END': 3}

https://wiki.libsdl.org/SDLKeycodeLookup

Copy link
Collaborator

@amirgon amirgon Sep 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's interesting.

@kisvegabor - Could you confirm it's OK for an input driver to return to LVGL the SDL key codes directly?

If it is, that means:

  • We assume that LVGL key code will always match SDL key codes
  • LVGL might receive codes that are not part of the LV_KEY_* enum

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LV_KEY_*s are non printable characters and it's fine to pass normal character, like 'a', 'b', 'c' as they are. E.g. in this example you can type anything into the Text area with your keyboard.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LV_KEY_*s are non printable characters and it's fine to pass normal character, like 'a', 'b', 'c' as they are. E.g. in this example you can type anything into the Text area with your keyboard.

@kisvegabor Right. But is it also ok for a read_cb callback of an LVGL input device to update key to something that is neither a normal character nor LV_KEY_*?
Here we return sdl_key so LVGL might be getting some unexpected codes - SDL codes which are not regular characters and are missing (or unmatching with) LV_KEY_* enum.

Maybe that's ok - I would just like to make sure it really is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe that's ok - I would just like to make sure it really is.

Confirmed, it's fine this way.

@daviel daviel changed the title WIP: rewrite sdl keyboard_handler to return SDL key and release state Rewrite sdl keyboard_handler to return SDL key and release state Sep 17, 2022
driver/SDL/sdl_common.c Outdated Show resolved Hide resolved
driver/SDL/sdl_common.c Outdated Show resolved Hide resolved
driver/SDL/sdl_common.c Outdated Show resolved Hide resolved
driver/SDL/sdl_common.c Outdated Show resolved Hide resolved
if (len < KEYBOARD_BUFFER_SIZE - 1) {
for (uint32_t i = 0; i < strlen(event->text.text); i++) {
key_buffer[key_buffer_len + i].key = event->text.text[i];
key_buffer[key_buffer_len + i].state = key_state;
Copy link
Collaborator

@amirgon amirgon Sep 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it correct to set state = key_state here?
Since we are under SDL_TEXTINPUT, key_state will always be "PRESSED" here.
So when is each of these keys "released"?

Perhaps you could add two events for each character, "press" and "release", to simulate a click on a key.

But in that case, if SDL sends you SDL_TEXTINPUT events, you will not be able to detect which keys are pressed together.
Do you know when SDL sends SDL_TEXTINPUT and when it sends SDL_KEYDOWN/SDL_KEYUP? Can we control that?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a good explanation of what SDL_TEXTINPUT does: https://wiki.libsdl.org/Tutorials-TextInput

So what would happen is that you would get events for special characters. Defaulting to PRESSED is okay as the implementation before did the same I think.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what would happen is that you would get events for special characters. Defaulting to PRESSED is okay as the implementation before did the same I think.

What previous implementation did, was to send both "PRESSED" and "RELEASED" states for each character, one after the other. This simulates a key press where a key was pressed and immediately released and that is the reason you weren't able to detect situations where two keys were pressed simultaneously.

But here you are sending only "PRESSED" and not "RELEASED" event. That's why I suggested adding two events for each character, a "press" event followed by a "release" event, to simulate a key click.

Here is a good explanation of what SDL_TEXTINPUT does: https://wiki.libsdl.org/Tutorials-TextInput

Thanks for the link, I wasn't familiar with these subtleties.

So suppose the user enables IME and sends some characters to the application.
Don't you think we need both "PRESSED" and "RELEASED" events for each character? Otherwise, we might get the impression that all these characters were pressed together and never released.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I have added a released event directly afterwards.

In my test application I can now see the default LVGL events when they are being hold but I still can't reproduce an LV_EVENT_LONG_PRESSED_REPEAT with pressing and holding enter. Is this intended? The behavior changes also when I add a group and focus it. Then I receive a pressed and released event for every key everytime.

Also the not-defined keys (SDL_KEYS) are now showing a released event immediately after being pressed. I am not sure what is happening here. This is my testing program which I run locally: https://sim.lvgl.io/v9.0/micropython/ports/javascript/index.html?script_direct=f1e7b7008363a47c9837289fe961a0bf8412143b

Copy link
Collaborator

@amirgon amirgon Sep 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to run your code and I can see multiple problems.

It looks like SDL is sending both SDL_KEY* and SDL_TEXTINPUT events for the same click.
This, or course, is a source of confusion, since LVGL sees every key twice.

I've tried removing SDL_TEXTINPUT altogether, although this is incorrect not only for IME but also for simple SHIFT+key (for typing capital letters or punctuation characters for example).

Next problem was that LVGL sets key in lv_indev_data_t to the last key automatically, but only the last key that was released. So it looks like LVGL itself was not built to handle correctly simultaneous key presses.

To work around this I tried to always set data in sdl_keyboard_read to either key_buffer[0] or, when key_buffer is empty, to the last state and key that were set, like this:

diff --git a/driver/SDL/sdl_common.c b/driver/SDL/sdl_common.c
index 071a1f1..4ef8511 100644
--- a/driver/SDL/sdl_common.c
+++ b/driver/SDL/sdl_common.c
@@ -79,14 +79,16 @@ void sdl_mousewheel_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
 void sdl_keyboard_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
 {
     (void) indev_drv;      /*Unused*/
+    static struct key_event last_state = {0};
 
     if(key_buffer_len > 0){
-        data->state = key_buffer[0].state;
-        data->key = key_buffer[0].key;
+        last_state = key_buffer[0];
         key_buffer_len--;
         data->continue_reading = key_buffer_len > 0;
         memmove(&key_buffer[0], &key_buffer[1], sizeof(struct key_event) * key_buffer_len);
     }
+    data->state = last_state.state;
+    data->key = last_state.key;
 }

With this fix, the test program prints the "Pressed" and "Released" events correctly, even in cases where multiple keys are pressed simultaneously. (with SDL_TEXTINPUT handling disabled for the test)
However, LVGL does not behave correctly in such cases and loses key events...

So to summarize, the challenges I see are:

  • How to handle both SDL_KEY* and SDL_TEXTINPUT events without sending duplicated key events to LVGL
  • How to fix LVGL to work correctly in situations where keys are pressed simultaneously (@kisvegabor and @embeddedt might have some ideas about that)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One disadvantage of SDL_TEXTINPUT is that it doesn't detect simultaneous key presses, which is the motivation for this change.

What do you exactly mean by "simultaneous key presses"? E.g. when If press S and D on my notebook at the same time I get: sddddddddddddddddddd

So what would be expected behavior?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expected behavior will be not to lose key strokes....
Here is an example:

https://sim.lvgl.io/v9.0/micropython/ports/javascript/index.html?script_direct=1f683e2db0e13bb25905bbfeaec508d5924b9bd4

The first line in the textarea is ABC where each key is pressed and released in turn.
The second and third lines are the result of the same ABC key presses, where their release is in different order.
As you can see, on the second and third lines, only A is displayed by LVGL.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the online C examples SDL_TEXTINPUT is enabled and text areas work like any text input. You can try it here. I don't know exactly how the current driver in MP works but I think it's a reasonable default to be similar to other text inputs on desktop/web.

I think the root of the problem is that PC keyboard sends the keys on press while LVGL sends them on release. If we send the keys on release, the newly pressed key will overwrite the old one, and the last one will be released.

We a little trick the read_cb can be modified to react on press: https://sim.lvgl.io/v9.0/micropython/ports/javascript/index.html?script_direct=efcd5f334fb6b46a367f591d77acb3088c435061

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We a little trick the read_cb can be modified to react on press:

@kisvegabor In that trick you triggered a fake "release" for every "press".
If I understand correctly, you are saying that in order for a driver to work correctly, every "press" must be immediately followed by a "release" of the same key....

So now I don't understand the point in data.state.
If every "press" must be followed by a "release" (or we lose key strokes), why can't we give up data.state altogether?
In each call read_cb could update only data.key, and let LVGL do the rest. Why do we need to bother with "press" and "release" states, unless they have some meaning?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without letting LVGL to handle the pressed and released state LVGL couldn't manage the long pressed and long pressed repeat events. So it's something for something.

The simplest would be to make LVGL send the key events on press instead of release. I have to try it out as it might have unexpected consequences but seems working at first glance.

if (len < KEYBOARD_BUFFER_SIZE - 1)
strcat(buf, event->text.text);
const size_t len = key_buffer_len + strlen(event->text.text);
if (len < KEYBOARD_BUFFER_SIZE - 1) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that you are adding both "pressed" and "released" events, I think you also need to change this "if" condition to prevent overflowing the buffer, since you are now adding two elements to the buffer for each character and not one.

@amirgon
Copy link
Collaborator

amirgon commented Mar 13, 2023

Micropython bindings removed this driver and now uses LVGL built-in SDL driver.
Any change to SDL driver should be done on the LVGL SDL driver.

@amirgon amirgon closed this Mar 13, 2023
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.

None yet

3 participants