fix(frontends): preserve preedit lifecycle

This commit is contained in:
2026-08-15 15:05:49 +09:00
parent aeb6010f3c
commit d29d43f443
6 changed files with 164 additions and 46 deletions

View File

@@ -50,7 +50,7 @@ setpreedit(Im *im, const char *s, int n)
if(n < 0 || n > Ipcfieldmax) if(n < 0 || n > Ipcfieldmax)
n = 0; n = 0;
if(n == im->prelen && memcmp(im->pre, s, n) == 0) if(n == 0 && im->prelen == 0)
return; return;
was = im->prelen; was = im->prelen;
if(n > 0) if(n > 0)
@@ -94,12 +94,10 @@ validtext(const char *s, size_t n)
} }
static int static int
readresp(Im *im, int want, int update, char *commit, int ncommit, readresp(Im *im, int want, char *commit, int ncommit, char *pre, int npre,
Ipcresp *resp) Ipcresp *resp)
{ {
char pre[Ipcfieldmax+1]; if(ipcreadresp(im->fd, want, commit, ncommit, pre, npre,
if(ipcreadresp(im->fd, want, commit, ncommit, pre, sizeof pre,
resp) < 0) resp) < 0)
return -1; return -1;
if(!validtext(commit, resp->commitlen) || if(!validtext(commit, resp->commitlen) ||
@@ -107,8 +105,6 @@ readresp(Im *im, int want, int update, char *commit, int ncommit,
errno = EPROTO; errno = EPROTO;
return -1; return -1;
} }
if(update)
setpreedit(im, pre, resp->preeditlen);
return 0; return 0;
} }
@@ -199,6 +195,7 @@ srvconnect(Im *im)
{ {
unsigned char buf[Ipcreqsz]; unsigned char buf[Ipcreqsz];
char commit[Ipcfieldmax+1]; char commit[Ipcfieldmax+1];
char pre[Ipcfieldmax+1];
Ipcresp resp; Ipcresp resp;
if(im->fd >= 0) if(im->fd >= 0)
@@ -209,11 +206,13 @@ srvconnect(Im *im)
/* Marker zero is an old daemon's harmless key-zero response. */ /* Marker zero is an old daemon's harmless key-zero response. */
ipcpackcap(buf, im->usepreedit); ipcpackcap(buf, im->usepreedit);
if(ipcsend(im->fd, buf, sizeof buf) < 0 || if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
readresp(im, im->usepreedit, im->usepreedit, commit, readresp(im, im->usepreedit, commit, sizeof commit,
sizeof commit, &resp) < 0){ pre, sizeof pre, &resp) < 0){
srvclose(im); srvclose(im);
return -1; return -1;
} }
if(im->usepreedit)
setpreedit(im, pre, resp.preeditlen);
im->ext = resp.eaten != 0; im->ext = resp.eaten != 0;
if(!im->usepreedit) if(!im->usepreedit)
setpreedit(im, "", 0); setpreedit(im, "", 0);
@@ -293,6 +292,7 @@ sendreset(Im *im)
{ {
unsigned char buf[Ipcreqsz]; unsigned char buf[Ipcreqsz];
char commit[Ipcfieldmax+1]; char commit[Ipcfieldmax+1];
char pre[Ipcfieldmax+1];
Ipcresp resp; Ipcresp resp;
if(im->fd < 0){ if(im->fd < 0){
@@ -301,8 +301,8 @@ sendreset(Im *im)
} }
ipcpackreset(buf, im->usepreedit); ipcpackreset(buf, im->usepreedit);
if(ipcsend(im->fd, buf, sizeof buf) < 0 || if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
readresp(im, im->usepreedit, 0, commit, readresp(im, im->usepreedit, commit, sizeof commit,
sizeof commit, &resp) < 0){ pre, sizeof pre, &resp) < 0){
srvclose(im); srvclose(im);
return; return;
} }
@@ -315,6 +315,7 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
Im *im; Im *im;
unsigned char buf[Ipcreqsz]; unsigned char buf[Ipcreqsz];
char commit[Ipcfieldmax+1]; char commit[Ipcfieldmax+1];
char pre[Ipcfieldmax+1];
uint32_t key, mod; uint32_t key, mod;
Ipcresp resp; Ipcresp resp;
@@ -339,13 +340,15 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
} }
ipcpackreq(buf, im->usepreedit, mod, key); ipcpackreq(buf, im->usepreedit, mod, key);
if(ipcsend(im->fd, buf, sizeof buf) < 0 || if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
readresp(im, im->usepreedit, im->usepreedit, commit, readresp(im, im->usepreedit, commit, sizeof commit,
sizeof commit, &resp) < 0){ pre, sizeof pre, &resp) < 0){
srvclose(im); srvclose(im);
return simplefilter(ctx, ev, 0); return simplefilter(ctx, ev, 0);
} }
if(commit[0] != '\0') if(commit[0] != '\0')
g_signal_emit_by_name(ctx, "commit", commit); g_signal_emit_by_name(ctx, "commit", commit);
if(im->usepreedit)
setpreedit(im, pre, resp.preeditlen);
if(resp.eaten) if(resp.eaten)
return TRUE; return TRUE;
return simplefilter(ctx, ev, 0); return simplefilter(ctx, ev, 0);
@@ -407,6 +410,7 @@ setusepreedit(GtkIMContext *ctx, gboolean use)
Im *im; Im *im;
unsigned char buf[Ipcreqsz]; unsigned char buf[Ipcreqsz];
char commit[Ipcfieldmax+1]; char commit[Ipcfieldmax+1];
char pre[Ipcfieldmax+1];
Ipcresp resp; Ipcresp resp;
im = (Im*)ctx; im = (Im*)ctx;
@@ -422,11 +426,14 @@ setusepreedit(GtkIMContext *ctx, gboolean use)
return; return;
ipcpackcap(buf, use); ipcpackcap(buf, use);
if(ipcsend(im->fd, buf, sizeof buf) < 0 || if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
readresp(im, use, use, commit, sizeof commit, &resp) < 0 || readresp(im, use, commit, sizeof commit,
pre, sizeof pre, &resp) < 0 ||
!resp.eaten){ !resp.eaten){
srvclose(im); srvclose(im);
return; return;
} }
if(use)
setpreedit(im, pre, resp.preeditlen);
} }
static void static void

28
ibus.c
View File

@@ -55,10 +55,12 @@ static int icctr;
static int busctr; static int busctr;
static Channel *replyc; static Channel *replyc;
static const char ibusowner[] = ":1.0"; static const char ibusowner[] = ":1.0";
static Ictx *preowner;
static DBusHandlerResult onmsg(DBusConnection*, DBusMessage*, void*); static DBusHandlerResult onmsg(DBusConnection*, DBusMessage*, void*);
static DBusHandlerResult handleerror(DBusConnection*, DBusMessage*, static DBusHandlerResult handleerror(DBusConnection*, DBusMessage*,
const char*, const char*); const char*, const char*);
static void checkpreowner(void);
static void static void
unlinkaddr(void) unlinkaddr(void)
@@ -355,6 +357,8 @@ static void
dropcontext(Ictx *ctx) dropcontext(Ictx *ctx)
{ {
releasecontext(ctx); releasecontext(ctx);
if(preowner == ctx)
preowner = nil;
memset(ctx, 0, sizeof *ctx); memset(ctx, 0, sizeof *ctx);
} }
@@ -382,6 +386,8 @@ processkey(Ictx *ctx, u32int sym, u32int state, Keyres *res)
if(state & Relmask || !ctx->focused || hidden(ctx)) if(state & Relmask || !ctx->focused || hidden(ctx))
return 0; return 0;
sendrequest(ctx, Keypress, kget(sym), mget(state), res); sendrequest(ctx, Keypress, kget(sym), mget(state), res);
if(preowner != nil && preowner != ctx)
checkpreowner();
return 1; return 1;
} }
@@ -488,10 +494,29 @@ emitpreedit(Ictx *ctx, const char *text)
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &mode); dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &mode);
} }
dbus_message_set_sender(sig, ibusowner); dbus_message_set_sender(sig, ibusowner);
dbus_connection_send(ctx->conn, sig, nil); if(dbus_connection_send(ctx->conn, sig, nil)){
if(text[0] != '\0')
preowner = ctx;
else if(preowner == ctx)
preowner = nil;
}
dbus_message_unref(sig); dbus_message_unref(sig);
} }
static void
checkpreowner(void)
{
Ictx *ctx;
Keyres res;
ctx = preowner;
if(ctx == nil)
return;
sendrequest(ctx, Keycap, 0, 0, &res);
if(!res.eaten)
emitpreedit(ctx, "");
}
static DBusHandlerResult static DBusHandlerResult
handlehello(DBusConnection *c, DBusMessage *m) handlehello(DBusConnection *c, DBusMessage *m)
{ {
@@ -1183,5 +1208,6 @@ ibusthread(void *_)
while(dbus_connection_dispatch(conns[i]) == DBUS_DISPATCH_DATA_REMAINS) while(dbus_connection_dispatch(conns[i]) == DBUS_DISPATCH_DATA_REMAINS)
; ;
pruneconns(); pruneconns();
checkpreowner();
} }
} }

View File

@@ -396,7 +396,25 @@ engine_active_owner_caret(struct ct *t)
void void
engine_commit_contract(struct ct *t) engine_commit_contract(struct ct *t)
{ {
Str com; Keyres res;
Str all, com;
char owner;
int i;
init();
im.l = getlang(LangKO);
sclear(&all);
for(i = 0; i < 3; i++){
res = ownerrequestcap(&owner, Cclientpreedit,
Keypress, 'z', 0);
CT_CHECK(t, res.eaten);
checkstr(t, "repeated consonant commit",
i == 0 ? "" : "", &res.commit);
checkstr(t, "repeated consonant preedit", "", &res.preedit);
sappend(&all, &res.commit);
}
sappend(&all, &res.preedit);
checkstr(t, "repeated consonant total", "ㅋㅋㅋ", &all);
init(); init();
im.l = getlang(LangJP); im.l = getlang(LangJP);

View File

@@ -37,6 +37,7 @@ enum
enum enum
{ {
Rnormal, Rnormal,
Rcommitpre,
Rbadcommit, Rbadcommit,
Rbadpreedit, Rbadpreedit,
}; };
@@ -156,7 +157,10 @@ sendresponse(Server *s, int fd, int eaten, int want, int key)
commit = ""; commit = "";
ncommit = 0; ncommit = 0;
npreedit = strlen(pre); npreedit = strlen(pre);
if(key && response == Rbadcommit){ if(key && response == Rcommitpre){
commit = pre;
ncommit = npreedit;
}else if(key && response == Rbadcommit){
commit = "\xc3("; commit = "\xc3(";
ncommit = 2; ncommit = 2;
}else if(key && response == Rbadpreedit){ }else if(key && response == Rbadpreedit){
@@ -581,6 +585,7 @@ commit(GtkIMContext *ctx, gchar *s, Siglog *l)
{ {
(void)ctx; (void)ctx;
snprintf(l->commit, sizeof l->commit, "%s", s); snprintf(l->commit, sizeof l->commit, "%s", s);
l->pre[0] = '\0';
logsig(l, 'K'); logsig(l, 'K');
} }
@@ -721,7 +726,7 @@ main(int argc, char **argv)
rect.height = 19; rect.height = 19;
gtk_im_context_set_cursor_location(ctx, &rect); gtk_im_context_set_cursor_location(ctx, &rect);
Check(eventcount(&srv) == 0, "cursor reporting opened the socket"); Check(eventcount(&srv) == 0, "cursor reporting opened the socket");
setpre(&srv, "pre"); setpre(&srv, "");
Check(key(ctx, win, GDK_KEY_a), "initial key was not eaten"); Check(key(ctx, win, GDK_KEY_a), "initial key was not eaten");
Check(waitcount(&srv, 3), "initial protocol frames timed out"); Check(waitcount(&srv, 3), "initial protocol frames timed out");
e = getevent(&srv, 0); e = getevent(&srv, 0);
@@ -739,8 +744,22 @@ main(int argc, char **argv)
(oy + rect.y) * scale, rect.height * scale); (oy + rect.y) * scale, rect.height * scale);
e = getevent(&srv, 2); e = getevent(&srv, 2);
Check(e.type == Ekey && e.want == 1, "initial key framing changed"); Check(e.type == Ekey && e.want == 1, "initial key framing changed");
Check(strcmp(log.event, "SC") == 0 && strcmp(log.pre, "pre") == 0, Check(log.n == 0, "initial empty preedit emitted %s", log.event);
"initial preedit signals were %s (%s)", log.event, log.pre); setpre(&srv, "");
clearlog(&log);
Check(key(ctx, win, GDK_KEY_z), "first preedit key was not eaten");
Check(strcmp(log.event, "SC") == 0 && strcmp(log.pre, "") == 0,
"first preedit signals were %s (%s)", log.event, log.pre);
clearlog(&log);
setreply(&srv, 1, Rcommitpre, 0);
Check(key(ctx, win, GDK_KEY_z), "repeated preedit key was not eaten");
Check(strcmp(log.event, "KC") == 0 &&
strcmp(log.commit, "") == 0 && strcmp(log.pre, "") == 0,
"repeated preedit signals were %s (commit %s, preedit %s)",
log.event, log.commit, log.pre);
Check(g_utf8_strlen(log.commit, -1) + g_utf8_strlen(log.pre, -1) == 2,
"two physical keys produced commit %s and preedit %s",
log.commit, log.pre);
/* Unconsumed input uses GtkIMContextSimple, including its stateful forms. */ /* Unconsumed input uses GtkIMContextSimple, including its stateful forms. */
setpre(&srv, ""); setpre(&srv, "");

View File

@@ -8,12 +8,19 @@ typedef struct Log Log;
struct Log struct Log
{ {
GMainLoop *loop; GMainLoop *loop;
int *waiting;
IBusInputContext *a;
IBusInputContext *clearctx;
int legacy; int legacy;
int modern; int modern;
int commit; int commit;
int invalid; int invalid;
int sawpreedit; int sawpreedit;
int sawcommit; int sawcommit;
int done;
int transfer;
int atext;
int aclear;
}; };
static void static void
@@ -38,10 +45,25 @@ legacy(IBusInputContext *ctx, IBusText *text, guint cursor,
a->start_index != 0 || a->end_index != 1 || a->start_index != 0 || a->end_index != 1 ||
ibus_attr_list_get(attrs, 1) != NULL) ibus_attr_list_get(attrs, 1) != NULL)
log->invalid = 1; log->invalid = 1;
else else{
log->sawpreedit = 1; log->sawpreedit = 1;
if(log->transfer && ctx == log->a)
log->atext = 1;
}
} }
if(log->loop != NULL && log->sawpreedit && log->sawcommit) if(log->transfer && log->atext && !log->aclear && s[0] == '\0'){
if(cursor != 0 || visible)
log->invalid = 1;
else{
log->clearctx = ctx;
if(ctx == log->a)
log->aclear = 1;
else
log->invalid = 1;
}
}
log->done = log->sawpreedit && log->sawcommit;
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
g_main_loop_quit(log->loop); g_main_loop_quit(log->loop);
} }
@@ -80,7 +102,8 @@ commit(IBusInputContext *ctx, IBusText *text, void *arg)
log->invalid = 1; log->invalid = 1;
else else
log->sawcommit = 1; log->sawcommit = 1;
if(log->loop != NULL && log->sawpreedit && log->sawcommit) log->done = log->sawpreedit && log->sawcommit;
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
g_main_loop_quit(log->loop); g_main_loop_quit(log->loop);
} }
@@ -92,12 +115,13 @@ timeout(void *arg)
} }
static int static int
waitdone(Log *log) waitflag(Log *log, int *flag)
{ {
guint timer; guint timer;
if(log->sawpreedit && log->sawcommit) if(*flag)
return 1; return 1;
log->waiting = flag;
log->loop = g_main_loop_new(NULL, FALSE); log->loop = g_main_loop_new(NULL, FALSE);
timer = g_timeout_add_seconds(4, timeout, log->loop); timer = g_timeout_add_seconds(4, timeout, log->loop);
g_main_loop_run(log->loop); g_main_loop_run(log->loop);
@@ -105,14 +129,15 @@ waitdone(Log *log)
g_source_remove(timer); g_source_remove(timer);
g_main_loop_unref(log->loop); g_main_loop_unref(log->loop);
log->loop = NULL; log->loop = NULL;
return log->sawpreedit && log->sawcommit; log->waiting = NULL;
return *flag;
} }
int int
main(int argc, char **argv) main(int argc, char **argv)
{ {
IBusBus *bus; IBusBus *bus;
IBusInputContext *ctx; IBusInputContext *a, *b;
Log log; Log log;
int ok; int ok;
@@ -127,6 +152,7 @@ main(int argc, char **argv)
return 1; return 1;
} }
memset(&log, 0, sizeof log); memset(&log, 0, sizeof log);
b = NULL;
ibus_init(); ibus_init();
bus = ibus_bus_new(); bus = ibus_bus_new();
if(bus == NULL || !ibus_bus_is_connected(bus)){ if(bus == NULL || !ibus_bus_is_connected(bus)){
@@ -134,36 +160,56 @@ main(int argc, char **argv)
if(bus != NULL) g_object_unref(bus); if(bus != NULL) g_object_unref(bus);
return 1; return 1;
} }
ctx = ibus_bus_create_input_context(bus, "strans-libibus-smoke"); a = ibus_bus_create_input_context(bus, "strans-libibus-smoke-a");
if(ctx == NULL){ if(a == NULL){
fprintf(stderr, "ibus_client_smoke: cannot create input context\n"); fprintf(stderr, "ibus_client_smoke: cannot create input context\n");
g_object_unref(bus); g_object_unref(bus);
return 1; return 1;
} }
g_signal_connect(ctx, "update-preedit-text", G_CALLBACK(legacy), &log); log.a = a;
g_signal_connect(ctx, "update-preedit-text-with-mode", G_CALLBACK(modern), g_signal_connect(a, "update-preedit-text", G_CALLBACK(legacy), &log);
g_signal_connect(a, "update-preedit-text-with-mode", G_CALLBACK(modern),
&log); &log);
g_signal_connect(ctx, "commit-text", G_CALLBACK(commit), &log); g_signal_connect(a, "commit-text", G_CALLBACK(commit), &log);
ibus_input_context_set_capabilities(ctx, IBUS_CAP_PREEDIT_TEXT); ibus_input_context_set_capabilities(a, IBUS_CAP_PREEDIT_TEXT);
ibus_input_context_focus_in(ctx); ibus_input_context_focus_in(a);
ok = ibus_input_context_process_key_event(ctx, 'n', 0, IBUS_CONTROL_MASK) && ok = ibus_input_context_process_key_event(a, 'n', 0, IBUS_CONTROL_MASK) &&
ibus_input_context_process_key_event(ctx, 'k', 0, 0) && ibus_input_context_process_key_event(a, 'k', 0, 0) &&
ibus_input_context_process_key_event(ctx, 'a', 0, 0) && ibus_input_context_process_key_event(a, 'a', 0, 0) &&
ibus_input_context_process_key_event(ctx, '0', 0, 0) && ibus_input_context_process_key_event(a, '0', 0, 0) &&
waitdone(&log); waitflag(&log, &log.done);
ibus_input_context_focus_out(ctx); b = ibus_bus_create_input_context(bus, "strans-libibus-smoke-b");
if(log.legacy == 0 || log.modern != 0 || log.commit != 1 || if(b == NULL)
log.invalid || !log.sawpreedit || !log.sawcommit)
ok = 0; ok = 0;
g_object_unref(ctx); else{
g_signal_connect(b, "update-preedit-text", G_CALLBACK(legacy), &log);
g_signal_connect(b, "update-preedit-text-with-mode",
G_CALLBACK(modern), &log);
ibus_input_context_set_capabilities(b, IBUS_CAP_PREEDIT_TEXT);
log.transfer = 1;
ok = ok && ibus_input_context_process_key_event(a, 'k', 0, 0) &&
waitflag(&log, &log.atext);
ibus_input_context_focus_in(b);
ok = ok && ibus_input_context_process_key_event(b, 'n', 0, 0) &&
waitflag(&log, &log.aclear);
ibus_input_context_focus_out(b);
}
ibus_input_context_focus_out(a);
if(log.legacy == 0 || log.modern != 0 || log.commit != 1 ||
log.invalid || !log.done || !log.atext || !log.aclear ||
log.clearctx != a)
ok = 0;
if(b != NULL)
g_object_unref(b);
g_object_unref(a);
g_object_unref(bus); g_object_unref(bus);
if(!ok){ if(!ok){
fprintf(stderr, fprintf(stderr,
"ibus_client_smoke: legacy=%d modern=%d commit=%d invalid=%d preedit=%d committed=%d\n", "ibus_client_smoke: legacy=%d modern=%d commit=%d invalid=%d preedit=%d committed=%d transfer-text=%d a-clear=%d\n",
log.legacy, log.modern, log.commit, log.invalid, log.legacy, log.modern, log.commit, log.invalid,
log.sawpreedit, log.sawcommit); log.sawpreedit, log.sawcommit, log.atext, log.aclear);
return 1; return 1;
} }
printf("official libibus client preedit and commit: ok\n"); printf("official libibus client preedit, commit, and owner clear: ok\n");
return 0; return 0;
} }

View File

@@ -199,6 +199,7 @@ ibusbegin(struct ct *t, Ibusfix *f)
memset(contexts, 0, sizeof contexts); memset(contexts, 0, sizeof contexts);
memset(conns, 0, sizeof conns); memset(conns, 0, sizeof conns);
nconns = 0; nconns = 0;
preowner = nil;
while(channbrecv(drawc, &dc) > 0) while(channbrecv(drawc, &dc) > 0)
; ;
testengineinit(LangEN); testengineinit(LangEN);
@@ -234,6 +235,7 @@ ibusend(Ibusfix *f)
memset(contexts, 0, sizeof contexts); memset(contexts, 0, sizeof contexts);
memset(conns, 0, sizeof conns); memset(conns, 0, sizeof conns);
nconns = 0; nconns = 0;
preowner = nil;
testengineinit(LangEN); testengineinit(LangEN);
while(channbrecv(drawc, &dc) > 0) while(channbrecv(drawc, &dc) > 0)
; ;