changeset 1178:9867f3d86e44

Real Unicode support for X11. Based on updated version of this patch: http://lists.arabeyes.org/archives/developer/2004/June/msg00160.html --ryan.
author Ryan C. Gordon <icculus@icculus.org>
date Mon, 21 Nov 2005 00:16:34 +0000
parents e967ab22e6fd
children abb4267e7028
files BUGS src/video/x11/SDL_x11events.c src/video/x11/SDL_x11events_c.h src/video/x11/SDL_x11sym.h src/video/x11/SDL_x11video.c src/video/x11/SDL_x11video.h
diffstat 6 files changed, 223 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/BUGS	Sun Nov 20 23:59:26 2005 +0000
+++ b/BUGS	Mon Nov 21 00:16:34 2005 +0000
@@ -15,6 +15,7 @@
 	It requires handling of keyboard mapping events and using the XIM
 	input translation extension.  I will implement it as requested.
 	Latin-1 keyboard input works fine.
+	(UPDATE 04/06/2004: this bug is now fixed)
 
 	The keyboard modifiers are not set to the correct state on startup.
 
@@ -100,7 +101,8 @@
 	It requires handling of keyboard mapping events and using the XIM
 	input translation extension.  I will implement it as requested.
 	Latin-1 keyboard input works fine.
-
+	(UPDATE 04/06/2004: this bug is now fixed but needs testing)
+	
 	The keyboard modifiers are not set to the correct state on startup.
 
 Solaris:
@@ -110,7 +112,8 @@
 	It requires handling of keyboard mapping events and using the XIM
 	input translation extension.  I will implement it as requested.
 	Latin-1 keyboard input works fine.
-
+	(UPDATE 04/06/2004: this bug is now fixed but needs testing)
+	
 	The keyboard modifiers are not set to the correct state on startup.
 
 IRIX:
@@ -122,7 +125,8 @@
 	It requires handling of keyboard mapping events and using the XIM
 	input translation extension.  I will implement it as requested.
 	Latin-1 keyboard input works fine.
-
+	(UPDATE 04/06/2004: this bug is now fixed but needs testing)
+	
 	The keyboard modifiers are not set to the correct state on startup.
 
 EPOC:
@@ -148,7 +152,8 @@
 	It requires handling of keyboard mapping events and using the XIM
 	input translation extension.  I will implement it as requested.
 	Latin-1 keyboard input works fine.
-
+	(UPDATE 04/06/2004: this bug is now fixed but needs testing)
+	
 	The keyboard modifiers are not set to the correct state on startup.
 
 OSF/Tru64:  -= NOT YET SUPPORTED =-
@@ -160,7 +165,8 @@
 	It requires handling of keyboard mapping events and using the XIM
 	input translation extension.  I will implement it as requested.
 	Latin-1 keyboard input works fine.
-
+	(UPDATE 04/06/2004: this bug is now fixed but needs testing)
+	
 	The keyboard modifiers are not set to the correct state on startup.
 
 AIX:  -= NOT YET SUPPORTED =-
@@ -176,7 +182,8 @@
 	It requires handling of keyboard mapping events and using the XIM
 	input translation extension.  I will implement it as requested.
 	Latin-1 keyboard input works fine.
-
+	(UPDATE 04/06/2004: this bug is now fixed but needs testing)
+	
 	The keyboard modifiers are not set to the correct state on startup.
 
 	The AIX port was done by Carsten.Griwodz@KOM.tu-darmstadt.de
--- a/src/video/x11/SDL_x11events.c	Sun Nov 20 23:59:26 2005 +0000
+++ b/src/video/x11/SDL_x11events.c	Mon Nov 21 00:16:34 2005 +0000
@@ -62,7 +62,7 @@
 /* The translation tables from an X11 keysym to a SDL keysym */
 static SDLKey ODD_keymap[256];
 static SDLKey MISC_keymap[256];
-SDL_keysym *X11_TranslateKey(Display *display, XKeyEvent *xkey, KeyCode kc,
+SDL_keysym *X11_TranslateKey(Display *display, XIC ic, XKeyEvent *xkey, KeyCode kc,
 			     SDL_keysym *keysym);
 
 /* Check to see if this is a repeated key.
@@ -241,7 +241,7 @@
 #ifdef DEBUG_XEVENTS
 printf("KeymapNotify!\n");
 #endif
-		X11_SetKeyboardState(SDL_Display, xevent.xkeymap.key_vector);
+		X11_SetKeyboardState(SDL_Display, SDL_IC,  xevent.xkeymap.key_vector);
 	    }
 	    break;
 
@@ -293,7 +293,7 @@
 printf("KeyPress (X11 keycode = 0x%X)\n", xevent.xkey.keycode);
 #endif
 		posted = SDL_PrivateKeyboard(SDL_PRESSED,
-				X11_TranslateKey(SDL_Display, &xevent.xkey,
+				X11_TranslateKey(SDL_Display, SDL_IC, &xevent.xkey,
 						 xevent.xkey.keycode,
 						 &keysym));
 	    }
@@ -309,7 +309,7 @@
 		/* Check to see if this is a repeated key */
 		if ( ! X11_KeyRepeat(SDL_Display, &xevent) ) {
 			posted = SDL_PrivateKeyboard(SDL_RELEASED, 
-				X11_TranslateKey(SDL_Display, &xevent.xkey,
+				X11_TranslateKey(SDL_Display, SDL_IC,  &xevent.xkey,
 						 xevent.xkey.keycode,
 						 &keysym));
 		}
@@ -612,7 +612,128 @@
 	MISC_keymap[XK_Hyper_R&0xFF] = SDLK_MENU;   /* Windows "Menu" key */
 }
 
-SDL_keysym *X11_TranslateKey(Display *display, XKeyEvent *xkey, KeyCode kc,
+#ifdef X_HAVE_UTF8_STRING
+Uint32 Utf8ToUcs4(const char * utf8)
+{
+	Uint32 c;
+	int i = 1;
+	int noOctets = 0;
+	int firstOctetMask = 0;
+	unsigned char firstOctet = utf8[0];
+	if (firstOctet < 0x80) {
+		/*
+		  Characters in the range:
+		    00000000 to 01111111 (ASCII Range)
+		  are stored in one octet:
+		    0xxxxxxx (The same as its ASCII representation)
+		  The least 6 significant bits of the first octet is the most 6 significant nonzero bits
+		  of the UCS4 representation.
+		*/
+		noOctets = 1;
+		firstOctetMask = 0x7F;  /* 0(1111111) - The most significant bit is ignored */
+	} else if ((firstOctet & 0xE0) /* get the most 3 significant bits by AND'ing with 11100000 */
+	              == 0xC0 ) {  /* see if those 3 bits are 110. If so, the char is in this range */
+		/*
+		  Characters in the range:
+		    00000000 10000000 to 00000111 11111111
+		  are stored in two octets:
+		    110xxxxx 10xxxxxx
+		  The least 5 significant bits of the first octet is the most 5 significant nonzero bits
+		  of the UCS4 representation.
+		*/
+		noOctets = 2;
+		firstOctetMask = 0x1F;  /* 000(11111) - The most 3 significant bits are ignored */
+	} else if ((firstOctet & 0xF0) /* get the most 4 significant bits by AND'ing with 11110000 */
+	              == 0xE0) {  /* see if those 4 bits are 1110. If so, the char is in this range */
+		/*
+		  Characters in the range:
+		    00001000 00000000 to 11111111 11111111
+		  are stored in three octets:
+		    1110xxxx 10xxxxxx 10xxxxxx
+		  The least 4 significant bits of the first octet is the most 4 significant nonzero bits
+		  of the UCS4 representation.
+		*/
+		noOctets = 3;
+		firstOctetMask = 0x0F; /* 0000(1111) - The most 4 significant bits are ignored */
+	} else if ((firstOctet & 0xF8) /* get the most 5 significant bits by AND'ing with 11111000 */
+	              == 0xF0) {  /* see if those 5 bits are 11110. If so, the char is in this range */
+		/*
+		  Characters in the range:
+		    00000001 00000000 00000000 to 00011111 11111111 11111111
+		  are stored in four octets:
+		    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+		  The least 3 significant bits of the first octet is the most 3 significant nonzero bits
+		  of the UCS4 representation.
+		*/
+		noOctets = 4;
+		firstOctetMask = 0x07; /* 11110(111) - The most 5 significant bits are ignored */
+	} else if ((firstOctet & 0xFC) /* get the most 6 significant bits by AND'ing with 11111100 */
+	              == 0xF8) { /* see if those 6 bits are 111110. If so, the char is in this range */
+		/*
+		  Characters in the range:
+		    00000000 00100000 00000000 00000000 to
+		    00000011 11111111 11111111 11111111
+		  are stored in five octets:
+		    111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+		  The least 2 significant bits of the first octet is the most 2 significant nonzero bits
+		  of the UCS4 representation.
+		*/
+		noOctets = 5;
+		firstOctetMask = 0x03; /* 111110(11) - The most 6 significant bits are ignored */
+	} else if ((firstOctet & 0xFE) /* get the most 7 significant bits by AND'ing with 11111110 */
+	              == 0xFC) { /* see if those 7 bits are 1111110. If so, the char is in this range */
+		/*
+		  Characters in the range:
+		    00000100 00000000 00000000 00000000 to
+		    01111111 11111111 11111111 11111111
+		  are stored in six octets:
+		    1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+		  The least significant bit of the first octet is the most significant nonzero bit
+		  of the UCS4 representation.
+		*/
+		noOctets = 6;
+		firstOctetMask = 0x01; /* 1111110(1) - The most 7 significant bits are ignored */
+	} else
+		return 0;  /* The given chunk is not a valid UTF-8 encoded Unicode character */
+	
+	/*
+	  The least noOctets significant bits of the first octet is the most 2 significant nonzero bits
+	  of the UCS4 representation.
+	  The first 6 bits of the UCS4 representation is the least 8-noOctets-1 significant bits of
+	  firstOctet if the character is not ASCII. If so, it's the least 7 significant bits of firstOctet.
+	  This done by AND'ing firstOctet with its mask to trim the bits used for identifying the
+	  number of continuing octets (if any) and leave only the free bits (the x's)
+	  Sample:
+	  1-octet:    0xxxxxxx  &  01111111 = 0xxxxxxx
+	  2-octets:  110xxxxx  &  00011111 = 000xxxxx
+	*/
+	c = firstOctet & firstOctetMask;
+	
+	/* Now, start filling c.ucs4 with the bits from the continuing octets from utf8. */
+	for (i = 1; i < noOctets; i++) {
+		/* A valid continuing octet is of the form 10xxxxxx */
+		if ((utf8[i] & 0xC0) /* get the most 2 significant bits by AND'ing with 11000000 */
+		    != 0x80) /* see if those 2 bits are 10. If not, the is a malformed sequence. */
+			/*The given chunk is a partial sequence at the end of a string that could
+			   begin a valid character */
+			return 0;
+		
+		/* Make room for the next 6-bits */
+		c <<= 6;
+		
+		/*
+		  Take only the least 6 significance bits of the current octet (utf8[i]) and fill the created room
+		  of c.ucs4 with them.
+		  This done by AND'ing utf8[i] with 00111111 and the OR'ing the result with c.ucs4.
+		*/
+		c |= utf8[i] & 0x3F;
+	}
+	return c;
+}
+#endif
+
+
+SDL_keysym *X11_TranslateKey(Display *display, XIC ic, XKeyEvent *xkey, KeyCode kc,
 			     SDL_keysym *keysym)
 {
 	KeySym xsym;
@@ -695,8 +816,7 @@
 	keysym->unicode = 0;
 	if ( SDL_TranslateUNICODE && xkey ) {
 		static XComposeStatus state;
-		/* Until we handle the IM protocol, use XLookupString() */
-		unsigned char keybuf[32];
+
 
 #define BROKEN_XFREE86_INTERNATIONAL_KBD
 /* This appears to be a magical flag that is used with AltGr on
@@ -711,15 +831,31 @@
 		}
 #endif
 		/* Look up the translated value for the key event */
-		if ( pXLookupString(xkey, (char *)keybuf, sizeof(keybuf),
-							NULL, &state) ) {
-			/*
-			 * FIXME,: XLookupString() may yield more than one
-			 * character, so we need a mechanism to allow for
-			 * this (perhaps generate null keypress events with
-			 * a unicode value)
-			 */
-			keysym->unicode = keybuf[0];
+
+		/* if there is no connection with the IM server, use the regular method */
+		if (ic == NULL) {
+			unsigned char keybuf[32];
+
+			if ( pXLookupString(xkey, (char *)keybuf, sizeof(keybuf),
+								NULL, &state) ) {
+				/*
+				* FIXME,: XLookupString() may yield more than one
+				* character, so we need a mechanism to allow for
+				* this (perhaps generate null keypress events with
+				* a unicode value)
+				*/
+				keysym->unicode = keybuf[0];
+			}
+		} else {  /* else, use the IM protocol */
+			#ifdef X_HAVE_UTF8_STRING
+			/* A UTF-8 character can be at most 6 bytes */
+			unsigned char keybuf[6];
+			pXSetICFocus(ic);
+			if ( pXutf8LookupString(ic, (XKeyPressedEvent *)xkey, (char *)keybuf, sizeof(keybuf),
+			                                    NULL, (Status *)&state) )
+				keysym->unicode = Utf8ToUcs4(keybuf);
+			pXUnsetICFocus(ic);
+			#endif
 		}
 	}
 	return(keysym);
@@ -832,12 +968,13 @@
 	return(unicode);
 }
 
+
 /*
  * Called when focus is regained, to read the keyboard state and generate
  * synthetic keypress/release events.
  * key_vec is a bit vector of keycodes (256 bits)
  */
-void X11_SetKeyboardState(Display *display, const char *key_vec)
+void X11_SetKeyboardState(Display *display, XIC ic, const char *key_vec)
 {
 	char keys_return[32];
 	int i;
@@ -886,7 +1023,7 @@
 			if(key_vec[i] & (1 << j)) {
 				SDL_keysym sk;
 				KeyCode kc = i << 3 | j;
-				X11_TranslateKey(display, NULL, kc, &sk);
+				X11_TranslateKey(display, ic, NULL, kc, &sk);
 				new_kstate[sk.sym] = SDL_PRESSED;
 				xcode[sk.sym] = kc;
 			}
--- a/src/video/x11/SDL_x11events_c.h	Sun Nov 20 23:59:26 2005 +0000
+++ b/src/video/x11/SDL_x11events_c.h	Mon Nov 21 00:16:34 2005 +0000
@@ -30,5 +30,5 @@
 /* Functions to be exported */
 extern void X11_InitOSKeymap(_THIS);
 extern void X11_PumpEvents(_THIS);
-extern void X11_SetKeyboardState(Display *display, const char *key_vec);
+extern void X11_SetKeyboardState(Display *display, XIC ic, const char *key_vec);
 
--- a/src/video/x11/SDL_x11sym.h	Sun Nov 20 23:59:26 2005 +0000
+++ b/src/video/x11/SDL_x11sym.h	Mon Nov 21 00:16:34 2005 +0000
@@ -114,6 +114,13 @@
 SDL_X11_SYM(int,XextRemoveDisplay,(XExtensionInfo*,Display*))
 #ifdef X_HAVE_UTF8_STRING
 SDL_X11_SYM(int,Xutf8TextListToTextProperty,(Display*,char**,int,XICCEncodingStyle,XTextProperty*))
+SDL_X11_SYM(int,Xutf8LookupString,(XIC,XKeyPressedEvent*,char*,int,KeySym*,Status*))
+SDL_X11_SYM(XIC,XCreateIC,(XIM, ...))
+SDL_X11_SYM(void,XDestroyIC,(XIC))
+SDL_X11_SYM(void,XSetICFocus,(XIC))
+SDL_X11_SYM(void,XUnsetICFocus,(XIC))
+SDL_X11_SYM(XIM,XOpenIM,(Display*,struct _XrmHashBucketRec*,char*,char*))
+SDL_X11_SYM(Status,XCloseIM,(XIM))
 #endif
 SDL_X11_SYM(void,_XEatData,(Display*,unsigned long))
 SDL_X11_SYM(void,_XFlush,(Display*))
--- a/src/video/x11/SDL_x11video.c	Sun Nov 20 23:59:26 2005 +0000
+++ b/src/video/x11/SDL_x11video.c	Mon Nov 21 00:16:34 2005 +0000
@@ -349,6 +349,7 @@
 		 FocusChangeMask | KeyPressMask | KeyReleaseMask
 		 | PropertyChangeMask | StructureNotifyMask | KeymapStateMask);
 
+    char * savedclassname = 0;
     /* Set the class hints so we can get an icon (AfterStep) */
     {
 	XClassHint *classhints;
@@ -358,6 +359,7 @@
             if ( ! classname ) {
                 classname = "SDL_App";
             }
+	    savedclassname = strdup(classname);
 	    classhints->res_name = classname;
 	    classhints->res_class = classname;
 	    pXSetClassHint(SDL_Display, WMwindow, classhints);
@@ -365,6 +367,33 @@
 	}
     }
 
+    /* Setup the communication with the IM server */
+    SDL_IM = NULL;
+    SDL_IC = NULL;
+
+    #ifdef X_HAVE_UTF8_STRING
+    SDL_IM = pXOpenIM(SDL_Display, NULL, savedclassname, savedclassname);
+    if (SDL_IM == NULL) {
+	SDL_SetError("no input method could be opened");
+    } else {
+	SDL_IC = pXCreateIC(SDL_IM,
+			XNClientWindow, WMwindow,
+			XNFocusWindow, WMwindow,
+			XNInputStyle, XIMPreeditNothing  | XIMStatusNothing,
+			XNResourceName, savedclassname,
+			XNResourceClass, savedclassname,
+			NULL);
+	if (SDL_IC == NULL) {
+		SDL_SetError("no input context could be created");
+		pXCloseIM(SDL_IM);
+		SDL_IM = NULL;
+	}
+    }
+    #endif
+
+    free(savedclassname);
+
+
     /* Allow the window to be deleted by the window manager */
     WM_DELETE_WINDOW = pXInternAtom(SDL_Display, "WM_DELETE_WINDOW", False);
     pXSetWMProtocols(SDL_Display, WMwindow, &WM_DELETE_WINDOW, 1);
@@ -808,7 +837,6 @@
 					| ButtonPressMask | ButtonReleaseMask
 					| PointerMotionMask | ExposureMask ));
 	}
-
 	/* Create the graphics context here, once we have a window */
 	if ( flags & SDL_OPENGL ) {
 		if ( X11_GL_CreateContext(this) < 0 ) {
@@ -854,7 +882,7 @@
 	}
 
 	/* Update the internal keyboard state */
-	X11_SetKeyboardState(SDL_Display, NULL);
+	X11_SetKeyboardState(SDL_Display, SDL_IC, NULL);
 
 	/* When the window is first mapped, ignore non-modifier keys */
 	{
@@ -892,6 +920,7 @@
 			screen->flags &= ~SDL_FULLSCREEN;
 		}
 	}
+	
 	return(0);
 }
 
@@ -1231,6 +1260,18 @@
 		/* Flush any delayed updates */
 		pXSync(GFX_Display, False);
 
+		/* Close the connection with the IM server */
+		#ifdef X_HAVE_UTF8_STRING
+		if (SDL_IC == NULL) {
+			pXDestroyIC(SDL_IC);
+			SDL_IC = NULL;
+		}
+		if (SDL_IM == NULL) {
+			pXCloseIM(SDL_IM);
+			SDL_IM = NULL;
+		}
+		#endif
+
 		/* Start shutting down the windows */
 		X11_DestroyImage(this, this->screen);
 		X11_DestroyWindow(this, this->screen);
--- a/src/video/x11/SDL_x11video.h	Sun Nov 20 23:59:26 2005 +0000
+++ b/src/video/x11/SDL_x11video.h	Mon Nov 21 00:16:34 2005 +0000
@@ -62,6 +62,8 @@
     Window SDL_Window;		/* Shared by both displays (no X security?) */
     Atom WM_DELETE_WINDOW;	/* "close-window" protocol atom */
     WMcursor *BlankCursor;	/* The invisible cursor */
+    XIM X11_IM;		/* Used to communicate with the input method (IM) server */
+    XIC X11_IC;		/* Used for retaining the state, properties, and semantics of communication with                                                  the input method (IM) server */
 
     char *SDL_windowid;		/* Flag: true if we have been passed a window */
 
@@ -147,15 +149,15 @@
 #define SDL_Display		(this->hidden->X11_Display)
 #define GFX_Display		(this->hidden->GFX_Display)
 #define SDL_Screen		DefaultScreen(this->hidden->X11_Display)
-
 #define SDL_Visual		(this->hidden->vis)
-
 #define SDL_Root		RootWindow(SDL_Display, SDL_Screen)
 #define WMwindow		(this->hidden->WMwindow)
 #define FSwindow		(this->hidden->FSwindow)
 #define SDL_Window		(this->hidden->SDL_Window)
 #define WM_DELETE_WINDOW	(this->hidden->WM_DELETE_WINDOW)
 #define SDL_BlankCursor		(this->hidden->BlankCursor)
+#define SDL_IM		(this->hidden->X11_IM)
+#define SDL_IC		(this->hidden->X11_IC)
 #define SDL_windowid		(this->hidden->SDL_windowid)
 #define using_dga		(this->hidden->using_dga)
 #define use_mitshm		(this->hidden->use_mitshm)
@@ -186,7 +188,6 @@
 #define gamma_saved		(this->hidden->gamma_saved)
 #define gamma_changed		(this->hidden->gamma_changed)
 #define SDL_iconcolors		(this->hidden->iconcolors)
-
 /* Some versions of XFree86 have bugs - detect if this is one of them */
 #define BUGGY_XFREE86(condition, buggy_version) \
 ((strcmp(ServerVendor(SDL_Display), "The XFree86 Project, Inc") == 0) && \