From 5ba5cf209c91a34ba3460746e4fb5587ddf405e5 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 12 Aug 2026 15:41:46 +0900 Subject: [PATCH] fix: assign one active engine owner --- dat.h | 8 ++++++ fn.h | 1 + gtk/main.c | 2 +- ibus.c | 12 +++++++- ipc.c | 17 +++++++++-- ipc.h | 10 ++++++- srv.c | 25 ++++++++++++++-- strans.c | 52 ++++++++++++++++++++++++++++++++-- tests/engine_test.c | 69 +++++++++++++++++++++++++++++++++++++++++++++ tests/ipc_test.c | 8 ++++++ tests/test.h | 1 + tests/unit_test.c | 1 + wayland.c | 17 +++++++++-- xim/main.c | 2 +- 14 files changed, 212 insertions(+), 13 deletions(-) diff --git a/dat.h b/dat.h index a2a4a78..cd9fa82 100644 --- a/dat.h +++ b/dat.h @@ -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 */ diff --git a/fn.h b/fn.h index dac431d..68dc10f 100644 --- a/fn.h +++ b/fn.h @@ -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*); diff --git a/gtk/main.c b/gtk/main.c index de30a91..e7dc40b 100644 --- a/gtk/main.c +++ b/gtk/main.c @@ -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); diff --git a/ibus.c b/ibus.c index 825e740..b74ba0d 100644 --- a/ibus.c +++ b/ibus.c @@ -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++){ diff --git a/ipc.c b/ipc.c index 6537cb1..275bb09 100644 --- a/ipc.c +++ b/ipc.c @@ -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, diff --git a/ipc.h b/ipc.h index bf52867..a56f588 100644 --- a/ipc.h +++ b/ipc.h @@ -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); diff --git a/srv.c b/srv.c index 41e4b93..72c80ae 100644 --- a/srv.c +++ b/srv.c @@ -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); } } } diff --git a/strans.c b/strans.c index 73681ad..21a1be3 100644 --- a/strans.c +++ b/strans.c @@ -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 diff --git a/tests/engine_test.c b/tests/engine_test.c index de8ad76..a48e0fa 100644 --- a/tests/engine_test.c +++ b/tests/engine_test.c @@ -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) { diff --git a/tests/ipc_test.c b/tests/ipc_test.c index e6dfb86..3c9bdaf 100644 --- a/tests/ipc_test.c +++ b/tests/ipc_test.c @@ -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)); } diff --git a/tests/test.h b/tests/test.h index db0519a..70d030b 100644 --- a/tests/test.h +++ b/tests/test.h @@ -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*); diff --git a/tests/unit_test.c b/tests/unit_test.c index b101aa0..cd1b9c0 100644 --- a/tests/unit_test.c +++ b/tests/unit_test.c @@ -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 }, diff --git a/wayland.c b/wayland.c index eb2cbe2..8f18caa 100644 --- a/wayland.c +++ b/wayland.c @@ -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; diff --git a/xim/main.c b/xim/main.c index 90d3f13..3961ec6 100644 --- a/xim/main.c +++ b/xim/main.c @@ -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)