SDL: Use XIM for IME input on X11

https://github.com/libsdl-org/SDL/commit/a8a65b6fca6b9d12defcc0aa334071c30f2650a2

From a8a65b6fca6b9d12defcc0aa334071c30f2650a2 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 9 Jan 2025 16:37:11 -0800
Subject: [PATCH] Use XIM for IME input on X11
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Tested with fcitx5 and ibus on Xorg and Xwayland
* Used US English with dead keys and verified that ` followed by a results in à
* Used Hangul to enter Korean and got text in the expected order
* Used the mozc IM to enter Japanese and was able to generate candidates and so forth

Fixes https://github.com/libsdl-org/SDL/issues/3907
Fixes https://github.com/libsdl-org/SDL/issues/6164
Fixes https://github.com/libsdl-org/SDL/issues/11894
---
 src/video/x11/SDL_x11events.c   | 92 ++++++++-------------------------
 src/video/x11/SDL_x11keyboard.c | 33 +-----------
 2 files changed, 22 insertions(+), 103 deletions(-)

diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c
index 3641cf9c35db0..95f2dc21970f8 100644
--- a/src/video/x11/SDL_x11events.c
+++ b/src/video/x11/SDL_x11events.c
@@ -438,9 +438,6 @@ static void X11_DispatchFocusIn(SDL_VideoDevice *_this, SDL_WindowData *data)
     if (data->ic) {
         X11_XSetICFocus(data->ic);
     }
-#endif
-#ifdef SDL_USE_IME
-    SDL_IME_SetFocus(true);
 #endif
     if (data->flashing_window) {
         X11_FlashWindow(_this, data->window, SDL_FLASH_CANCEL);
@@ -464,9 +461,6 @@ static void X11_DispatchFocusOut(SDL_VideoDevice *_this, SDL_WindowData *data)
         X11_XUnsetICFocus(data->ic);
     }
 #endif
-#ifdef SDL_USE_IME
-    SDL_IME_SetFocus(false);
-#endif
 }
 
 static void X11_DispatchMapNotify(SDL_WindowData *data)
@@ -893,13 +887,15 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_
     char text[64];
     Status status = 0;
     bool handled_by_ime = false;
+    bool pressed = (xevent->type == KeyPress);
+    SDL_Scancode scancode = videodata->key_layout[keycode];
     Uint64 timestamp = X11_GetEventTimestamp(xevent->xkey.time);
 
 #ifdef DEBUG_XEVENTS
     SDL_Log("window 0x%lx %s (X11 keycode = 0x%X)\n", xevent->xany.window, (xevent->type == KeyPress ? "KeyPress" : "KeyRelease"), xevent->xkey.keycode);
 #endif
 #ifdef DEBUG_SCANCODES
-    if (videodata->key_layout[keycode] == SDL_SCANCODE_UNKNOWN && keycode) {
+    if (scancode == SDL_SCANCODE_UNKNOWN && keycode) {
         int min_keycode, max_keycode;
         X11_XDisplayKeycodes(display, &min_keycode, &max_keycode);
         keysym = X11_KeyCodeToSym(_this, keycode, xevent->xkey.state >> 13);
@@ -913,61 +909,34 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_
     X11_UpdateSystemKeyModifiers(videodata);
 
     if (SDL_TextInputActive(windowdata->window)) {
-#if defined(HAVE_IBUS_IBUS_H) || defined(HAVE_FCITX)
-        /* Save the original keycode for dead keys, which are filtered out by
-           the XFilterEvent() call below.
-        */
-        int orig_event_type = xevent->type;
-        KeyCode orig_keycode = xevent->xkey.keycode;
-#endif
-
         // filter events catches XIM events and sends them to the correct handler
         if (X11_XFilterEvent(xevent, None)) {
 #ifdef DEBUG_XEVENTS
             SDL_Log("Filtered event type = %d display = %p window = 0x%lx\n",
                    xevent->type, xevent->xany.display, xevent->xany.window);
 #endif
-            // Make sure dead key press/release events are sent
-            /* But only if we're using one of the DBus IMEs, otherwise
-               some XIM IMEs will generate duplicate events */
-#if defined(HAVE_IBUS_IBUS_H) || defined(HAVE_FCITX)
-            SDL_Scancode scancode = videodata->key_layout[orig_keycode];
-            videodata->filter_code = orig_keycode;
-            videodata->filter_time = xevent->xkey.time;
-            if (orig_event_type == KeyPress) {
-                X11_HandleModifierKeys(videodata, scancode, true, true);
-                SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, orig_keycode, scancode, true);
-            } else {
-                X11_HandleModifierKeys(videodata, scancode, false, true);
-                SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, orig_keycode, scancode, false);
-            }
-#endif
-            return;
+            handled_by_ime = true;
         }
 
+        if (!handled_by_ime) {
 #ifdef X_HAVE_UTF8_STRING
-        if (windowdata->ic && xevent->type == KeyPress) {
-            text_length = X11_Xutf8LookupString(windowdata->ic, &xevent->xkey, text, sizeof(text) - 1,
-                                  &keysym, &status);
-        } else {
-            text_length = XLookupStringAsUTF8(&xevent->xkey, text, sizeof(text) - 1, &keysym, NULL);
-        }
+            if (windowdata->ic && xevent->type == KeyPress) {
+                text_length = X11_Xutf8LookupString(windowdata->ic, &xevent->xkey, text, sizeof(text) - 1,
+                                      &keysym, &status);
+            } else {
+                text_length = XLookupStringAsUTF8(&xevent->xkey, text, sizeof(text) - 1, &keysym, NULL);
+            }
 #else
-        text_length = XLookupStringAsUTF8(&xevent->xkey, text, sizeof(text) - 1, &keysym, NULL);
-#endif
-
-#ifdef SDL_USE_IME
-        handled_by_ime = SDL_IME_ProcessKeyEvent(keysym, keycode, (xevent->type == KeyPress));
+            text_length = XLookupStringAsUTF8(&xevent->xkey, text, sizeof(text) - 1, &keysym, NULL);
 #endif
+        }
     }
 
     if (!handled_by_ime) {
-        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) {
-                X11_HandleModifierKeys(videodata, videodata->key_layout[keycode], true, true);
-                SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, videodata->key_layout[keycode], true);
-            }
+        if (pressed) {
+            X11_HandleModifierKeys(videodata, scancode, true, true);
+            SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true);
+
             if (*text) {
                 text[text_length] = '\0';
                 SDL_SendKeyboardText(text);
@@ -977,12 +946,13 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_
                 // We're about to get a repeated key down, ignore the key up
                 return;
             }
-            X11_HandleModifierKeys(videodata, videodata->key_layout[keycode], false, true);
-            SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, videodata->key_layout[keycode], false);
+
+            X11_HandleModifierKeys(videodata, scancode, false, true);
+            SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, false);
         }
     }
 
-    if (xevent->type == KeyPress) {
+    if (pressed) {
         X11_UpdateUserTime(windowdata, xevent->xkey.time);
     }
 }
@@ -1462,12 +1432,6 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
                 SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y);
                 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y);
 
-#ifdef SDL_USE_IME
-                if (SDL_TextInputActive(data->window)) {
-                    // Update IME candidate list position
-                    SDL_IME_UpdateTextInputArea(NULL);
-                }
-#endif
                 for (w = data->window->first_child; w; w = w->next_sibling) {
                     // Don't update hidden child popup windows, their relative position doesn't change
                     if (SDL_WINDOW_IS_POPUP(w) && !(w->flags & SDL_WINDOW_HIDDEN)) {
@@ -2109,13 +2073,6 @@ int X11_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS)
 
     X11_DispatchEvent(_this, &xevent);
 
-#ifdef SDL_USE_IME
-    SDL_Window *keyboard_focus = SDL_GetKeyboardFocus();
-    if (keyboard_focus && SDL_TextInputActive(keyboard_focus)) {
-        SDL_IME_PumpEvents();
-    }
-#endif
-
 #ifdef SDL_USE_LIBDBUS
     SDL_DBus_PumpEvents();
 #endif
@@ -2170,13 +2127,6 @@ void X11_PumpEvents(SDL_VideoDevice *_this)
         X11_DispatchEvent(_this, &xevent);
     }
 
-#ifdef SDL_USE_IME
-    SDL_Window *keyboard_focus = SDL_GetKeyboardFocus();
-    if (keyboard_focus && SDL_TextInputActive(keyboard_focus)) {
-        SDL_IME_PumpEvents();
-    }
-#endif
-
 #ifdef SDL_USE_LIBDBUS
     SDL_DBus_PumpEvents();
 #endif
diff --git a/src/video/x11/SDL_x11keyboard.c b/src/video/x11/SDL_x11keyboard.c
index d1d60f30c546f..2d25ec2de51c8 100644
--- a/src/video/x11/SDL_x11keyboard.c
+++ b/src/video/x11/SDL_x11keyboard.c
@@ -169,9 +169,6 @@ bool X11_InitKeyboard(SDL_VideoDevice *_this)
            Compose keys will work correctly. */
         char *prev_locale = setlocale(LC_ALL, NULL);
         char *prev_xmods = X11_XSetLocaleModifiers(NULL);
-        const char *new_xmods = "";
-        const char *env_xmods = SDL_getenv("XMODIFIERS");
-        bool has_dbus_ime_support = false;
 
         if (prev_locale) {
             prev_locale = SDL_strdup(prev_locale);
@@ -181,22 +178,8 @@ bool X11_InitKeyboard(SDL_VideoDevice *_this)
             prev_xmods = SDL_strdup(prev_xmods);
         }
 
-        /* IBus resends some key events that were filtered by XFilterEvents
-           when it is used via XIM which causes issues. Prevent this by forcing
-           @im=none if XMODIFIERS contains @im=ibus. IBus can still be used via
-           the DBus implementation, which also has support for pre-editing. */
-        if (env_xmods && SDL_strstr(env_xmods, "@im=ibus") != NULL) {
-            has_dbus_ime_support = true;
-        }
-        if (env_xmods && SDL_strstr(env_xmods, "@im=fcitx") != NULL) {
-            has_dbus_ime_support = true;
-        }
-        if (has_dbus_ime_support || !xkb_repeat) {
-            new_xmods = "@im=none";
-        }
-
         (void)setlocale(LC_ALL, "");
-        X11_XSetLocaleModifiers(new_xmods);
+        X11_XSetLocaleModifiers("");
 
         data->im = X11_XOpenIM(data->display, NULL, NULL, NULL);
 
@@ -318,10 +301,6 @@ bool X11_InitKeyboard(SDL_VideoDevice *_this)
 
     SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu");
 
-#ifdef SDL_USE_IME
-    SDL_IME_Init();
-#endif
-
     X11_ReconcileKeyboardState(_this);
 
     return true;
@@ -470,10 +449,6 @@ void X11_QuitKeyboard(SDL_VideoDevice *_this)
         data->xkb.desc_ptr = NULL;
     }
 #endif
-
-#ifdef SDL_USE_IME
-    SDL_IME_Quit();
-#endif
 }
 
 static void X11_ResetXIM(SDL_VideoDevice *_this, SDL_Window *window)
@@ -501,17 +476,11 @@ bool X11_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_Properti
 bool X11_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window)
 {
     X11_ResetXIM(_this, window);
-#ifdef SDL_USE_IME
-    SDL_IME_Reset();
-#endif
     return true;
 }
 
 bool X11_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window)
 {
-#ifdef SDL_USE_IME
-    SDL_IME_UpdateTextInputArea(window);
-#endif
     return true;
 }