xim: dead keys and Compose

Xlib does no local composing for a client on an input method server,
so under XIM a dead key was forwarded as a bare keysym and vanished.
The XIM frontend now feeds keys through xkbcommon's Compose table for
the daemon's locale and commits the composed character itself.
This commit is contained in:
2026-08-16 21:39:22 +09:00
parent 6b1de2df4f
commit 48d3e165c3

View File

@@ -2,10 +2,12 @@
#include "fn.h" #include "fn.h"
#include <errno.h> #include <errno.h>
#include <locale.h>
#include <poll.h> #include <poll.h>
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <xcb/xkb.h> #include <xcb/xkb.h>
#include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-compose.h>
#include <xkbcommon/xkbcommon-x11.h> #include <xkbcommon/xkbcommon-x11.h>
#include <xcb-imdkit/imdkit.h> #include <xcb-imdkit/imdkit.h>
#include <xcb-imdkit/encoding.h> #include <xcb-imdkit/encoding.h>
@@ -38,6 +40,7 @@ enum
static xcb_connection_t *conn; static xcb_connection_t *conn;
static xcb_im_t *xim; static xcb_im_t *xim;
static struct xkb_state *kstate; static struct xkb_state *kstate;
static struct xkb_compose_state *compose;
static uint8_t xkbevent; static uint8_t xkbevent;
static xcb_window_t rootwin; static xcb_window_t rootwin;
static Ic *ics; static Ic *ics;
@@ -96,6 +99,26 @@ out:
return ok; return ok;
} }
/* Dead keys and Compose: Xlib leaves them to the input method server. */
static void
cinit(void)
{
struct xkb_context *context;
struct xkb_compose_table *table;
char *locale;
locale = setlocale(LC_CTYPE, "");
context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if(context == nil)
return;
table = xkb_compose_table_new_from_locale(context,
locale != nil ? locale : "C", XKB_COMPOSE_COMPILE_NO_FLAGS);
if(table != nil)
compose = xkb_compose_state_new(table, XKB_COMPOSE_STATE_NO_FLAGS);
xkb_compose_table_unref(table);
xkb_context_unref(context);
}
static void static void
kwatch(void) kwatch(void)
{ {
@@ -361,11 +384,25 @@ kpress(Ic *state, xcb_key_press_event_t *ev)
{ {
Keyres res; Keyres res;
char buf[Maxutf]; char buf[Maxutf];
u32int key; u32int key, sym;
int n, wasvalid; int composed, n, wasvalid;
key = keymaplookup(kstate, ev->detail, ev->state); sym = keymaplookup(kstate, ev->detail, ev->state);
key = ipckeysym(key, xkb_keysym_to_utf32(key)); composed = 0;
if(compose != nil &&
xkb_compose_state_feed(compose, sym) == XKB_COMPOSE_FEED_ACCEPTED)
switch(xkb_compose_state_get_status(compose)){
case XKB_COMPOSE_COMPOSING:
case XKB_COMPOSE_CANCELLED:
return;
case XKB_COMPOSE_COMPOSED:
sym = xkb_compose_state_get_one_sym(compose);
composed = 1;
break;
default:
break;
}
key = ipckeysym(sym, xkb_keysym_to_utf32(sym));
if(keymeaningful(key)){ if(keymeaningful(key)){
wasvalid = state->caret.valid; wasvalid = state->caret.valid;
place(state); place(state);
@@ -376,7 +413,13 @@ kpress(Ic *state, xcb_key_press_event_t *ev)
n = stoutf(&res.commit, buf, sizeof buf); n = stoutf(&res.commit, buf, sizeof buf);
commit(state, buf, n); commit(state, buf, n);
updatepreedit(state, &res.preedit); updatepreedit(state, &res.preedit);
if(!res.eaten) if(res.eaten)
;
else if(composed){
/* The event holds the last key of the sequence, not its result. */
n = xkb_keysym_to_utf8(sym, buf, sizeof buf);
commit(state, buf, max(n - 1, 0));
}else
xcb_im_forward_event(xim, state->xic, ev); xcb_im_forward_event(xim, state->xic, ev);
xcb_flush(conn); xcb_flush(conn);
} }
@@ -498,6 +541,7 @@ ximinit(void)
ximlog("cannot read keyboard mapping"); ximlog("cannot read keyboard mapping");
return -1; return -1;
} }
cinit();
kwatch(); kwatch();
win = xcb_generate_id(conn); win = xcb_generate_id(conn);
xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, screen->root, xcb_create_window(conn, XCB_COPY_FROM_PARENT, win, screen->root,