Index: intern/ghost/intern/GHOST_WindowX11.h =================================================================== --- intern/ghost/intern/GHOST_WindowX11.h (revision 46788) +++ intern/ghost/intern/GHOST_WindowX11.h (working copy) @@ -234,6 +234,8 @@ XIC getX11_XIC() { return m_xic; } + + bool createIC(); #endif #ifdef WITH_XDND Index: intern/ghost/intern/GHOST_SystemX11.h =================================================================== --- intern/ghost/intern/GHOST_SystemX11.h (revision 46788) +++ intern/ghost/intern/GHOST_SystemX11.h (working copy) @@ -309,6 +309,10 @@ * X11 window xwind */ +#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) + bool openIM(); +#endif + GHOST_WindowX11 * findGhostWindow( Window xwind Index: intern/ghost/intern/GHOST_WindowX11.cpp =================================================================== --- intern/ghost/intern/GHOST_WindowX11.cpp (revision 46788) +++ intern/ghost/intern/GHOST_WindowX11.cpp (working copy) @@ -401,10 +401,7 @@ } #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) - m_xic = XCreateIC(m_system->getX11_XIM(), XNClientWindow, m_window, XNFocusWindow, m_window, - XNInputStyle, XIMPreeditNothing | XIMStatusNothing, - XNResourceName, GHOST_X11_RES_NAME, XNResourceClass, - GHOST_X11_RES_CLASS, NULL); + m_xic = NULL; #endif // Set the window icon @@ -474,6 +471,46 @@ XFlush(m_display); } +#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) +static void destroyICCallback(XIC xic, XPointer ptr, XPointer data) +{ + printf("XIM input context destroyed\n"); + + if (ptr) + *(XIC *)ptr = NULL; +} + +bool GHOST_WindowX11::createIC() +{ + XIM xim = m_system->getX11_XIM(); + if (!xim) + return false; + + XICCallback destroy; + destroy.callback = (XICProc)destroyICCallback; + destroy.client_data = (XPointer)&m_xic; + m_xic = XCreateIC(xim, XNClientWindow, m_window, XNFocusWindow, m_window, + XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNResourceName, GHOST_X11_RES_NAME, + XNResourceClass, GHOST_X11_RES_CLASS, + XNDestroyCallback, &destroy, + NULL); + if (!m_xic) + return false; + + unsigned long fevent; + XGetICValues(m_xic, XNFilterEvents, &fevent, NULL); + XSelectInput(m_display, m_window, + ExposureMask | StructureNotifyMask | + KeyPressMask | KeyReleaseMask | + EnterWindowMask | LeaveWindowMask | + ButtonPressMask | ButtonReleaseMask | + PointerMotionMask | FocusChangeMask | + PropertyChangeMask | fevent); + return true; +} +#endif + #ifdef WITH_X11_XINPUT /* * Dummy function to get around IO Handler exiting if device invalid Index: intern/ghost/intern/GHOST_SystemX11.cpp =================================================================== --- intern/ghost/intern/GHOST_SystemX11.cpp (revision 46788) +++ intern/ghost/intern/GHOST_SystemX11.cpp (working copy) @@ -93,9 +93,12 @@ abort(); //was return before, but this would just mean it will crash later } - /* Open a connection to the X input manager */ #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) - m_xim = XOpenIM(m_display, NULL, (char *)GHOST_X11_RES_NAME, (char *)GHOST_X11_RES_CLASS); + /* note -- don't open connection to XIM server here, because the locale + * has to be set before opening the connection but setlocale() has not + * been called yet. the connection will be opened after entering + * the event loop. */ + m_xim = NULL; #endif m_delete_window_atom @@ -273,6 +276,35 @@ return window; } +#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) +static void destroyIMCallback(XIM xim, XPointer ptr, XPointer data) +{ + printf("XIM server died\n"); + + if (ptr) + *(XIM *)ptr = NULL; +} + +bool GHOST_SystemX11::openIM() +{ + if (!m_display) + return false; + + /* set locale modifiers such as "@im=ibus" specified by XMODIFIERS */ + XSetLocaleModifiers(""); + + m_xim = XOpenIM(m_display, NULL, (char *)GHOST_X11_RES_NAME, (char *)GHOST_X11_RES_CLASS); + if (!m_xim) + return false; + + XIMCallback destroy; + destroy.callback = (XIMProc)destroyIMCallback; + destroy.client_data = (XPointer)&m_xim; + XSetIMValues(m_xim, XNDestroyCallback, &destroy, NULL); + return true; +} +#endif + GHOST_WindowX11 * GHOST_SystemX11:: findGhostWindow( @@ -408,6 +440,38 @@ while (XPending(m_display)) { XEvent xevent; XNextEvent(m_display, &xevent); + +#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) + /* open connection to XIM server and create input context (XIC) + * when receiving the first FocusIn or KeyPress event after startup, + * or recover XIM and XIC when the XIM server has been restarted */ + if (xevent.type == FocusIn || xevent.type == KeyPress) { + if (!m_xim && openIM()) { + GHOST_PRINT("Connected to XIM server\n"); + } + + if (m_xim) { + GHOST_WindowX11 * window = findGhostWindow(xevent.xany.window); + if (window && !window->getX11_XIC() && window->createIC()) { + GHOST_PRINT("XIM input context created\n"); + if (xevent.type == KeyPress) + /* we can assume the window has input focus + * here, because key events are received only + * when the window is focused. */ + XSetICFocus(window->getX11_XIC()); + } + } + } + + /* dispatch event to XIM server */ + if (XFilterEvent(&xevent, (Window)NULL) == True + && xevent.type != KeyRelease) + /* do nothing now, the event is consumed by XIM. + * however, KeyRelease event should be processed + * here, otherwise modifiers remain activated. */ + continue; +#endif + processEvent(&xevent); anyProcessed = true; } @@ -535,7 +599,19 @@ XKeyEvent *xke = &(xe->xkey); KeySym key_sym = XLookupKeysym(xke, 0); char ascii; - char utf8_buf[6]; /* 6 is enough for a utf8 char */ +#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) + /* utf8_array[] is initial buffer used for Xutf8LookupString(). + * if the length of the utf8 string exceeds this array, allocate + * another memory area and call Xutf8LookupString() again. + * the last 5 bytes are used to avoid segfault that might happen + * at the end of this buffer when the constructor of GHOST_EventKey + * reads 6 bytes regardless of the effective data length. */ + char utf8_array[16 * 6 + 5]; /* 16 utf8 characters */ + char *utf8_buf = utf8_array; + int len = 1; /* at least one null character will be stored */ +#else + char *utf8_buf = NULL; +#endif GHOST_TKey gkey = convertXKey(key_sym); GHOST_TEventType type = (xke->type == KeyPress) ? @@ -547,15 +623,20 @@ #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) /* getting unicode on key-up events gives XLookupNone status */ - if (xke->type == KeyPress) { + XIC xic = window->getX11_XIC(); + if (xic && xke->type == KeyPress) { Status status; - int len; /* use utf8 because its not locale depentant, from xorg docs */ - if (!(len = Xutf8LookupString(window->getX11_XIC(), xke, utf8_buf, sizeof(utf8_buf), &key_sym, &status))) { + if (!(len = Xutf8LookupString(xic, xke, utf8_buf, sizeof(utf8_array) - 5, &key_sym, &status))) { utf8_buf[0] = '\0'; } + if (status == XBufferOverflow) { + utf8_buf = (char *) malloc(len + 5); + len = Xutf8LookupString(xic, xke, utf8_buf, len, &key_sym, &status); + } + if ((status == XLookupChars || status == XLookupBoth)) { if ((unsigned char)utf8_buf[0] >= 32) { /* not an ascii control character */ /* do nothing for now, this is valid utf8 */ @@ -571,19 +652,16 @@ else { printf("Bad keycode lookup. Keysym 0x%x Status: %s\n", (unsigned int) key_sym, - (status == XBufferOverflow ? "BufferOverflow" : - status == XLookupNone ? "XLookupNone" : + (status == XLookupNone ? "XLookupNone" : status == XLookupKeySym ? "XLookupKeySym" : "Unknown status")); - printf("'%.*s' %p %p\n", len, utf8_buf, window->getX11_XIC(), m_xim); + printf("'%.*s' %p %p\n", len, utf8_buf, xic, m_xim); } } else { utf8_buf[0] = '\0'; } -#else - utf8_buf[0] = '\0'; #endif g_event = new @@ -595,6 +673,42 @@ ascii, utf8_buf ); + +#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) + /* when using IM for some languages such as Japanese, + * one event inserts multiple utf8 characters */ + if (xic && xke->type == KeyPress) { + unsigned char c; + int i = 0; + while (1) { + /* search character boundary */ + if ((unsigned char)utf8_buf[i++] > 0x7f) { + for (; i < len; ++i) { + c = utf8_buf[i]; + if (c < 0x80 || c > 0xbf) break; + } + } + + if (i >= len) break; + + /* enqueue previous character */ + pushEvent(g_event); + + g_event = new + GHOST_EventKey( + getMilliSeconds(), + type, + window, + gkey, + '\0', + &utf8_buf[i] + ); + } + } + + if (utf8_buf != utf8_array) + free(utf8_buf); +#endif break; } @@ -675,6 +789,16 @@ GHOST_TEventType gtype = (xfe.type == FocusIn) ? GHOST_kEventWindowActivate : GHOST_kEventWindowDeactivate; +#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) + XIC xic = window->getX11_XIC(); + if (xic) { + if (xe->type == FocusIn) + XSetICFocus(xic); + else + XUnsetICFocus(xic); + } +#endif + g_event = new GHOST_Event( getMilliSeconds(), Index: source/blender/editors/space_text/text_ops.c =================================================================== --- source/blender/editors/space_text/text_ops.c (revision 46788) +++ source/blender/editors/space_text/text_ops.c (working copy) @@ -2928,8 +2928,11 @@ // if (!RNA_struct_property_is_set(op->ptr, "text")) { /* always set from keymap XXX */ if (!RNA_string_length(op->ptr, "text")) { - /* if alt/ctrl/super are pressed pass through */ - if (event->ctrl || event->oskey) { + /* if alt/ctrl/super are pressed pass through except for utf8 character event + (when input method are used for utf8 inputs, the user may assign key event + including alt/ctrl/super like ctrl+m to commit utf8 string. in such case, + the modifiers in the utf8 character event make no sense.) */ + if ((event->ctrl || event->oskey) && !event->utf8_buf[0]) { return OPERATOR_PASS_THROUGH; } else {