Index | Thread | Search

From:
Walter Alejandro Iglesias <wai@roquesor.com>
Subject:
Re: Proper UTF-8 support for cwm(1) menu search (3rd PING)
To:
tech@openbsd.org
Date:
Sat, 23 May 2026 19:46:55 +0200

Download raw body.

Thread
3rd ping

On Mon, Jan 19, 2026 at 11:07:02AM +0100, Walter Alejandro Iglesias wrote:
> On Tue, Dec 23, 2025 at 01:09:23PM +0100, Walter Alejandro Iglesias wrote:
> > 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


Updated (safer) corrected version:


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	28 Apr 2026 12:38:30 -0000
@@ -36,6 +36,7 @@
 
 #define PROMPT_SCHAR	"\xc2\xbb"
 #define PROMPT_ECHAR	"\xc2\xab"
+#define CHR_SZ		32
 
 #define MENUMASK 	(MOUSEMASK | ButtonMotionMask | KeyPressMask | \
 			 ExposureMask)
@@ -65,7 +66,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 +78,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 *, size_t);
 
 struct menu *
 menu_filter(struct screen_ctx *sc, struct menu_q *menuq, const char *prompt,
@@ -91,6 +93,8 @@ menu_filter(struct screen_ctx *sc, struc
 	XEvent			 e;
 	Window			 focuswin;
 	int			 focusrevert, xsave, ysave, xcur, ycur;
+	XIM im;
+	XIC ic;
 
 	TAILQ_INIT(&resultq);
 
@@ -124,6 +128,17 @@ menu_filter(struct screen_ctx *sc, struc
 	XSelectInput(X_Dpy, mc.win, MENUMASK);
 	XMapRaised(X_Dpy, mc.win);
 
+	im = XOpenIM(X_Dpy, NULL, NULL, NULL);
+	ic = NULL;
+
+	if (im != NULL) {
+		ic = XCreateIC(im, XNInputStyle,
+		    XIMPreeditNothing | XIMStatusNothing,
+		    XNClientWindow, mc.win, NULL);
+		if (ic != NULL)
+			XSetICFocus(ic);
+	}
+
 	if (XGrabPointer(X_Dpy, mc.win, False, MENUGRABMASK,
 	    GrabModeAsync, GrabModeAsync, None, Conf.cursor[CF_QUESTION],
 	    CurrentTime) != GrabSuccess) {
@@ -144,12 +159,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;
@@ -173,6 +191,13 @@ out:
 		mi = NULL;
 	}
 
+	if (ic != NULL) {
+		XUnsetICFocus(ic);
+		XDestroyIC(ic);
+	}
+	if (im != NULL)
+		XCloseIM(im);
+
 	XftDrawDestroy(mc.xftdraw);
 	XDestroyWindow(X_Dpy, mc.win);
 
@@ -217,17 +242,17 @@ 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;
-	char		 chr[32];
+	char		 chr[CHR_SZ];
 	size_t		 len;
 	int 		 clen, i;
 	wchar_t 	 wc;
 
-	if (menu_keycode(&e->xkey, &ctl, chr) < 0)
+	if (menu_keycode(ic, &e->xkey, &ctl, chr, sizeof(chr)) < 0)
 		return NULL;
 
 	switch (ctl) {
@@ -501,9 +526,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, size_t chrlen)
 {
-	KeySym		 ks;
+	KeySym		 ks;
+	Status		 status;
+	size_t		 len;
 
 	*ctl = CTL_NONE;
 	chr[0] = '\0';
@@ -582,8 +609,15 @@ menu_keycode(XKeyEvent *ev, enum ctltype
 	if (*ctl != CTL_NONE)
 		return 0;
 
-	if (XLookupString(ev, chr, 32, &ks, NULL) < 0)
+	if (ic != NULL) {
+		len = Xutf8LookupString(ic, ev, chr, chrlen - 1, &ks, &status);
+		if (status == XBufferOverflow)
+			return -1;
+		chr[len] = '\0';
+	} else {
+		if (XLookupString(ev, chr, chrlen, &ks, NULL) < 0)
 		return -1;
+	}
 
 	return 0;
 }


-- 
Walter