diff --git a/compose.c b/compose.c index 89d7611..9486f59 100644 --- a/compose.c +++ b/compose.c @@ -5,7 +5,13 @@ #include #include -static struct xkb_compose_state *cstate; +/* + * One state per frontend, all made before the procs are: xkbcommon locks + * nothing, and even its table refcount is a plain increment. cowner is + * the context whose sequence a state holds. + */ +static struct xkb_compose_state *cstate[Ncompose]; +static void *cowner[Ncompose]; /* Dead keys and Compose: no frontend's client does them for us. */ void @@ -14,6 +20,7 @@ composeinit(void) struct xkb_context *ctx; struct xkb_compose_table *table; char *locale; + int i; locale = setlocale(LC_CTYPE, ""); ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); @@ -22,30 +29,51 @@ composeinit(void) table = xkb_compose_table_new_from_locale(ctx, locale != nil ? locale : "C", XKB_COMPOSE_COMPILE_NO_FLAGS); if(table != nil) - cstate = xkb_compose_state_new(table, XKB_COMPOSE_STATE_NO_FLAGS); + for(i = 0; i < Ncompose; i++) + cstate[i] = xkb_compose_state_new(table, + XKB_COMPOSE_STATE_NO_FLAGS); xkb_compose_table_unref(table); xkb_context_unref(ctx); } +/* The context is going, and the sequence it left half typed with it. */ +void +composedrop(int who, void *owner) +{ + if(cstate[who] == nil || owner != cowner[who]) + return; + xkb_compose_state_reset(cstate[who]); + cowner[who] = nil; +} + /* * Feeds sym to the Compose table. Returns 1 while a sequence is * unfinished: the key belongs to the sequence, not to the engine. A * finished sequence leaves its text in buf, which the caller commits - * after whatever the engine had pending. + * after whatever the engine had pending. A sequence belongs to the + * context that started it: a key from another one starts over. */ int -composekey(u32int sym, char *buf, int n) +composekey(int who, void *owner, u32int sym, char *buf, int n) { + struct xkb_compose_state *cs; + buf[0] = '\0'; - if(cstate == nil || - xkb_compose_state_feed(cstate, sym) != XKB_COMPOSE_FEED_ACCEPTED) + cs = cstate[who]; + if(cs == nil) return 0; - switch(xkb_compose_state_get_status(cstate)){ + if(owner != cowner[who]){ + xkb_compose_state_reset(cs); + cowner[who] = owner; + } + if(xkb_compose_state_feed(cs, sym) != XKB_COMPOSE_FEED_ACCEPTED) + return 0; + switch(xkb_compose_state_get_status(cs)){ case XKB_COMPOSE_COMPOSING: case XKB_COMPOSE_CANCELLED: return 1; case XKB_COMPOSE_COMPOSED: - xkb_compose_state_get_utf8(cstate, buf, n); + xkb_compose_state_get_utf8(cs, buf, n); break; default: break; diff --git a/dat.h b/dat.h index 581302a..71066a2 100644 --- a/dat.h +++ b/dat.h @@ -41,6 +41,15 @@ enum Colselfg = 0xffffff, }; +/* Each frontend composes on its own: they run in different procs. */ +enum +{ + Composeibus, + Composexim, + Composewl, + Ncompose, +}; + /* Popup metrics in pixels; popupscale is GDK_SCALE, for HiDPI. */ extern int popupscale; #define Fontsz (32*popupscale) diff --git a/fn.h b/fn.h index 327761f..738edd3 100644 --- a/fn.h +++ b/fn.h @@ -48,7 +48,8 @@ void wlthread(void*); int keymeaningful(u32int); void composeinit(void); -int composekey(u32int, char*, int); +void composedrop(int, void*); +int composekey(int, void*, u32int, char*, int); void* emalloc(ulong); void* erealloc(void*, ulong); diff --git a/ibus.c b/ibus.c index 63a1106..6a8e1b0 100644 --- a/ibus.c +++ b/ibus.c @@ -509,6 +509,7 @@ static void dropcontext(Ictx *ctx) { releasecontext(ctx); + composedrop(Composeibus, ctx); if(preowner == ctx) preowner = nil; memset(ctx, 0, sizeof *ctx); @@ -536,7 +537,7 @@ processkey(Ictx *ctx, u32int sym, u32int state, char *text, int n, Keyres *res) return 0; ctx->focused = 1; /* An IBus client drops a dead key we do not take: compose it here. */ - if(composekey(sym, text, n)) + if(composekey(Composeibus, ctx, sym, text, n)) return -1; if(text[0] != '\0') /* Composed text follows whatever was pending. */ diff --git a/tests/compose_test.c b/tests/compose_test.c index 76805cc..a82cb97 100644 --- a/tests/compose_test.c +++ b/tests/compose_test.c @@ -3,24 +3,37 @@ #include "test.h" #include -/* A sequence swallows its keys and hands its text back at the end. */ +/* + * A sequence swallows its keys and hands its text back at the end, and + * belongs to the one context, in the one frontend, that started it. + */ void compose_sequences(struct ct *t) { + static int a, b; /* two input contexts, told apart by address */ static const struct { + int who; + void *owner; u32int sym; int composing; char *text; } keys[] = { - { XKB_KEY_a, 0, "" }, - { XKB_KEY_dead_acute, 1, "" }, - { XKB_KEY_e, 0, "é" }, - { XKB_KEY_Multi_key, 1, "" }, - { XKB_KEY_o, 1, "" }, - { XKB_KEY_c, 0, "©" }, - { XKB_KEY_dead_acute, 1, "" }, - { XKB_KEY_Escape, 1, "" }, - { XKB_KEY_a, 0, "" }, + { Composexim, &a, XKB_KEY_a, 0, "" }, + { Composexim, &a, XKB_KEY_dead_acute, 1, "" }, + { Composexim, &a, XKB_KEY_e, 0, "é" }, + { Composexim, &a, XKB_KEY_Multi_key, 1, "" }, + { Composexim, &a, XKB_KEY_o, 1, "" }, + { Composexim, &a, XKB_KEY_c, 0, "©" }, + { Composexim, &a, XKB_KEY_dead_acute, 1, "" }, + { Composexim, &a, XKB_KEY_Escape, 1, "" }, + { Composexim, &a, XKB_KEY_a, 0, "" }, + /* another context of the same frontend starts over */ + { Composexim, &a, XKB_KEY_dead_acute, 1, "" }, + { Composexim, &b, XKB_KEY_e, 0, "" }, + /* another frontend does not touch this one's sequence */ + { Composexim, &a, XKB_KEY_dead_acute, 1, "" }, + { Composewl, &a, XKB_KEY_o, 0, "" }, + { Composexim, &a, XKB_KEY_e, 0, "é" }, }; char buf[Maxutf]; int i; @@ -29,9 +42,15 @@ compose_sequences(struct ct *t) return; composeinit(); for(i = 0; i < nelem(keys); i++){ - CT_EQ_INT(t, keys[i].composing, - composekey(keys[i].sym, buf, sizeof buf)); + CT_EQ_INT(t, keys[i].composing, composekey(keys[i].who, + keys[i].owner, keys[i].sym, buf, sizeof buf)); CT_EQ_STR(t, keys[i].text, buf); } + /* a context that goes takes its half-typed sequence with it */ + CT_EQ_INT(t, 1, + composekey(Composewl, &a, XKB_KEY_dead_acute, buf, sizeof buf)); + composedrop(Composewl, &a); + CT_EQ_INT(t, 0, composekey(Composewl, &a, XKB_KEY_e, buf, sizeof buf)); + CT_EQ_STR(t, "", buf); unsetenv("XCOMPOSEFILE"); } diff --git a/wl.c b/wl.c index ef9cac6..28ea693 100644 --- a/wl.c +++ b/wl.c @@ -356,6 +356,7 @@ leave(void) Keyres res; releasekeys(); + composedrop(Composewl, &context); if(engaged){ sendrequest(Keyrelease, 0, 0, &res); engaged = 0; @@ -506,7 +507,7 @@ grabkey(void*, struct zwp_input_method_keyboard_grab_v2*, u32int, return; } sym = xkb_state_key_get_one_sym(kstate, code + 8); - if(composekey(sym, text, sizeof text)) + if(composekey(Composewl, &context, sym, text, sizeof text)) return; /* a sequence in the making */ key = ipckeysym(sym, xkb_keysym_to_utf32(sym)); if(keymeaningful(key)) diff --git a/xim.c b/xim.c index 85e334a..80d223b 100644 --- a/xim.c +++ b/xim.c @@ -384,7 +384,7 @@ kpress(Ic *state, xcb_key_press_event_t *ev) int n; sym = keymaplookup(kstate, ev->detail, ev->state); - if(composekey(sym, text, sizeof text)) + if(composekey(Composexim, state, sym, text, sizeof text)) return; key = ipckeysym(sym, xkb_keysym_to_utf32(sym)); if(keymeaningful(key)) @@ -424,6 +424,7 @@ icfree(void *p) state = p; /* The owner remains valid until imthread acknowledges its release. */ release(state); + composedrop(Composexim, state); icunlink(state); free(state); }