[Bf-blender-cvs] SVN commit: /data/svn/bf-blender [48827] trunk/blender/intern/ghost/intern: patch [#30274] XIM improvement (non-latin support + connection recovery)

Campbell Barton ideasman42 at gmail.com
Wed Jul 11 10:31:55 CEST 2012


Revision: 48827
          http://projects.blender.org/scm/viewvc.php?view=rev&root=bf-blender&revision=48827
Author:   campbellbarton
Date:     2012-07-11 08:31:54 +0000 (Wed, 11 Jul 2012)
Log Message:
-----------
patch [#30274] XIM improvement (non-latin support + connection recovery)
from Shinsuke Irie (irie)

(from the tracker submission)

- allow us to input non-latin languages such as Japanese/Chinese
- recover XIM connection and its input contexts when XIM server restarted

Currently it supports only "root window" style input, while most people (and I) want "over the spot" or "on the spot" style one. Probably the implementation of "over the spot" or "on the spot" style becomes much complicated, because XIM server requires the coordinates of current cursor location relative to the screen in order to show the candidate window in appropriate position.

Modified Paths:
--------------
    trunk/blender/intern/ghost/intern/GHOST_SystemX11.cpp
    trunk/blender/intern/ghost/intern/GHOST_SystemX11.h
    trunk/blender/intern/ghost/intern/GHOST_WindowX11.cpp
    trunk/blender/intern/ghost/intern/GHOST_WindowX11.h

Modified: trunk/blender/intern/ghost/intern/GHOST_SystemX11.cpp
===================================================================
--- trunk/blender/intern/ghost/intern/GHOST_SystemX11.cpp	2012-07-11 07:49:08 UTC (rev 48826)
+++ trunk/blender/intern/ghost/intern/GHOST_SystemX11.cpp	2012-07-11 08:31:54 UTC (rev 48827)
@@ -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)
+{
+	GHOST_PRINT("XIM server died\n");
+
+	if (ptr)
+		*(XIM *)ptr = NULL;
+}
+
+bool GHOST_SystemX11::openX11_IM()
+{
+	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 && openX11_IM()) {
+					GHOST_PRINT("Connected to XIM server\n");
+				}
+
+				if (m_xim) {
+					GHOST_WindowX11 * window = findGhostWindow(xevent.xany.window);
+					if (window && !window->getX11_XIC() && window->createX11_XIC()) {
+						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(),

Modified: trunk/blender/intern/ghost/intern/GHOST_SystemX11.h
===================================================================
--- trunk/blender/intern/ghost/intern/GHOST_SystemX11.h	2012-07-11 07:49:08 UTC (rev 48826)
+++ trunk/blender/intern/ghost/intern/GHOST_SystemX11.h	2012-07-11 08:31:54 UTC (rev 48827)
@@ -309,6 +309,10 @@
 	 * X11 window xwind
 	 */
 
+#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
+	bool openX11_IM();
+#endif
+
 	GHOST_WindowX11 *
 	findGhostWindow(
 	    Window xwind

Modified: trunk/blender/intern/ghost/intern/GHOST_WindowX11.cpp
===================================================================
--- trunk/blender/intern/ghost/intern/GHOST_WindowX11.cpp	2012-07-11 07:49:08 UTC (rev 48826)
+++ trunk/blender/intern/ghost/intern/GHOST_WindowX11.cpp	2012-07-11 08:31:54 UTC (rev 48827)
@@ -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
@@ -478,6 +475,47 @@
 	XFlush(m_display);
 }
 
+#if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING)
+static void destroyICCallback(XIC xic, XPointer ptr, XPointer data)
+{
+	GHOST_PRINT("XIM input context destroyed\n");
+
+	if (ptr) {
+		*(XIC *)ptr = NULL;
+	}
+}
+
+bool GHOST_WindowX11::createX11_XIC()
+{
+	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

Modified: trunk/blender/intern/ghost/intern/GHOST_WindowX11.h
===================================================================
--- trunk/blender/intern/ghost/intern/GHOST_WindowX11.h	2012-07-11 07:49:08 UTC (rev 48826)
+++ trunk/blender/intern/ghost/intern/GHOST_WindowX11.h	2012-07-11 08:31:54 UTC (rev 48827)
@@ -234,6 +234,8 @@
 	XIC getX11_XIC() {
 		return m_xic;
 	}
+
+	bool createX11_XIC();
 #endif
 
 #ifdef WITH_XDND




More information about the Bf-blender-cvs mailing list