SDL: Send key release event to input method. (#5281)

From 138d96c8a6a3e3b19e081c450b834d1c3a4121e3 Mon Sep 17 00:00:00 2001
From: Weng Xuetian <[EMAIL REDACTED]>
Date: Tue, 5 Apr 2022 19:30:25 -0700
Subject: [PATCH] Send key release event to input method. (#5281)

Co-authored-by: Ethan Lee <flibitijibibo@gmail.com>
---
 src/core/linux/SDL_fcitx.c            |  8 ++---
 src/core/linux/SDL_fcitx.h            |  2 +-
 src/core/linux/SDL_ibus.c             |  8 +++--
 src/core/linux/SDL_ibus.h             |  2 +-
 src/core/linux/SDL_ime.c              |  6 ++--
 src/core/linux/SDL_ime.h              |  2 +-
 src/video/wayland/SDL_waylandevents.c | 11 ++++--
 src/video/x11/SDL_x11events.c         | 48 ++++++++++++---------------
 8 files changed, 46 insertions(+), 41 deletions(-)

diff --git a/src/core/linux/SDL_fcitx.c b/src/core/linux/SDL_fcitx.c
index 3f0794736ab..bab5e88f350 100644
--- a/src/core/linux/SDL_fcitx.c
+++ b/src/core/linux/SDL_fcitx.c
@@ -339,11 +339,11 @@ SDL_Fcitx_Reset(void)
 }
 
 SDL_bool
-SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
+SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state)
 {
-    Uint32 state = Fcitx_ModState();
+    Uint32 mod_state = Fcitx_ModState();
     Uint32 handled = SDL_FALSE;
-    Uint32 is_release = SDL_FALSE;
+    Uint32 is_release = (state == SDL_RELEASED);
     Uint32 event_time = 0;
 
     if (!fcitx_client.ic_path) {
@@ -351,7 +351,7 @@ SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
     }
 
     if (SDL_DBus_CallMethod(FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "ProcessKeyEvent",
-            DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &state, DBUS_TYPE_BOOLEAN, &is_release, DBUS_TYPE_UINT32, &event_time, DBUS_TYPE_INVALID,
+            DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &mod_state, DBUS_TYPE_BOOLEAN, &is_release, DBUS_TYPE_UINT32, &event_time, DBUS_TYPE_INVALID,
             DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID)) {
         if (handled) {
             SDL_Fcitx_UpdateTextRect(NULL);
diff --git a/src/core/linux/SDL_fcitx.h b/src/core/linux/SDL_fcitx.h
index f7884ea4341..c3cea523d52 100644
--- a/src/core/linux/SDL_fcitx.h
+++ b/src/core/linux/SDL_fcitx.h
@@ -31,7 +31,7 @@ extern SDL_bool SDL_Fcitx_Init(void);
 extern void SDL_Fcitx_Quit(void);
 extern void SDL_Fcitx_SetFocus(SDL_bool focused);
 extern void SDL_Fcitx_Reset(void);
-extern SDL_bool SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode);
+extern SDL_bool SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state);
 extern void SDL_Fcitx_UpdateTextRect(SDL_Rect *rect);
 extern void SDL_Fcitx_PumpEvents(void);
 
diff --git a/src/core/linux/SDL_ibus.c b/src/core/linux/SDL_ibus.c
index 20236e71387..60af0ba2925 100644
--- a/src/core/linux/SDL_ibus.c
+++ b/src/core/linux/SDL_ibus.c
@@ -503,14 +503,18 @@ SDL_IBus_Reset(void)
 }
 
 SDL_bool
-SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
+SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state)
 { 
     Uint32 result = 0;
     SDL_DBusContext *dbus = SDL_DBus_GetContext();
-    
+
+
     if (IBus_CheckConnection(dbus)) {
         Uint32 mods = IBus_ModState();
         Uint32 ibus_keycode = keycode - 8;
+        if (state == SDL_RELEASED) {
+            mods |= (1 << 30); // IBUS_RELEASE_MASK
+        }
         if (!SDL_DBus_CallMethodOnConnection(ibus_conn, IBUS_SERVICE, input_ctx_path, IBUS_INPUT_INTERFACE, "ProcessKeyEvent",
                 DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &ibus_keycode, DBUS_TYPE_UINT32, &mods, DBUS_TYPE_INVALID,
                 DBUS_TYPE_BOOLEAN, &result, DBUS_TYPE_INVALID)) {
diff --git a/src/core/linux/SDL_ibus.h b/src/core/linux/SDL_ibus.h
index 73a9f1b8b65..71d1f2d3015 100644
--- a/src/core/linux/SDL_ibus.h
+++ b/src/core/linux/SDL_ibus.h
@@ -41,7 +41,7 @@ extern void SDL_IBus_Reset(void);
 /* Sends a keypress event to IBus, returns SDL_TRUE if IBus used this event to
    update its candidate list or change input methods. PumpEvents should be
    called some time after this, to recieve the TextInput / TextEditing event back. */
-extern SDL_bool SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode);
+extern SDL_bool SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state);
 
 /* Update the position of IBus' candidate list. If rect is NULL then this will 
    just reposition it relative to the focused window's new position. */
diff --git a/src/core/linux/SDL_ime.c b/src/core/linux/SDL_ime.c
index 84c461f8b7e..9c0cb80f0fe 100644
--- a/src/core/linux/SDL_ime.c
+++ b/src/core/linux/SDL_ime.c
@@ -27,7 +27,7 @@ typedef SDL_bool (*_SDL_IME_Init)(void);
 typedef void (*_SDL_IME_Quit)(void);
 typedef void (*_SDL_IME_SetFocus)(SDL_bool);
 typedef void (*_SDL_IME_Reset)(void);
-typedef SDL_bool (*_SDL_IME_ProcessKeyEvent)(Uint32, Uint32);
+typedef SDL_bool (*_SDL_IME_ProcessKeyEvent)(Uint32, Uint32, Uint8 state);
 typedef void (*_SDL_IME_UpdateTextRect)(SDL_Rect *);
 typedef void (*_SDL_IME_PumpEvents)(void);
 
@@ -127,10 +127,10 @@ SDL_IME_Reset(void)
 }
 
 SDL_bool
-SDL_IME_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
+SDL_IME_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state)
 {
     if (SDL_IME_ProcessKeyEvent_Real)
-        return SDL_IME_ProcessKeyEvent_Real(keysym, keycode);
+        return SDL_IME_ProcessKeyEvent_Real(keysym, keycode, state);
 
     return SDL_FALSE;
 }
diff --git a/src/core/linux/SDL_ime.h b/src/core/linux/SDL_ime.h
index a28a4b43082..cc5b105f0ab 100644
--- a/src/core/linux/SDL_ime.h
+++ b/src/core/linux/SDL_ime.h
@@ -31,7 +31,7 @@ extern SDL_bool SDL_IME_Init(void);
 extern void SDL_IME_Quit(void);
 extern void SDL_IME_SetFocus(SDL_bool focused);
 extern void SDL_IME_Reset(void);
-extern SDL_bool SDL_IME_ProcessKeyEvent(Uint32 keysym, Uint32 keycode);
+extern SDL_bool SDL_IME_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state);
 extern void SDL_IME_UpdateTextRect(SDL_Rect *rect);
 extern void SDL_IME_PumpEvents(void);
 
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index f4d6ade2704..b81c9db4a2e 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -921,7 +921,7 @@ keyboard_handle_leave(void *data, struct wl_keyboard *keyboard,
 }
 
 static SDL_bool
-keyboard_input_get_text(char text[8], const struct SDL_WaylandInput *input, uint32_t key, SDL_bool *handled_by_ime)
+keyboard_input_get_text(char text[8], const struct SDL_WaylandInput *input, uint32_t key, Uint8 state, SDL_bool *handled_by_ime)
 {
     SDL_WindowData *window = input->keyboard_focus;
     const xkb_keysym_t *syms;
@@ -938,12 +938,16 @@ keyboard_input_get_text(char text[8], const struct SDL_WaylandInput *input, uint
     sym = syms[0];
 
 #ifdef SDL_USE_IME
-    if (SDL_IME_ProcessKeyEvent(sym, key + 8)) {
+    if (SDL_IME_ProcessKeyEvent(sym, key + 8, state)) {
         *handled_by_ime = SDL_TRUE;
         return SDL_TRUE;
     }
 #endif
 
+    if (state == SDL_RELEASED) {
+        return SDL_FALSE;
+    }
+
     if (input->xkb.compose_state && WAYLAND_xkb_compose_state_feed(input->xkb.compose_state, sym) == XKB_COMPOSE_FEED_ACCEPTED) {
         switch(WAYLAND_xkb_compose_state_get_status(input->xkb.compose_state)) {
             case XKB_COMPOSE_COMPOSING:
@@ -977,7 +981,7 @@ keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
     SDL_bool handled_by_ime = SDL_FALSE;
 
     if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
-        has_text = keyboard_input_get_text(text, input, key, &handled_by_ime);
+        has_text = keyboard_input_get_text(text, input, key, SDL_PRESSED, &handled_by_ime);
     } else {
         if (keyboard_repeat_is_set(&input->keyboard_repeat)) {
             // Send any due key repeat events before stopping the repeat and generating the key up event
@@ -987,6 +991,7 @@ keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
             keyboard_repeat_handle(&input->keyboard_repeat, time - input->keyboard_repeat.wl_press_time);
             keyboard_repeat_clear(&input->keyboard_repeat);
         }
+        keyboard_input_get_text(text, input, key, SDL_RELEASED, &handled_by_ime);
     }
 
     if (!handled_by_ime && key < SDL_arraysize(xfree86_scancode_table2)) {
diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c
index 186f37b05e6..31e739c7047 100644
--- a/src/video/x11/SDL_x11events.c
+++ b/src/video/x11/SDL_x11events.c
@@ -996,8 +996,9 @@ X11_DispatchEvent(_THIS, XEvent *xevent)
         }
         break;
 
-        /* Key press? */
-    case KeyPress:{
+        /* Key press/release? */
+    case KeyPress:
+    case KeyRelease: {
             KeyCode keycode = xevent->xkey.keycode;
             KeySym keysym = NoSymbol;
             char text[SDL_TEXTINPUTEVENT_TEXT_SIZE];
@@ -1005,7 +1006,7 @@ X11_DispatchEvent(_THIS, XEvent *xevent)
             SDL_bool handled_by_ime = SDL_FALSE;
 
 #ifdef DEBUG_XEVENTS
-            printf("window %p: KeyPress (X11 keycode = 0x%X)\n", data, xevent->xkey.keycode);
+            printf("window %p: %s (X11 keycode = 0x%X)\n" data, (xevent->type == KeyPress ? "KeyPress" : "KeyRelease"),  xevent->xkey.keycode);
 #endif
 #if 1
             if (videodata->key_layout[keycode] == SDL_SCANCODE_UNKNOWN && keycode) {
@@ -1021,7 +1022,7 @@ X11_DispatchEvent(_THIS, XEvent *xevent)
             /* */
             SDL_zeroa(text);
 #ifdef X_HAVE_UTF8_STRING
-            if (data->ic) {
+            if (data->ic && xevent->type == KeyPress) {
                 X11_Xutf8LookupString(data->ic, &xevent->xkey, text, sizeof(text),
                                   &keysym, &status);
             } else {
@@ -1033,35 +1034,30 @@ X11_DispatchEvent(_THIS, XEvent *xevent)
 
 #ifdef SDL_USE_IME
             if(SDL_GetEventState(SDL_TEXTINPUT) == SDL_ENABLE){
-                handled_by_ime = SDL_IME_ProcessKeyEvent(keysym, keycode);
+                handled_by_ime = SDL_IME_ProcessKeyEvent(keysym, keycode, (xevent->type == KeyPress ? SDL_PRESSED : SDL_RELEASED));
             }
 #endif
             if (!handled_by_ime) {
-                /* Don't send the key if it looks like a duplicate of a filtered key sent by an IME */
-                if (xevent->xkey.keycode != videodata->filter_code || xevent->xkey.time != videodata->filter_time) {
-                    SDL_SendKeyboardKey(SDL_PRESSED, videodata->key_layout[keycode]);
-                }
-                if(*text) {
-                    SDL_SendKeyboardText(text);
+                if (xevent->type == KeyPress) {
+                    /* Don't send the key if it looks like a duplicate of a filtered key sent by an IME */
+                    if (xevent->xkey.keycode != videodata->filter_code || xevent->xkey.time != videodata->filter_time) {
+                        SDL_SendKeyboardKey(SDL_PRESSED, videodata->key_layout[keycode]);
+                    }
+                    if(*text) {
+                        SDL_SendKeyboardText(text);
+                    }
+                } else {
+                    if (X11_KeyRepeat(display, xevent)) {
+                        /* We're about to get a repeated key down, ignore the key up */
+                        break;
+                    }
+                    SDL_SendKeyboardKey(SDL_RELEASED, videodata->key_layout[keycode]);
                 }
             }
 
-            X11_UpdateUserTime(data, xevent->xkey.time);
-        }
-        break;
-
-        /* Key release? */
-    case KeyRelease:{
-            KeyCode keycode = xevent->xkey.keycode;
-
-#ifdef DEBUG_XEVENTS
-            printf("window %p: KeyRelease (X11 keycode = 0x%X)\n", data, xevent->xkey.keycode);
-#endif
-            if (X11_KeyRepeat(display, xevent)) {
-                /* We're about to get a repeated key down, ignore the key up */
-                break;
+            if (xevent->type == KeyPress) {
+                X11_UpdateUserTime(data, xevent->xkey.time);
             }
-            SDL_SendKeyboardKey(SDL_RELEASED, videodata->key_layout[keycode]);
         }
         break;