From 098a888ec0594383bdc16c27ea6ecc1b29ecd81b Mon Sep 17 00:00:00 2001 From: Jia Wang Date: Tue, 27 Jun 2017 17:26:43 +0800 Subject: [PATCH] Workaround for IME and echo events on Linux: Request detectable auto-repeat (Require XKB extension) to support echo events and IME at the same time. Fixes #29, #7106 and #9381. --- platform/x11/godot_x11.cpp | 3 + platform/x11/os_x11.cpp | 172 +++++++++++++++++++++++++++++++++++-- platform/x11/os_x11.h | 4 + 3 files changed, 170 insertions(+), 9 deletions(-) diff --git a/platform/x11/godot_x11.cpp b/platform/x11/godot_x11.cpp index b293b1bebb..6f418b213f 100644 --- a/platform/x11/godot_x11.cpp +++ b/platform/x11/godot_x11.cpp @@ -28,6 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include +#include #include #include @@ -38,6 +39,8 @@ int main(int argc, char *argv[]) { OS_X11 os; + setlocale(LC_CTYPE, ""); + char *cwd = (char *)malloc(PATH_MAX); getcwd(cwd, PATH_MAX); diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 2eebc96d2c..383abffe46 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -68,6 +68,8 @@ #undef CursorShape +#include + int OS_X11::get_video_driver_count() const { return 1; } @@ -93,6 +95,7 @@ const char *OS_X11::get_audio_driver_name(int p_driver) const { void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { + long im_event_mask = 0; last_button_state = 0; xmbstring = NULL; @@ -113,7 +116,32 @@ void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_au /** XLIB INITIALIZATION **/ x11_display = XOpenDisplay(NULL); - char *modifiers = XSetLocaleModifiers("@im=none"); + Bool xkb_dar = False; + if (x11_display) { + XAutoRepeatOn(x11_display); + xkb_dar = XkbSetDetectableAutoRepeat(x11_display, True, NULL); + } + + char *modifiers = NULL; + + // Try to support IME if detectable auto-repeat is supported + + if (xkb_dar == True) { + +// Xutf8LookupString will be used later instead of XmbLookupString before +// the multibyte sequences can be converted to unicode string. + +#ifdef X_HAVE_UTF8_STRING + modifiers = XSetLocaleModifiers(""); +#endif + } + + if (modifiers == NULL) { + if (is_stdout_verbose()) { + WARN_PRINT("IME is disabled"); + } + modifiers = XSetLocaleModifiers("@im=none"); + } if (modifiers == NULL) { WARN_PRINT("Error setting locale modifiers"); } @@ -153,6 +181,14 @@ void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_au WARN_PRINT("XOpenIM failed"); xim_style = 0L; } else { + ::XIMCallback im_destroy_callback; + im_destroy_callback.client_data = (::XPointer)(this); + im_destroy_callback.callback = (::XIMProc)(xim_destroy_callback); + if (XSetIMValues(xim, XNDestroyCallback, &im_destroy_callback, + NULL) != NULL) { + WARN_PRINT("Error setting XIM destroy callback"); + } + ::XIMStyles *xim_styles = NULL; xim_style = 0L; char *imvalret = NULL; @@ -303,7 +339,8 @@ void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_au StructureNotifyMask | SubstructureNotifyMask | SubstructureRedirectMask | FocusChangeMask | PropertyChangeMask | - ColormapChangeMask | OwnerGrabButtonMask; + ColormapChangeMask | OwnerGrabButtonMask | + im_event_mask; XChangeWindowAttributes(x11_display, x11_window, CWEventMask, &new_attr); @@ -327,6 +364,16 @@ void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_au if (xim && xim_style) { xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, x11_window, XNFocusWindow, x11_window, (char *)NULL); + if (XGetICValues(xic, XNFilterEvents, &im_event_mask, NULL) != NULL) { + WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value"); + XDestroyIC(xic); + xic = NULL; + } + if (xic) { + XSetICFocus(xic); + } else { + WARN_PRINT("XCreateIC couldn't create xic"); + } } else { xic = NULL; @@ -445,6 +492,33 @@ void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_au _ensure_data_dir(); } +void OS_X11::xim_destroy_callback(::XIM im, ::XPointer client_data, + ::XPointer call_data) { + + WARN_PRINT("Input method stopped"); + OS_X11 *os = reinterpret_cast(client_data); + os->xim = NULL; + os->xic = NULL; +} + +void OS_X11::set_ime_position(short x, short y) { + + if (!xic) { + return; + } + ::XPoint spot; + spot.x = x; + spot.y = y; + XVaNestedList preedit_attr = XVaCreateNestedList(0, + XNSpotLocation, &spot, + NULL); + XSetICValues(xic, + XNPreeditAttributes, preedit_attr, + NULL); + XFree(preedit_attr); + return; +} + void OS_X11::finalize() { if (main_loop) @@ -492,8 +566,12 @@ void OS_X11::finalize() { XcursorImageDestroy(img[i]); }; - XDestroyIC(xic); - XCloseIM(xim); + if (xic) { + XDestroyIC(xic); + } + if (xim) { + XCloseIM(xim); + } XCloseDisplay(x11_display); if (xmbstring) @@ -1041,9 +1119,61 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { xmblen = 8; } + keysym_unicode = keysym_keycode; + if (xkeyevent->type == KeyPress && xic) { Status status; +#ifdef X_HAVE_UTF8_STRING + int utf8len = 8; + char *utf8string = (char *)memalloc(sizeof(char) * utf8len); + int utf8bytes = Xutf8LookupString(xic, xkeyevent, utf8string, + utf8len - 1, &keysym_unicode, &status); + if (status == XBufferOverflow) { + utf8len = utf8bytes + 1; + utf8string = (char *)memrealloc(utf8string, utf8len); + utf8bytes = Xutf8LookupString(xic, xkeyevent, utf8string, + utf8len - 1, &keysym_unicode, &status); + } + utf8string[utf8bytes] = '\0'; + + if (status == XLookupChars) { + bool keypress = xkeyevent->type == KeyPress; + unsigned int keycode = KeyMappingX11::get_keycode(keysym_keycode); + if (keycode >= 'a' && keycode <= 'z') + keycode -= 'a' - 'A'; + + String tmp; + tmp.parse_utf8(utf8string, utf8bytes); + for (int i = 0; i < tmp.length(); i++) { + Ref k; + k.instance(); + if (keycode == 0 && tmp[i] == 0) { + continue; + } + + get_key_modifier_state(xkeyevent->state, k); + + k->set_unicode(tmp[i]); + + k->set_pressed(keypress); + + k->set_scancode(keycode); + + k->set_echo(false); + + if (k->get_scancode() == KEY_BACKTAB) { + //make it consistent across platforms. + k->set_scancode(KEY_TAB); + k->set_shift(true); + } + + input->parse_input_event(k); + } + return; + } + memfree(utf8string); +#else do { int mnbytes = XmbLookupString(xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status); @@ -1054,6 +1184,7 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { xmbstring = (char *)memrealloc(xmbstring, xmblen); } } while (status == XBufferOverflow); +#endif } /* Phase 2, obtain a pigui keycode from the keysym */ @@ -1082,11 +1213,6 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { bool keypress = xkeyevent->type == KeyPress; - if (xkeyevent->type == KeyPress && xic) { - if (XFilterEvent((XEvent *)xkeyevent, x11_window)) - return; - } - if (keycode == 0 && unicode == 0) return; @@ -1112,6 +1238,8 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { if (xkeyevent->type != KeyPress) { + p_echo = false; + // make sure there are events pending, // so this call won't block. if (XPending(x11_display) > 0) { @@ -1172,6 +1300,18 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { k->set_metakey(false); } + bool last_is_pressed = Input::get_singleton()->is_key_pressed(k->get_scancode()); + if (k->is_pressed()) { + if (last_is_pressed) { + k->set_echo(true); + } + } else { + //ignore + if (last_is_pressed == false) { + return; + } + } + //printf("key: %x\n",k->get_scancode()); input->parse_input_event(k); } @@ -1253,6 +1393,10 @@ void OS_X11::process_xevents() { XEvent event; XNextEvent(x11_display, &event); + if (XFilterEvent(&event, None)) { + continue; + } + switch (event.type) { case Expose: Main::force_redraw(); @@ -1295,6 +1439,9 @@ void OS_X11::process_xevents() { ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, x11_window, None, CurrentTime); } + if (xic) { + XSetICFocus(xic); + } break; case FocusOut: @@ -1308,9 +1455,16 @@ void OS_X11::process_xevents() { } XUngrabPointer(x11_display, CurrentTime); } + if (xic) { + XUnsetICFocus(xic); + } break; case ConfigureNotify: + if (xic) { + // Not portable. + set_ime_position(0, 1); + } /* call resizeGLScene only if our window-size changed */ if ((event.xconfigure.width == current_videomode.width) && diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h index d62186e5bd..b253934a05 100644 --- a/platform/x11/os_x11.h +++ b/platform/x11/os_x11.h @@ -113,6 +113,10 @@ class OS_X11 : public OS_Unix { ::XIC xic; ::XIM xim; ::XIMStyle xim_style; + static void xim_destroy_callback(::XIM im, ::XPointer client_data, + ::XPointer call_data); + void set_ime_position(short x, short y); + Point2i last_mouse_pos; bool last_mouse_pos_valid; Point2i last_click_pos;