Download raw body.
Proper UTF-8 support for cwm(1) menu search (3rd PING)
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
Proper UTF-8 support for cwm(1) menu search (3rd PING)