Use XIM for IME input on X11

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
This commit is contained in:
Sam Lantinga 2025-01-09 16:37:11 -08:00
parent 1f3b40797d
commit a8a65b6fca
2 changed files with 22 additions and 103 deletions

View file

@ -438,9 +438,6 @@ static void X11_DispatchFocusIn(SDL_VideoDevice *_this, SDL_WindowData *data)
if (data->ic) { if (data->ic) {
X11_XSetICFocus(data->ic); X11_XSetICFocus(data->ic);
} }
#endif
#ifdef SDL_USE_IME
SDL_IME_SetFocus(true);
#endif #endif
if (data->flashing_window) { if (data->flashing_window) {
X11_FlashWindow(_this, data->window, SDL_FLASH_CANCEL); 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); X11_XUnsetICFocus(data->ic);
} }
#endif #endif
#ifdef SDL_USE_IME
SDL_IME_SetFocus(false);
#endif
} }
static void X11_DispatchMapNotify(SDL_WindowData *data) static void X11_DispatchMapNotify(SDL_WindowData *data)
@ -893,13 +887,15 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_
char text[64]; char text[64];
Status status = 0; Status status = 0;
bool handled_by_ime = false; bool handled_by_ime = false;
bool pressed = (xevent->type == KeyPress);
SDL_Scancode scancode = videodata->key_layout[keycode];
Uint64 timestamp = X11_GetEventTimestamp(xevent->xkey.time); Uint64 timestamp = X11_GetEventTimestamp(xevent->xkey.time);
#ifdef DEBUG_XEVENTS #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); SDL_Log("window 0x%lx %s (X11 keycode = 0x%X)\n", xevent->xany.window, (xevent->type == KeyPress ? "KeyPress" : "KeyRelease"), xevent->xkey.keycode);
#endif #endif
#ifdef DEBUG_SCANCODES #ifdef DEBUG_SCANCODES
if (videodata->key_layout[keycode] == SDL_SCANCODE_UNKNOWN && keycode) { if (scancode == SDL_SCANCODE_UNKNOWN && keycode) {
int min_keycode, max_keycode; int min_keycode, max_keycode;
X11_XDisplayKeycodes(display, &min_keycode, &max_keycode); X11_XDisplayKeycodes(display, &min_keycode, &max_keycode);
keysym = X11_KeyCodeToSym(_this, keycode, xevent->xkey.state >> 13); keysym = X11_KeyCodeToSym(_this, keycode, xevent->xkey.state >> 13);
@ -913,38 +909,16 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_
X11_UpdateSystemKeyModifiers(videodata); X11_UpdateSystemKeyModifiers(videodata);
if (SDL_TextInputActive(windowdata->window)) { 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 // filter events catches XIM events and sends them to the correct handler
if (X11_XFilterEvent(xevent, None)) { if (X11_XFilterEvent(xevent, None)) {
#ifdef DEBUG_XEVENTS #ifdef DEBUG_XEVENTS
SDL_Log("Filtered event type = %d display = %p window = 0x%lx\n", SDL_Log("Filtered event type = %d display = %p window = 0x%lx\n",
xevent->type, xevent->xany.display, xevent->xany.window); xevent->type, xevent->xany.display, xevent->xany.window);
#endif #endif
// Make sure dead key press/release events are sent handled_by_ime = true;
/* 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;
} }
if (!handled_by_ime) {
#ifdef X_HAVE_UTF8_STRING #ifdef X_HAVE_UTF8_STRING
if (windowdata->ic && xevent->type == KeyPress) { if (windowdata->ic && xevent->type == KeyPress) {
text_length = X11_Xutf8LookupString(windowdata->ic, &xevent->xkey, text, sizeof(text) - 1, text_length = X11_Xutf8LookupString(windowdata->ic, &xevent->xkey, text, sizeof(text) - 1,
@ -955,19 +929,14 @@ void X11_HandleKeyEvent(SDL_VideoDevice *_this, SDL_WindowData *windowdata, SDL_
#else #else
text_length = XLookupStringAsUTF8(&xevent->xkey, text, sizeof(text) - 1, &keysym, NULL); text_length = XLookupStringAsUTF8(&xevent->xkey, text, sizeof(text) - 1, &keysym, NULL);
#endif #endif
}
#ifdef SDL_USE_IME
handled_by_ime = SDL_IME_ProcessKeyEvent(keysym, keycode, (xevent->type == KeyPress));
#endif
} }
if (!handled_by_ime) { if (!handled_by_ime) {
if (xevent->type == KeyPress) { if (pressed) {
// Don't send the key if it looks like a duplicate of a filtered key sent by an IME X11_HandleModifierKeys(videodata, scancode, true, true);
if (xevent->xkey.keycode != videodata->filter_code || xevent->xkey.time != videodata->filter_time) { SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, scancode, true);
X11_HandleModifierKeys(videodata, videodata->key_layout[keycode], true, true);
SDL_SendKeyboardKeyIgnoreModifiers(timestamp, keyboardID, keycode, videodata->key_layout[keycode], true);
}
if (*text) { if (*text) {
text[text_length] = '\0'; text[text_length] = '\0';
SDL_SendKeyboardText(text); 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 // We're about to get a repeated key down, ignore the key up
return; 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); 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_GlobalToRelativeForWindow(data->window, x, y, &x, &y);
SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, 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) { for (w = data->window->first_child; w; w = w->next_sibling) {
// Don't update hidden child popup windows, their relative position doesn't change // Don't update hidden child popup windows, their relative position doesn't change
if (SDL_WINDOW_IS_POPUP(w) && !(w->flags & SDL_WINDOW_HIDDEN)) { 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); 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 #ifdef SDL_USE_LIBDBUS
SDL_DBus_PumpEvents(); SDL_DBus_PumpEvents();
#endif #endif
@ -2170,13 +2127,6 @@ void X11_PumpEvents(SDL_VideoDevice *_this)
X11_DispatchEvent(_this, &xevent); 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 #ifdef SDL_USE_LIBDBUS
SDL_DBus_PumpEvents(); SDL_DBus_PumpEvents();
#endif #endif

View file

@ -169,9 +169,6 @@ bool X11_InitKeyboard(SDL_VideoDevice *_this)
Compose keys will work correctly. */ Compose keys will work correctly. */
char *prev_locale = setlocale(LC_ALL, NULL); char *prev_locale = setlocale(LC_ALL, NULL);
char *prev_xmods = X11_XSetLocaleModifiers(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) { if (prev_locale) {
prev_locale = SDL_strdup(prev_locale); prev_locale = SDL_strdup(prev_locale);
@ -181,22 +178,8 @@ bool X11_InitKeyboard(SDL_VideoDevice *_this)
prev_xmods = SDL_strdup(prev_xmods); 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, ""); (void)setlocale(LC_ALL, "");
X11_XSetLocaleModifiers(new_xmods); X11_XSetLocaleModifiers("");
data->im = X11_XOpenIM(data->display, NULL, NULL, NULL); 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"); SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu");
#ifdef SDL_USE_IME
SDL_IME_Init();
#endif
X11_ReconcileKeyboardState(_this); X11_ReconcileKeyboardState(_this);
return true; return true;
@ -470,10 +449,6 @@ void X11_QuitKeyboard(SDL_VideoDevice *_this)
data->xkb.desc_ptr = NULL; data->xkb.desc_ptr = NULL;
} }
#endif #endif
#ifdef SDL_USE_IME
SDL_IME_Quit();
#endif
} }
static void X11_ResetXIM(SDL_VideoDevice *_this, SDL_Window *window) 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) bool X11_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window)
{ {
X11_ResetXIM(_this, window); X11_ResetXIM(_this, window);
#ifdef SDL_USE_IME
SDL_IME_Reset();
#endif
return true; return true;
} }
bool X11_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window) bool X11_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window)
{ {
#ifdef SDL_USE_IME
SDL_IME_UpdateTextInputArea(window);
#endif
return true; return true;
} }