fix: assign one active engine owner

This commit is contained in:
2026-08-12 15:41:46 +09:00
parent 9c244d1dd1
commit 5ba5cf209c
14 changed files with 212 additions and 13 deletions

8
dat.h
View File

@@ -128,6 +128,12 @@ struct Drawcmd
typedef struct Keyreq Keyreq;
typedef struct Keyres Keyres;
enum
{
Keypress,
Keyreset,
Keyrelease,
};
struct Keyres
{
int eaten;
@@ -137,6 +143,8 @@ struct Keyres
struct Keyreq
{
uvlong owner;
int op;
u32int ks;
u32int mod;
int want; /* nonzero: include preedit in reply */

1
fn.h
View File

@@ -35,6 +35,7 @@ Emit transvi(Im*, Rune);
void backko(Im*);
void backvi(Im*);
void dictsend(Im*, Str*);
uvlong ownernew(void);
int srvreadkey(int, Keyreq*);
void srvthread(void*);

View File

@@ -120,7 +120,7 @@ sendreset(Im *im)
srvclose(im);
return;
}
ipcpackreq(buf, 1, 0, Kesc);
ipcpackreset(buf, 1);
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
readresp(im, resp, sizeof(resp)) < 0)
srvclose(im);

12
ibus.c
View File

@@ -32,6 +32,7 @@ static char addrfile[256];
static int icctr;
static int busctr;
static Channel *replyc;
static uvlong owner;
static DBusHandlerResult onmsg(DBusConnection*, DBusMessage*, void*);
@@ -212,6 +213,8 @@ sendkey(u32int ks, u32int mod, Keyres *res)
{
Keyreq kr;
kr.owner = owner;
kr.op = Keypress;
kr.ks = ks;
kr.mod = mod;
kr.want = 1;
@@ -223,9 +226,15 @@ sendkey(u32int ks, u32int mod, Keyres *res)
static void
sendreset(void)
{
Keyreq kr;
Keyres res;
sendkey(Kesc, 0, &res);
memset(&kr, 0, sizeof kr);
kr.owner = owner;
kr.op = Keyreset;
kr.reply = replyc;
chansend(keyc, &kr);
chanrecv(replyc, &res);
}
static void
@@ -561,6 +570,7 @@ ibusthread(void *_)
if(ibusinit() < 0)
return;
replyc = chancreate(sizeof(Keyres), 0);
owner = ownernew();
for(;;){
n = 0;
for(i = 0; i < nwatches && n < Maxwatches; i++){

17
ipc.c
View File

@@ -25,7 +25,7 @@ void
ipcpackreq(unsigned char req[Ipcreqsz], int want, unsigned int mod,
unsigned int key)
{
req[0] = want != 0;
req[0] = want ? Ipcreqwant : 0;
req[1] = mod;
req[2] = key;
req[3] = key >> 8;
@@ -33,11 +33,18 @@ ipcpackreq(unsigned char req[Ipcreqsz], int want, unsigned int mod,
req[5] = key >> 24;
}
void
ipcpackreset(unsigned char req[Ipcreqsz], int want)
{
memset(req, 0, Ipcreqsz);
req[0] = Ipcreqreset | (want ? Ipcreqwant : 0);
}
void
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, unsigned int *mod,
unsigned int *key)
{
*want = req[0] != 0;
*want = (req[0] & Ipcreqwant) != 0;
*mod = req[1] & Mmask;
*key = (unsigned int)req[2] |
((unsigned int)req[3] << 8) |
@@ -45,6 +52,12 @@ ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, unsigned int *mod,
((unsigned int)req[5] << 24);
}
int
ipcreqreset(const unsigned char req[Ipcreqsz])
{
return (req[0] & Ipcreqreset) != 0;
}
int
ipcpackresp(unsigned char *dst, size_t cap, int eaten,
const char *commit, size_t ncommit, const char *preedit, size_t npreedit,

10
ipc.h
View File

@@ -6,7 +6,10 @@
#define IPCPATH "/tmp/strans.%d"
/*
* Request: [want-preedit, modifiers, key byte 0, ..., key byte 3].
* Request: [flags, modifiers, key byte 0, ..., key byte 3].
* Flags request preedit and distinguish lifecycle reset from physical Escape.
* The server assigns the connection's owner generation; it is not sent on
* the wire.
* Only Mmask modifier bits are retained.
* Response: [eaten, commit-length-low, commit-length-high, commit...],
* followed, when requested, by
@@ -16,6 +19,9 @@
enum
{
Ipcreqwant = 1<<0,
Ipcreqreset = 1<<1,
Kspec = 0x110000,
Kback = Kspec|0x08,
Ktab = Kspec|0x09,
@@ -48,7 +54,9 @@ struct Ipcresp
};
void ipcpackreq(unsigned char[Ipcreqsz], int, unsigned int, unsigned int);
void ipcpackreset(unsigned char[Ipcreqsz], int);
void ipcunpackreq(const unsigned char[Ipcreqsz], int*, unsigned int*, unsigned int*);
int ipcreqreset(const unsigned char[Ipcreqsz]);
int ipcpackresp(unsigned char*, size_t, int, const char*, size_t, const char*, size_t, int);
int ipcreadn(int, void*, size_t);
int ipcsend(int, const void*, size_t);

25
srv.c
View File

@@ -4,6 +4,13 @@
static char adir[40];
static Channel *clientc;
typedef struct Client Client;
struct Client
{
int fd;
uvlong owner;
};
int
srvreadkey(int fd, Keyreq *kr)
{
@@ -14,6 +21,7 @@ srvreadkey(int fd, Keyreq *kr)
if(ipcreadn(fd, req, sizeof req) < 0)
return -1;
ipcunpackreq(req, &want, &mod, &ks);
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
kr->ks = ks;
kr->mod = mod;
kr->want = want;
@@ -23,6 +31,7 @@ srvreadkey(int fd, Keyreq *kr)
static void
clientthread(void *arg)
{
Client *client;
Channel *reply;
int fd;
Keyreq kr;
@@ -32,10 +41,12 @@ clientthread(void *arg)
int n, ncommit, npreedit;
uchar token;
fd = (int)(uintptr)arg;
client = arg;
fd = client->fd;
threadsetname("client %d", fd);
reply = chancreate(sizeof(Keyres), 0);
kr.reply = reply;
kr.owner = client->owner;
while(srvreadkey(fd, &kr) == 0){
chansend(keyc, &kr);
chanrecv(reply, &res);
@@ -46,8 +57,13 @@ clientthread(void *arg)
if(n < 0 || ipcsend(fd, out, n) < 0)
break;
}
kr.op = Keyrelease;
kr.want = 0;
chansend(keyc, &kr);
chanrecv(reply, &res);
chanfree(reply);
close(fd);
free(client);
chanrecv(clientc, &token);
}
@@ -65,6 +81,7 @@ srvinit(void)
void
srvthread(void*)
{
Client *client;
char ldir[40];
int fd;
uchar token;
@@ -81,9 +98,13 @@ srvthread(void*)
close(fd);
continue;
}
if(proccreate(clientthread, (void*)(uintptr)fd, 8192) < 0){
client = emalloc(sizeof *client);
client->fd = fd;
client->owner = ownernew();
if(proccreate(clientthread, client, 8192) < 0){
chanrecv(clientc, &token);
close(fd);
free(client);
}
}
}

View File

@@ -4,6 +4,9 @@
static Im im;
static int popup = 0;
static int visible = 0;
static Lock ownerlock;
static uvlong ownerctr;
static uvlong activeowner;
static void dictqmap(Im*);
static void dictqjp(Im*);
static void backjp(Im*);
@@ -18,6 +21,19 @@ struct Search
};
static Search search;
uvlong
ownernew(void)
{
uvlong n;
lock(&ownerlock);
n = ++ownerctr;
if(n == 0)
n = ++ownerctr;
unlock(&ownerlock);
return n;
}
Lang langs[] = {
{LangEN, nil, nil, nil, nil, nil, nil, nil},
{LangJP, "hira", "kanji", transmap, backjp, dictqjp, nil, nil},
@@ -128,6 +144,9 @@ reset(void)
{
sclear(&im.pre);
sclear(&im.raw);
search.on = 0;
sclear(&search.raw);
sclear(&search.text);
clearkouho();
show();
}
@@ -715,9 +734,16 @@ init(void)
memset(&im, 0, sizeof(im));
im.l = getlang(LangEN);
memset(&search, 0, sizeof search);
activeowner = 0;
visible = 0;
}
static int
meaningful(Keyreq *kr)
{
return kr->ks != 0 && !ismodkey(kr->ks);
}
static void
imhandlekey(Keyreq *kr)
{
@@ -725,8 +751,30 @@ imhandlekey(Keyreq *kr)
sclear(&res.commit);
sclear(&res.preedit);
res.eaten = keystroke(kr->ks, kr->mod, &res.commit);
if(kr->want){
res.eaten = 1;
switch(kr->op){
case Keyrelease:
if(kr->owner == activeowner){
reset();
activeowner = 0;
}
break;
case Keyreset:
if(kr->owner == activeowner)
reset();
break;
default:
res.eaten = 0;
if(meaningful(kr)){
if(kr->owner != activeowner){
reset();
activeowner = kr->owner;
}
res.eaten = keystroke(kr->ks, kr->mod, &res.commit);
}
break;
}
if(kr->want && kr->owner == activeowner){
if(search.on)
res.preedit = search.text;
else

View File

@@ -155,6 +155,75 @@ engine_dictionary_queue_latest_wins(struct ct *t)
dictreqc = saved;
}
static Keyres
ownerrequest(uvlong owner, int op, Rune key, u32int mod)
{
Channel *reply;
Keyreq req;
Keyres res;
reply = chancreate(sizeof(Keyres), 1);
memset(&req, 0, sizeof req);
req.owner = owner;
req.op = op;
req.ks = key;
req.mod = mod;
req.want = 1;
req.reply = reply;
imhandlekey(&req);
chanrecv(reply, &res);
chanfree(reply);
return res;
}
void
engine_active_owner_lifecycle(struct ct *t)
{
Keyres res;
Str shown;
uvlong a, b, c;
a = ownernew();
b = ownernew();
c = ownernew();
CT_CHECK(t, a != 0 && b != 0 && c != 0);
CT_CHECK(t, a != b && b != c && a != c);
init();
im.l = getlang(LangJP);
res = ownerrequest(a, Keypress, 'k', 0);
CT_CHECK(t, res.eaten);
res = ownerrequest(a, Keypress, 'a', 0);
checkstr(t, "first owner preedit", "", &res.preedit);
CT_EQ_UINT(t, a, activeowner);
res = ownerrequest(b, Keypress, 'n', 0);
checkstr(t, "takeover resets old preedit", "", &res.preedit);
CT_EQ_UINT(t, b, activeowner);
res = ownerrequest(a, Keyreset, 0, 0);
CT_EQ_INT(t, 0, res.preedit.n);
shown = shownpre(&im);
checkstr(t, "stale reset preserves owner", "", &shown);
CT_EQ_UINT(t, b, activeowner);
res = ownerrequest(b, Keypress, 'y', 0);
res = ownerrequest(b, Keypress, 'a', 0);
checkstr(t, "current owner continues", "にゃ", &res.preedit);
ownerrequest(a, Keyrelease, 0, 0);
shown = shownpre(&im);
checkstr(t, "stale release preserves owner", "にゃ", &shown);
CT_EQ_UINT(t, b, activeowner);
ownerrequest(b, Keyrelease, 0, 0);
CT_EQ_UINT(t, 0, activeowner);
CT_EQ_INT(t, 0, im.pre.n);
CT_EQ_INT(t, 0, im.raw.n);
ownerrequest(c, Keypress, Kmodfirst, 0);
CT_EQ_UINT(t, 0, activeowner);
res = ownerrequest(c, Keypress, 'k', 0);
CT_EQ_UINT(t, c, activeowner);
checkstr(t, "next owner first key", "k", &res.preedit);
}
void
engine_commit_contract(struct ct *t)
{

View File

@@ -12,4 +12,12 @@ ipc_masks_modifiers(struct ct *t)
CT_EQ_INT(t, 1, want);
CT_EQ_UINT(t, Mctrl|Malt, mod);
CT_EQ_UINT(t, 0x1f642, key);
CT_CHECK(t, !ipcreqreset(buf));
ipcpackreset(buf, 1);
ipcunpackreq(buf, &want, &mod, &key);
CT_EQ_INT(t, 1, want);
CT_EQ_UINT(t, 0, mod);
CT_EQ_UINT(t, 0, key);
CT_CHECK(t, ipcreqreset(buf));
}

View File

@@ -34,6 +34,7 @@ void engine_backspace_clears_candidates(struct ct*);
void engine_selects_visible_candidate(struct ct*);
void engine_candidate_shortcut_modifiers(struct ct*);
void engine_dictionary_queue_latest_wins(struct ct*);
void engine_active_owner_lifecycle(struct ct*);
void engine_commit_contract(struct ct*);
void engine_language_switch_state(struct ct*);
void engine_telex_history_bound(struct ct*);

View File

@@ -83,6 +83,7 @@ static const struct ct_test tests[] = {
{ "engine/selects-visible-candidate", engine_selects_visible_candidate },
{ "engine/candidate-shortcut-modifiers", engine_candidate_shortcut_modifiers },
{ "engine/dictionary-queue-latest", engine_dictionary_queue_latest_wins },
{ "engine/active-owner-lifecycle", engine_active_owner_lifecycle },
{ "engine/commit-contract", engine_commit_contract },
{ "engine/language-switch-state", engine_language_switch_state },
{ "engine/telex-history-bound", engine_telex_history_bound },

View File

@@ -26,12 +26,15 @@ static int active;
static int pending;
static u32int imserial;
static Channel *replyc;
static uvlong owner;
static void
sendkey(u32int ks, u32int mod, Keyres *res)
{
Keyreq kr;
kr.owner = owner;
kr.op = Keypress;
kr.ks = ks;
kr.mod = mod;
kr.want = 1;
@@ -41,11 +44,17 @@ sendkey(u32int ks, u32int mod, Keyres *res)
}
static void
sendreset(void)
sendrelease(void)
{
Keyreq kr;
Keyres res;
sendkey(Kesc, 0, &res);
memset(&kr, 0, sizeof kr);
kr.owner = owner;
kr.op = Keyrelease;
kr.reply = replyc;
chansend(keyc, &kr);
chanrecv(replyc, &res);
}
static u32int
@@ -243,13 +252,15 @@ im_done(void *data, struct zwp_input_method_v2 *m)
imserial++;
if(pending && !active){
active = 1;
owner = ownernew();
grab = zwp_input_method_v2_grab_keyboard(im);
if(grab != nil)
zwp_input_method_keyboard_grab_v2_add_listener(grab,
&grab_listener, nil);
}else if(!pending && active){
active = 0;
sendreset();
sendrelease();
owner = 0;
if(grab != nil){
zwp_input_method_keyboard_grab_v2_release(grab);
grab = nil;

View File

@@ -183,7 +183,7 @@ reset(xcb_im_input_context_t *ic)
{
unsigned char buf[Ipcreqsz];
ipcpackreq(buf, 0, 0, Kesc);
ipcpackreset(buf, 0);
if(srvconnect() < 0)
return;
if(ipcsend(srvfd, buf, sizeof buf) < 0 || readresp(ic) < 0)