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>
222 lines
3.5 KiB
C
222 lines
3.5 KiB
C
#include <u.h>
|
|
#include <libc.h>
|
|
#include <thread.h>
|
|
#include <bio.h>
|
|
#include "ipc.h"
|
|
|
|
#define min(a, b) ((a) < (b) ? (a) : (b))
|
|
#define max(a, b) ((a) > (b) ? (a) : (b))
|
|
|
|
/* A language id is the control code of the Ctrl key that selects it. */
|
|
enum
|
|
{
|
|
LangEN = 0x14,
|
|
LangJP = 0x0e,
|
|
LangJPK = 0x0b,
|
|
LangKO = 0x13,
|
|
LangHANJA = 0x08,
|
|
LangEMOJI = 0x05,
|
|
LangVI = 0x16,
|
|
|
|
Maxrunes = 64,
|
|
Maxutf = Maxrunes * UTFmax + 1,
|
|
};
|
|
|
|
enum
|
|
{
|
|
Maxclients = 64,
|
|
Ownerpoll = 200, /* ms between a frontend's owner checks while its preedit shows */
|
|
Maxkouho = 128,
|
|
Maxdisp = 9,
|
|
|
|
/* Content purposes whose keys the engine never sees; IBus and
|
|
* text-input-v3 number them alike, both after GTK. */
|
|
Purposepassword = 8,
|
|
Purposepin = 9,
|
|
|
|
Colfg = 0x000000,
|
|
Colbg = 0xffffff,
|
|
Colsep = 0xd0d0d0,
|
|
Colsel = 0x333333,
|
|
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)
|
|
#define PopupPad (4*popupscale)
|
|
#define PopupSep popupscale
|
|
#define PopupBorder popupscale
|
|
#define PopupNumw Fontsz
|
|
#define PopupTextw (12*Fontsz)
|
|
#define PopupBasew (2*PopupPad + PopupNumw + PopupTextw)
|
|
|
|
typedef struct Str Str;
|
|
struct Str
|
|
{
|
|
Rune r[Maxrunes];
|
|
int n;
|
|
};
|
|
|
|
/*
|
|
* What one key does to a composition: text to commit, the new pending
|
|
* text, and the new raw state (Telex key history, or pending Japanese
|
|
* romaji), given the old ones in Im.
|
|
*/
|
|
typedef struct Emit Emit;
|
|
struct Emit
|
|
{
|
|
int eat;
|
|
Str s;
|
|
Str next;
|
|
Str raw;
|
|
};
|
|
|
|
typedef struct Tnode Tnode;
|
|
struct Tnode
|
|
{
|
|
char *val;
|
|
int vlen;
|
|
int child;
|
|
int sibling;
|
|
Rune c;
|
|
};
|
|
|
|
typedef struct Trie Trie;
|
|
struct Trie
|
|
{
|
|
Tnode *nodes;
|
|
int n;
|
|
int cap;
|
|
/* the last key put and its path, so a sorted file walks it once */
|
|
Str last;
|
|
int path[Maxrunes];
|
|
};
|
|
|
|
enum
|
|
{
|
|
TrieMiss,
|
|
TriePrefix,
|
|
TrieExact,
|
|
};
|
|
|
|
typedef struct Lang Lang;
|
|
typedef struct Im Im;
|
|
struct Lang
|
|
{
|
|
int lang;
|
|
char *mapname;
|
|
char *dictname;
|
|
Emit (*trans)(Im*, Rune);
|
|
void (*back)(Im*);
|
|
Trie *map;
|
|
Trie *dict;
|
|
};
|
|
|
|
struct Im
|
|
{
|
|
Lang *l;
|
|
Str pre;
|
|
/* Telex history, or the short pending romaji in a Japanese mode. */
|
|
Str raw;
|
|
Str kouho[Maxkouho];
|
|
int nkouho;
|
|
int sel;
|
|
};
|
|
|
|
typedef struct Drawcmd Drawcmd;
|
|
typedef struct Caret Caret;
|
|
typedef struct Area Area;
|
|
struct Area
|
|
{
|
|
int x;
|
|
int y;
|
|
int w;
|
|
int h;
|
|
};
|
|
|
|
struct Caret
|
|
{
|
|
int valid;
|
|
int x;
|
|
int y;
|
|
int h;
|
|
};
|
|
|
|
struct Drawcmd
|
|
{
|
|
Str pre;
|
|
Str kouho[Maxdisp];
|
|
int nkouho;
|
|
int sel;
|
|
int first;
|
|
int total;
|
|
Caret caret;
|
|
};
|
|
|
|
/*
|
|
* Popup geometry from popuplayout: n rows from row0 of the candidates,
|
|
* each Fontsz tall; a section's y is -1 when it is absent. Numbers
|
|
* start at PopupPad, text at PopupPad+PopupNumw.
|
|
*/
|
|
typedef struct Popup Popup;
|
|
struct Popup
|
|
{
|
|
int w;
|
|
int h;
|
|
int n;
|
|
int row0;
|
|
int prey;
|
|
int sepy;
|
|
int rowsy;
|
|
int textw;
|
|
int marky;
|
|
int markx;
|
|
int markw;
|
|
Str mark;
|
|
int sely;
|
|
int selw;
|
|
};
|
|
|
|
typedef struct Keyreq Keyreq;
|
|
typedef struct Keyres Keyres;
|
|
enum
|
|
{
|
|
Keypress,
|
|
Keyreset,
|
|
Keyrelease,
|
|
Keycaret,
|
|
Keycap,
|
|
};
|
|
struct Keyres
|
|
{
|
|
int eaten; /* Keycap: the caller is the owner and clientpre took. */
|
|
Str commit;
|
|
Str preedit;
|
|
};
|
|
|
|
struct Keyreq
|
|
{
|
|
void *owner; /* stable until the context's release is acknowledged */
|
|
int clientpre; /* the client draws the preedit; the popup does not */
|
|
int op;
|
|
u32int ks;
|
|
u32int mod;
|
|
Caret caret;
|
|
Channel *reply;
|
|
};
|
|
|
|
extern Lang langs[];
|
|
extern int nlang;
|
|
extern Channel *drawc;
|
|
extern Channel *keyc;
|