Index | Thread | Search

From:
Walter Alejandro Iglesias <wai@roquesor.com>
Subject:
Proper UTF-8 support for cwm(1) menu search
To:
tech@openbsd.org
Date:
Tue, 23 Dec 2025 13:09:19 +0100

Download raw body.

Thread
Currently UTF-8 support in in cwm(1) menu search is incomplete.  Dead
keys don't work as expected, for example, 'aacute' will print first the
tilde, then the 'a' by separate.  This is because cwm uses
XLookupString(3) which handles keyboard input for Latin-1.

There is no direct way to replace that function for
Xutf8LookupString(3).  I had to add XIM and XIC definitions.

In case anyone is concerned about portability to systems that still
support Latin-1.  I compiled the Debian version with my patch and tested
it with the en_US.ISO-8859-15 locale, it works perfectly.


Index: menu.c
===================================================================
RCS file: /cvs/xenocara/app/cwm/menu.c,v
diff -u -p -u -p -r1.110 menu.c
--- menu.c	15 Oct 2022 16:06:07 -0000	1.110
+++ menu.c	23 Dec 2025 10:59:04 -0000
@@ -65,7 +65,7 @@ struct menu_ctx {
 	void (*match)(struct menu_q *, struct menu_q *, char *);
 	void (*print)(struct menu *, int);
 };
-static struct menu	*menu_handle_key(XEvent *, struct menu_ctx *,
+static struct menu	*menu_handle_key(XIC, XEvent *, struct menu_ctx *,
 			     struct menu_q *, struct menu_q *);
 static void		 menu_handle_move(struct menu_ctx *,
 			     struct menu_q *, int, int);
@@ -77,7 +77,8 @@ static void 		 menu_draw_entry(struct me
 			     int, int);
 static int		 menu_calc_entry(struct menu_ctx *, int, int);
 static struct menu	*menu_complete_path(struct menu_ctx *);
-static int		 menu_keycode(XKeyEvent *, enum ctltype *, char *);
+static int		 menu_keycode(XIC, XKeyEvent *, enum ctltype *,
+			     char *);
 
 struct menu *
 menu_filter(struct screen_ctx *sc, struct menu_q *menuq, const char *prompt,
@@ -124,6 +125,12 @@ menu_filter(struct screen_ctx *sc, struc
 	XSelectInput(X_Dpy, mc.win, MENUMASK);
 	XMapRaised(X_Dpy, mc.win);
 
+	XIM im = XOpenIM(X_Dpy, NULL, NULL, NULL);
+	XIC ic = XCreateIC(im, XNInputStyle,
+	    XIMPreeditNothing | XIMStatusNothing,
+	    XNClientWindow, mc.win, NULL);
+	XSetICFocus(ic);
+
 	if (XGrabPointer(X_Dpy, mc.win, False, MENUGRABMASK,
 	    GrabModeAsync, GrabModeAsync, None, Conf.cursor[CF_QUESTION],
 	    CurrentTime) != GrabSuccess) {
@@ -144,12 +151,15 @@ menu_filter(struct screen_ctx *sc, struc
 
 		XWindowEvent(X_Dpy, mc.win, MENUMASK, &e);
 
+		if (XFilterEvent(&e, None))
+			continue;
+
 		switch (e.type) {
 		case KeyPress:
-			if ((mi = menu_handle_key(&e, &mc, menuq, &resultq))
-			    != NULL)
+			if ((mi = menu_handle_key(ic, &e, &mc, menuq,
+			    &resultq)) != NULL)
 				goto out;
-			/* FALLTHROUGH */
+				/* FALLTHROUGH */
 		case Expose:
 			menu_draw(&mc, menuq, &resultq);
 			break;
@@ -217,8 +227,8 @@ menu_complete_path(struct menu_ctx *mc)
 }
 
 static struct menu *
-menu_handle_key(XEvent *e, struct menu_ctx *mc, struct menu_q *menuq,
-    struct menu_q *resultq)
+menu_handle_key(XIC ic, XEvent *e, struct menu_ctx *mc,
+    struct menu_q *menuq, struct menu_q *resultq)
 {
 	struct menu	*mi;
 	enum ctltype	 ctl;
@@ -227,7 +237,7 @@ menu_handle_key(XEvent *e, struct menu_c
 	int 		 clen, i;
 	wchar_t 	 wc;
 
-	if (menu_keycode(&e->xkey, &ctl, chr) < 0)
+	if (menu_keycode(ic, &e->xkey, &ctl, chr) < 0)
 		return NULL;
 
 	switch (ctl) {
@@ -501,9 +511,11 @@ menu_calc_entry(struct menu_ctx *mc, int
 }
 
 static int
-menu_keycode(XKeyEvent *ev, enum ctltype *ctl, char *chr)
+menu_keycode(XIC ic, XKeyEvent *ev, enum ctltype *ctl, char *chr)
 {
-	KeySym		 ks;
+	KeySym			 ks;
+	Status			 status;
+	size_t			 len = sizeof(chr);
 
 	*ctl = CTL_NONE;
 	chr[0] = '\0';
@@ -582,7 +594,10 @@ menu_keycode(XKeyEvent *ev, enum ctltype
 	if (*ctl != CTL_NONE)
 		return 0;
 
-	if (XLookupString(ev, chr, 32, &ks, NULL) < 0)
+	len = Xutf8LookupString(ic, ev, chr, len - 1, &ks, &status);
+	chr[len] = 0;
+
+	if (status == XBufferOverflow)
 		return -1;
 
 	return 0;


-- 
Walter