compose: a sequence belongs to one context
One xkb_compose_state served the whole session, so a dead key left half typed in one window was completed by the first key in the next. Two IBus contexts through the real processkey(), before this: want length 0, got length 2; byte 0: want <end>, got 0xc3 (text) want 0, got 1 (req.op) Context A pressed dead_acute; context B pressed e, got é, and went down the composed-text branch instead of sending a key. Only COMPOSING is sticky -- xkbcommon starts over by itself after COMPOSED and CANCELLED, which the table in compose_test already pins -- and nothing here ever called xkb_compose_state_reset. That static was also fed from three procs, two of them live at once, with no lock, which xkbcommon forbids. Reaching it needs two focused windows, so it cannot be made to fail on demand and has no test: it goes because the sharing goes, not because anything guards it. So a state per frontend, each starting over when the key comes from another context. All are made in composeinit(), on threadmain, before proccreate, because xkb_compose_state_new refs the table and that ref is a plain increment -- b160: mov (%rdi),%edx b16a: add $0x1,%edx b170: mov %edx,(%rdi) -- so making a state from a shared table on two procs would race in place of the feed. Made before the procs exist they can still share the one table. An owner address that gets reused says so with composedrop: ibus hands out a contexts[] slot again, xim can malloc an Ic at a freed one, and wl's single context, which serves every client in turn, drops at every activate and deactivate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
44
compose.c
44
compose.c
@@ -5,7 +5,13 @@
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbcommon-compose.h>
|
||||
|
||||
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;
|
||||
|
||||
9
dat.h
9
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)
|
||||
|
||||
3
fn.h
3
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);
|
||||
|
||||
3
ibus.c
3
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. */
|
||||
|
||||
@@ -3,24 +3,37 @@
|
||||
#include "test.h"
|
||||
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||
|
||||
/* 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");
|
||||
}
|
||||
|
||||
3
wl.c
3
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))
|
||||
|
||||
3
xim.c
3
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user