From: Walter Alejandro Iglesias 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 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