diff options
author | Tor Andersson <tor@ccxvii.net> | 2024-11-28 15:16:55 +0100 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2024-11-28 15:17:10 +0100 |
commit | 919a8a82e16622c56146b44b4e151d195a52efc8 (patch) | |
tree | 466dbf1ff6d19975107fd99a2d3ee40d431dd351 | |
parent | aaf110d35ea191ef75407e6af6c9d142c123b6ad (diff) |
X11: fix handling of utf8 window/icon titlestor
-rw-r--r-- | src/x11/fg_init_x11.c | 2 | ||||
-rw-r--r-- | src/x11/fg_internal_x11.h | 2 | ||||
-rw-r--r-- | src/x11/fg_window_x11.c | 191 |
3 files changed, 160 insertions, 35 deletions
diff --git a/src/x11/fg_init_x11.c b/src/x11/fg_init_x11.c index 62b64fde..ce6dd0dd 100644 --- a/src/x11/fg_init_x11.c +++ b/src/x11/fg_init_x11.c @@ -258,6 +258,8 @@ void fgPlatformInitialize( const char* displayName ) fgDisplay.pDisplay.NetWMPid = fghGetAtom("_NET_WM_PID"); fgDisplay.pDisplay.ClientMachine = fghGetAtom("WM_CLIENT_MACHINE"); + fgDisplay.pDisplay.NetWMName = fghGetAtom("_NET_WM_NAME"); + fgDisplay.pDisplay.NetWMIconName = fghGetAtom("_NET_WM_ICON_NAME"); } /* Open an input method */ diff --git a/src/x11/fg_internal_x11.h b/src/x11/fg_internal_x11.h index 4b155846..c1ed501e 100644 --- a/src/x11/fg_internal_x11.h +++ b/src/x11/fg_internal_x11.h @@ -63,6 +63,8 @@ struct tagSFG_PlatformDisplay int NetWMSupported; /* Flag for EWMH Window Managers */ Atom NetWMPid; /* The _NET_WM_PID atom */ Atom ClientMachine; /* The client machine name atom */ + Atom NetWMName; + Atom NetWMIconName; XIM IM; /* The input method */ XIMStyle InputStyle; /* The input method style */ diff --git a/src/x11/fg_window_x11.c b/src/x11/fg_window_x11.c index f097d39b..e6a4fdde 100644 --- a/src/x11/fg_window_x11.c +++ b/src/x11/fg_window_x11.c @@ -26,6 +26,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#undef X_HAVE_UTF8_STRING + #define FREEGLUT_BUILDING_LIB #include <GL/freeglut.h> #include <limits.h> /* LONG_MAX */ @@ -44,6 +46,9 @@ #define HOST_NAME_MAX 255 #endif +static void set_window_title(Window win, const char *str); +static void set_icon_title(Window win, const char *str); + /* Motif window hints, only define needed ones */ typedef struct { @@ -357,21 +362,20 @@ void fgPlatformOpenWindow( SFG_Window* window, const char* title, wmHints.flags = StateHint; wmHints.initial_state = fgState.ForceIconic ? IconicState : NormalState; - /* Prepare the window and iconified window names... */ - XStringListToTextProperty( (char **) &title, 1, &textProperty ); - XSetWMProperties( fgDisplay.pDisplay.Display, window->Window.Handle, - &textProperty, - &textProperty, + 0, + 0, 0, 0, &sizeHints, &wmHints, NULL ); - XFree( textProperty.value ); + + set_window_title(window->Window.Handle, title); + set_icon_title(window->Window.Handle, title); XSetWMProtocols( fgDisplay.pDisplay.Display, window->Window.Handle, &fgDisplay.pDisplay.DeleteWindow, 1 ); @@ -532,46 +536,163 @@ void fgPlatformIconifyWindow( SFG_Window *window ) fgStructure.CurrentWindow->State.Visible = GL_FALSE; } -/* - * Set the current window's title +/* glutSetWindow(Title|Icon) needs to decide when to set WM_NAME and when to set + * _NET_WM_NAME. WM_NAME should not get utf8 characters, but 8-bit extended + * ASCII might be ok, while _NET_WM_NAME should be always be utf8. So we need + * to distinguish between the following cases: + * - string is all 7bit ASCII: goes in WM_NAME + * - has 8th bits but is not valid utf8: assume ext-ASCII goes in WM_NAME + * - has 8th bits somewhere and is valid utf8: goes in _NET_WM_NAME + * Whenever we don't set _NET_WM_NAME explicitly remove it, because otherwise + * it takes precedence over WM_NAME. + * Similarly for WM_ICON_NAME vs _NET_WM_ICON_NAME. + * + * This assumes we're building on a platform where X_HAVE_UTF8_STRING is defined + * Otherwise we'll just pass whatever through to XSetWMName as usual and it'll + * come out as gibberish if it was utf8. + * + * To make this work, the following function detects if a string has at least + * a single valid multi-byte utf8 character in it, and no invalid utf8 sequences + * in which case it returns non-zero. If no mutli-byte utf8 characters are + * detected, or a single invalid sequence, it returns 0 */ -void fgPlatformGlutSetWindowTitle( const char* title ) +#ifdef X_HAVE_UTF8_STRING +static int str_multibyte_utf8(const char *s) { - XTextProperty text; + int c, cont = 0, mbfound = 0; + + while(*s) { + c = *(unsigned char*)s++; + if(!cont) { + /* we're looking at the first byte of a character, see if it starts + * a multi-byte sequence + */ + if((c & 0xe0) == 0xc0) { + cont = 1; + } else if((c & 0xf0) == 0xe0) { + cont = 2; + } else if((c & 0xf8) == 0xf0) { + cont = 3; + } + } else { + /* we're in continuation bytes */ + if((c & 0xc0) != 0x80) { + /* not a valid continuation byte, string is not valid utf8 */ + return 0; + } + if(--cont == 0) { + /* we found a valid multi-byte utf8 sequence */ + mbfound++; + } + } + } + return mbfound ? 1 : 0; +} - text.value = (unsigned char *) title; - text.encoding = XA_STRING; - text.format = 8; - text.nitems = strlen( title ); +/* sets utf8 window title (_NET_WM_NAME or _NET_WM_ICON_NAME) */ +static int set_utf8_title(Window win, Atom prop, const char *str) +{ + Display *dpy = fgDisplay.pDisplay.Display; + XTextProperty text; + + if(Xutf8TextListToTextProperty(dpy, (char**)&str, 1, XUTF8StringStyle, &text) < 0) { + return -1; + } + XChangeProperty(dpy, win, prop, text.encoding, 8, PropModeReplace, + (unsigned char*)str, strlen(str)); + XFree(text.value); + return 0; +} +#endif /* X_HAVE_UTF8_STRING */ - XSetWMName( - fgDisplay.pDisplay.Display, - fgStructure.CurrentWindow->Window.Handle, - &text - ); +/* sets WM_NAME and/or WM_ICON_NAME */ +static void set_ascii_title(Window win, Atom prop, const char *str) +{ + char *p, *tmp = malloc(strlen(str)+1); + Display *dpy = fgDisplay.pDisplay.Display; + XTextProperty text; + int c; + + p = tmp; + while ((c = *((unsigned char*)str++)) != 0) { + if (c == 0) + break; + if (c < 128) { + *p++ = c; // ascii + } else if ((c & 0xe0) == 0xc0) { + *p++ = '_'; // first byte of a new character + } else { + // ignore continuation bytes + } + } + *p = 0; - XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */ + text.value = (unsigned char*)tmp; + text.encoding = XA_STRING; + text.format = 8; + text.nitems = strlen(tmp); + + if(prop == XA_WM_NAME) { + XSetWMName(dpy, win, &text); + } else if(prop == XA_WM_ICON_NAME) { + XSetWMIconName(dpy, win, &text); + } + + free(tmp); } -/* - * Set the current window's iconified title - */ -void fgPlatformGlutSetIconTitle( const char* title ) +static void set_window_title(Window win, const char *str) { - XTextProperty text; + set_ascii_title(win, XA_WM_NAME, str); +#ifdef X_HAVE_UTF8_STRING + Display *dpy = fgDisplay.pDisplay.Display; + + if(str_multibyte_utf8(str)) { + if(set_utf8_title(win, fgDisplay.pDisplay.NetWMName, str) != -1) { + return; /* success */ + } + } + /* if it's not utf8 or if set_utf8_title fails, delete _NET_WM_NAME if it + * exists, and fallback to setting WM_NAME instead + */ + XDeleteProperty(dpy, win, fgDisplay.pDisplay.NetWMName); +#endif +} - text.value = (unsigned char *) title; - text.encoding = XA_STRING; - text.format = 8; - text.nitems = strlen( title ); +static void set_icon_title(Window win, const char *str) +{ + set_ascii_title(win, XA_WM_ICON_NAME, str); +#ifdef X_HAVE_UTF8_STRING + Display *dpy = fgDisplay.pDisplay.Display; + + if(str_multibyte_utf8(str)) { + if(set_utf8_title(win, fgDisplay.pDisplay.NetWMIconName, str) != -1) { + return; /* success */ + } + } + /* if it's not utf8 or if set_utf8_title fails, delete _NET_WM_ICON_NAME if + * it exists, and fallback to setting WM_NAME instead + */ + XDeleteProperty(dpy, win, fgDisplay.pDisplay.NetWMIconName); +#endif +} - XSetWMIconName( - fgDisplay.pDisplay.Display, - fgStructure.CurrentWindow->Window.Handle, - &text - ); +/* + * Set the current window's title + */ +void fgPlatformGlutSetWindowTitle(const char *str) +{ + if(!str || !*str) return; + set_window_title(fgStructure.CurrentWindow->Window.Handle, str); +} - XFlush( fgDisplay.pDisplay.Display ); /* XXX Shouldn't need this */ +/* + * Set the current window's iconified title + */ +void fgPlatformGlutSetIconTitle(const char *str) +{ + if(!str || !*str) return; + set_icon_title(fgStructure.CurrentWindow->Window.Handle, str); } /* |