Behavior difference in TextArea between lwjgl 2 and 3

It’s not platform dependent. I do remember this issue with lwjgl3 now. (To me) they really messed up keyboard events.

Probably this key handler needs some extra checking for the redundant events.

Edit: if you build Lemur from source then maybe you can try some things out? If you find something then let me know and/or submit a PR. I’d be super grateful.

Looking where those events are created:

In lwjgl 2, they are created by LwjglKeyInput:

    @Override
    public void update() {
        if (!context.isRenderable())
            return;
        
        Keyboard.poll();
        while (Keyboard.next()){
            int keyCode = Keyboard.getEventKey();
            char keyChar = Keyboard.getEventCharacter();
            boolean pressed = Keyboard.getEventKeyState();
            boolean down = Keyboard.isRepeatEvent();
            long time = Keyboard.getEventNanoseconds();
            KeyInputEvent evt = new KeyInputEvent(keyCode, keyChar, pressed, down);
            evt.setTime(time);
            listener.onKeyEvent(evt);
        }
    }

with lwjgl3 it’s in GlfwKeyInput, creates the events in two callbacks:

    private void initCallbacks() {
        glfwSetKeyCallback(context.getWindowHandle(), keyCallback = new GLFWKeyCallback() {
            @Override
            public void invoke(final long window, final int key, final int scancode, final int action, final int mods) {

                if (key < 0 || key > GLFW_KEY_LAST) {
                    return;
                }

                int jmeKey = GlfwKeyMap.toJmeKeyCode(key);

                final KeyInputEvent event = new KeyInputEvent(jmeKey, '\0', GLFW_PRESS == action, GLFW_REPEAT == action);
                event.setTime(getInputTimeNanos());

                keyInputEvents.add(event);
            }
        });

        glfwSetCharCallback(context.getWindowHandle(), charCallback = new GLFWCharCallback() {
            @Override
            public void invoke(final long window, final int codepoint) {

                final char keyChar = (char) codepoint;

                final KeyInputEvent pressed = new KeyInputEvent(KeyInput.KEY_UNKNOWN, keyChar, true, false);
                pressed.setTime(getInputTimeNanos());

                keyInputEvents.add(pressed);

                final KeyInputEvent released = new KeyInputEvent(KeyInput.KEY_UNKNOWN, keyChar, false, false);
                released.setTime(getInputTimeNanos());

                keyInputEvents.add(released);
            }
        });
    }

both callbacks are called, so three events are created when I press:

new KeyInputEvent(jmeKey, '\0', GLFW_PRESS == action, GLFW_REPEAT == action);
new KeyInputEvent(KeyInput.KEY_UNKNOWN, keyChar, true, false);
new KeyInputEvent(KeyInput.KEY_UNKNOWN, keyChar, false, false);             

they match what I saw in the handler.

Gladly, I made a super quick hack

                if( evt.getKeyChar() >= 32 ) {
                    model.insert(evt.getKeyChar());
                    evt.setConsumed();
                    //resetText(); ...should be automatic now
                } else if (  evt.getKeyChar() == '\0') {
                    evt.setConsumed();
                }

and it consumed the extra events, but it also consumed the ESCAPE key and I guess other stuff that shouldn’t have.
If you tell me what to change I can try it (tomorrow now is late here).

1 Like

It’s tricky. Especially if the non-char event comes first.

We may have to specifically check for the KeyInput values we want to let through. Which is fragile but I can’t think of another way at the moment.

For keys like escape, do you still see the character version of the event come through?

This was also bugging me in my editor for a while now. Applying your patch solved the problem. Thanks! :slightly_smiling_face:

I think it would be good enough for my case too, but I’d like to get a proper fix to not depend on a custom build.

How would you go about that? Something like this? looks terrible :see_no_evil: :

                if( evt.getKeyChar() >= 32 ) {
                    model.insert(evt.getKeyChar());
                    evt.setConsumed();
                    //resetText(); ...should be automatic now
                  } else if ( evt.getKeyChar() == '\0'
                      && ( evt.getKeyCode() >= KeyInput.KEY_0 && evt.getKeyCode() <= KeyInput.KEY_RETURN
                          || evt.getKeyCode() >= KeyInput.KEY_A && evt.getKeyCode() <= KeyInput.KEY_MULTIPLY
                          || evt.getKeyCode() == KeyInput.KEY_SPACE
                          || evt.getKeyCode() >= KeyInput.KEY_NUMPAD7 && evt.getKeyCode() <= KeyInput.KEY_DECIMAL
                          || evt.getKeyCode() >= KeyInput.KEY_YEN && evt.getKeyCode() <= KeyInput.KEY_UNDERLINE
                          || evt.getKeyCode() >= KeyInput.KEY_HOME && evt.getKeyCode() <= KeyInput.KEY_DELETE) ) {
                    evt.setConsumed();
                }

I excluded the Ctrl and other keys and made guesses about the japanese keyboard codes.

No, I only get one event from the first callback (the one with the keycode and not charcode).

Sorry for being ignorant, but isn’t it enough to just check with evt.getKeyChar() == '\0'? Isn’t that supposed to block all non-char keys?

Yes, but you don’t want to block ESC, or at least when running with lwjgl2 it does not block it (so the simple app exists).

But I suppose any other key that’s not relevant to the text field should also not be blocked (F1, Print Screen, etc.). I’m not 100% sure which ones exactly, Ctrl? Alt?

1 Like

Asking again because I didn’t see an answer.

:point_up:

I like lwjgl3’s key handling less and less every day.

…does it at least send through character 8 for backspace?

No, it only sends the keycode 14 with backspace.

Regarding the escape key, I have registered a release-focus action, mapped to the escape key in the TextEntryComponent (see my other post) so it won’t block the escape key. After the focus is released the rest of the keys should also work normally.

Any ideas how to proceed?

I see, that’s a workaround I think, ideally lemur would behave the same with lwjgl2 and 3.
Unless Paul think it’s fine to consume all input even non-char keys.

I have to look at it a little more and play with the lwjgl2 side to remind me how that works.

1 Like

If someone is looking for ways to help this along then one thing that would save my a BUNCH of time would be to submit a PR for a couple test cases… I’m thinking of some simple app with a UI with text field, a couple buttons (OK, Cancel). Like a project under Lemur/examples.

…even better if there is an lwjgl2 project and an lwjgl3 project so that I can easily run them side-by-side.

Else I will have to create this for myself and that pushes things out a little farther. (I’m trying to get a public Mythruna release out at the end of the month and that is monopolizing my limited free time these days.)

The code snippet I posted at the beginning is enough to see the problem easily, typing and the camera moves or not depending on the lwjgl version.
I can make a simple project that can be easily be built with one or the other versions (but I’ll make it with maven and somebody else port it to gradle I they want), otherwise I could add another Demo to the demo submodule in lemur.

I made this: GitHub - jcfandino/lemur-lwjgl-example

So you simply need to build it twice using the profiles:

./mvnw package -Plwjgl2
./mvnw package -Plwjgl3

and execute each jar:

java -jar target/lemur-example-lwjgl2.jar
java -jar target/lemur-example-lwjgl3.jar

The example code is my original one plus some small additions, let me know if I can add something else.

@pspeed sorry for the ping, did you have chance to try my testcase?
I made it as simple as possible and should be really easy to run side by side.

My quickfix that consumes extra events works for me and I’m using it in a local build, however I see that change can be a problem in case some event has to be handled by another component (e.g. when no pointer enabled).
My more “advanced” fix of consuming certain events depending on the key code may have the correct behavior, but it is very hacky and I think a more proper fix escapes my knowledge of lemur’s design.

This is the only problem I noticed using lwjgl3 and lemur.

No, sorry. I haven’t gotten a chance to mess with this. Your test case gets me only maybe 50% of the way to where I need to be to actually fix the problem without breaking other cases and I haven’t had time to make up the other 50% yet. (Maybe it’s a little closer than that now that I look again.)

It is on my to-do list.

Edit: definitely keep bugging me about it.

3 Likes