summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Andersson <tor@ccxvii.net>2024-11-28 15:16:55 +0100
committerTor Andersson <tor@ccxvii.net>2024-11-28 15:17:10 +0100
commit919a8a82e16622c56146b44b4e151d195a52efc8 (patch)
tree466dbf1ff6d19975107fd99a2d3ee40d431dd351
parentaaf110d35ea191ef75407e6af6c9d142c123b6ad (diff)
X11: fix handling of utf8 window/icon titlestor
-rw-r--r--src/x11/fg_init_x11.c2
-rw-r--r--src/x11/fg_internal_x11.h2
-rw-r--r--src/x11/fg_window_x11.c191
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);
}
/*