diff --git a/gtk/main.c b/gtk/main.c index 0fa2e5d..09f443d 100644 --- a/gtk/main.c +++ b/gtk/main.c @@ -50,7 +50,7 @@ setpreedit(Im *im, const char *s, int n) if(n < 0 || n > Ipcfieldmax) n = 0; - if(n == im->prelen && memcmp(im->pre, s, n) == 0) + if(n == 0 && im->prelen == 0) return; was = im->prelen; if(n > 0) @@ -94,12 +94,10 @@ validtext(const char *s, size_t n) } 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) { - char pre[Ipcfieldmax+1]; - - if(ipcreadresp(im->fd, want, commit, ncommit, pre, sizeof pre, + if(ipcreadresp(im->fd, want, commit, ncommit, pre, npre, resp) < 0) return -1; if(!validtext(commit, resp->commitlen) || @@ -107,8 +105,6 @@ readresp(Im *im, int want, int update, char *commit, int ncommit, errno = EPROTO; return -1; } - if(update) - setpreedit(im, pre, resp->preeditlen); return 0; } @@ -199,6 +195,7 @@ srvconnect(Im *im) { unsigned char buf[Ipcreqsz]; char commit[Ipcfieldmax+1]; + char pre[Ipcfieldmax+1]; Ipcresp resp; if(im->fd >= 0) @@ -209,11 +206,13 @@ srvconnect(Im *im) /* Marker zero is an old daemon's harmless key-zero response. */ ipcpackcap(buf, im->usepreedit); if(ipcsend(im->fd, buf, sizeof buf) < 0 || - readresp(im, im->usepreedit, im->usepreedit, commit, - sizeof commit, &resp) < 0){ + readresp(im, im->usepreedit, commit, sizeof commit, + pre, sizeof pre, &resp) < 0){ srvclose(im); return -1; } + if(im->usepreedit) + setpreedit(im, pre, resp.preeditlen); im->ext = resp.eaten != 0; if(!im->usepreedit) setpreedit(im, "", 0); @@ -293,6 +292,7 @@ sendreset(Im *im) { unsigned char buf[Ipcreqsz]; char commit[Ipcfieldmax+1]; + char pre[Ipcfieldmax+1]; Ipcresp resp; if(im->fd < 0){ @@ -301,8 +301,8 @@ sendreset(Im *im) } ipcpackreset(buf, im->usepreedit); if(ipcsend(im->fd, buf, sizeof buf) < 0 || - readresp(im, im->usepreedit, 0, commit, - sizeof commit, &resp) < 0){ + readresp(im, im->usepreedit, commit, sizeof commit, + pre, sizeof pre, &resp) < 0){ srvclose(im); return; } @@ -315,6 +315,7 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev) Im *im; unsigned char buf[Ipcreqsz]; char commit[Ipcfieldmax+1]; + char pre[Ipcfieldmax+1]; uint32_t key, mod; Ipcresp resp; @@ -339,13 +340,15 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev) } ipcpackreq(buf, im->usepreedit, mod, key); if(ipcsend(im->fd, buf, sizeof buf) < 0 || - readresp(im, im->usepreedit, im->usepreedit, commit, - sizeof commit, &resp) < 0){ + readresp(im, im->usepreedit, commit, sizeof commit, + pre, sizeof pre, &resp) < 0){ srvclose(im); return simplefilter(ctx, ev, 0); } if(commit[0] != '\0') g_signal_emit_by_name(ctx, "commit", commit); + if(im->usepreedit) + setpreedit(im, pre, resp.preeditlen); if(resp.eaten) return TRUE; return simplefilter(ctx, ev, 0); @@ -407,6 +410,7 @@ setusepreedit(GtkIMContext *ctx, gboolean use) Im *im; unsigned char buf[Ipcreqsz]; char commit[Ipcfieldmax+1]; + char pre[Ipcfieldmax+1]; Ipcresp resp; im = (Im*)ctx; @@ -422,11 +426,14 @@ setusepreedit(GtkIMContext *ctx, gboolean use) return; ipcpackcap(buf, use); 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){ srvclose(im); return; } + if(use) + setpreedit(im, pre, resp.preeditlen); } static void diff --git a/ibus.c b/ibus.c index e7de213..83c295b 100644 --- a/ibus.c +++ b/ibus.c @@ -55,10 +55,12 @@ static int icctr; static int busctr; static Channel *replyc; static const char ibusowner[] = ":1.0"; +static Ictx *preowner; static DBusHandlerResult onmsg(DBusConnection*, DBusMessage*, void*); static DBusHandlerResult handleerror(DBusConnection*, DBusMessage*, const char*, const char*); +static void checkpreowner(void); static void unlinkaddr(void) @@ -355,6 +357,8 @@ static void dropcontext(Ictx *ctx) { releasecontext(ctx); + if(preowner == ctx) + preowner = nil; 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)) return 0; sendrequest(ctx, Keypress, kget(sym), mget(state), res); + if(preowner != nil && preowner != ctx) + checkpreowner(); return 1; } @@ -488,10 +494,29 @@ emitpreedit(Ictx *ctx, const char *text) dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &mode); } 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); } +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 handlehello(DBusConnection *c, DBusMessage *m) { @@ -1183,5 +1208,6 @@ ibusthread(void *_) while(dbus_connection_dispatch(conns[i]) == DBUS_DISPATCH_DATA_REMAINS) ; pruneconns(); + checkpreowner(); } } diff --git a/tests/engine_test.c b/tests/engine_test.c index 02fa826..aacc0d9 100644 --- a/tests/engine_test.c +++ b/tests/engine_test.c @@ -396,7 +396,25 @@ engine_active_owner_caret(struct ct *t) void 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(); im.l = getlang(LangJP); diff --git a/tests/gtk_live_test.c b/tests/gtk_live_test.c index f264089..9c4b05a 100644 --- a/tests/gtk_live_test.c +++ b/tests/gtk_live_test.c @@ -37,6 +37,7 @@ enum enum { Rnormal, + Rcommitpre, Rbadcommit, Rbadpreedit, }; @@ -156,7 +157,10 @@ sendresponse(Server *s, int fd, int eaten, int want, int key) commit = ""; ncommit = 0; npreedit = strlen(pre); - if(key && response == Rbadcommit){ + if(key && response == Rcommitpre){ + commit = pre; + ncommit = npreedit; + }else if(key && response == Rbadcommit){ commit = "\xc3("; ncommit = 2; }else if(key && response == Rbadpreedit){ @@ -581,6 +585,7 @@ commit(GtkIMContext *ctx, gchar *s, Siglog *l) { (void)ctx; snprintf(l->commit, sizeof l->commit, "%s", s); + l->pre[0] = '\0'; logsig(l, 'K'); } @@ -721,7 +726,7 @@ main(int argc, char **argv) rect.height = 19; gtk_im_context_set_cursor_location(ctx, &rect); 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(waitcount(&srv, 3), "initial protocol frames timed out"); e = getevent(&srv, 0); @@ -739,8 +744,22 @@ main(int argc, char **argv) (oy + rect.y) * scale, rect.height * scale); e = getevent(&srv, 2); Check(e.type == Ekey && e.want == 1, "initial key framing changed"); - Check(strcmp(log.event, "SC") == 0 && strcmp(log.pre, "pre") == 0, - "initial preedit signals were %s (%s)", log.event, log.pre); + Check(log.n == 0, "initial empty preedit emitted %s", log.event); + 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. */ setpre(&srv, ""); diff --git a/tests/ibus_client_smoke.c b/tests/ibus_client_smoke.c index e04f90d..cfcbb2f 100644 --- a/tests/ibus_client_smoke.c +++ b/tests/ibus_client_smoke.c @@ -8,12 +8,19 @@ typedef struct Log Log; struct Log { GMainLoop *loop; + int *waiting; + IBusInputContext *a; + IBusInputContext *clearctx; int legacy; int modern; int commit; int invalid; int sawpreedit; int sawcommit; + int done; + int transfer; + int atext; + int aclear; }; static void @@ -38,10 +45,25 @@ legacy(IBusInputContext *ctx, IBusText *text, guint cursor, a->start_index != 0 || a->end_index != 1 || ibus_attr_list_get(attrs, 1) != NULL) log->invalid = 1; - else + else{ 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); } @@ -80,7 +102,8 @@ commit(IBusInputContext *ctx, IBusText *text, void *arg) log->invalid = 1; else 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); } @@ -92,12 +115,13 @@ timeout(void *arg) } static int -waitdone(Log *log) +waitflag(Log *log, int *flag) { guint timer; - if(log->sawpreedit && log->sawcommit) + if(*flag) return 1; + log->waiting = flag; log->loop = g_main_loop_new(NULL, FALSE); timer = g_timeout_add_seconds(4, timeout, log->loop); g_main_loop_run(log->loop); @@ -105,14 +129,15 @@ waitdone(Log *log) g_source_remove(timer); g_main_loop_unref(log->loop); log->loop = NULL; - return log->sawpreedit && log->sawcommit; + log->waiting = NULL; + return *flag; } int main(int argc, char **argv) { IBusBus *bus; - IBusInputContext *ctx; + IBusInputContext *a, *b; Log log; int ok; @@ -127,6 +152,7 @@ main(int argc, char **argv) return 1; } memset(&log, 0, sizeof log); + b = NULL; ibus_init(); bus = ibus_bus_new(); if(bus == NULL || !ibus_bus_is_connected(bus)){ @@ -134,36 +160,56 @@ main(int argc, char **argv) if(bus != NULL) g_object_unref(bus); return 1; } - ctx = ibus_bus_create_input_context(bus, "strans-libibus-smoke"); - if(ctx == NULL){ + a = ibus_bus_create_input_context(bus, "strans-libibus-smoke-a"); + if(a == NULL){ fprintf(stderr, "ibus_client_smoke: cannot create input context\n"); g_object_unref(bus); return 1; } - g_signal_connect(ctx, "update-preedit-text", G_CALLBACK(legacy), &log); - g_signal_connect(ctx, "update-preedit-text-with-mode", G_CALLBACK(modern), + log.a = a; + g_signal_connect(a, "update-preedit-text", G_CALLBACK(legacy), &log); + g_signal_connect(a, "update-preedit-text-with-mode", G_CALLBACK(modern), &log); - g_signal_connect(ctx, "commit-text", G_CALLBACK(commit), &log); - ibus_input_context_set_capabilities(ctx, IBUS_CAP_PREEDIT_TEXT); - ibus_input_context_focus_in(ctx); - ok = ibus_input_context_process_key_event(ctx, 'n', 0, IBUS_CONTROL_MASK) && - ibus_input_context_process_key_event(ctx, 'k', 0, 0) && - ibus_input_context_process_key_event(ctx, 'a', 0, 0) && - ibus_input_context_process_key_event(ctx, '0', 0, 0) && - waitdone(&log); - ibus_input_context_focus_out(ctx); - if(log.legacy == 0 || log.modern != 0 || log.commit != 1 || - log.invalid || !log.sawpreedit || !log.sawcommit) + g_signal_connect(a, "commit-text", G_CALLBACK(commit), &log); + ibus_input_context_set_capabilities(a, IBUS_CAP_PREEDIT_TEXT); + ibus_input_context_focus_in(a); + ok = ibus_input_context_process_key_event(a, 'n', 0, IBUS_CONTROL_MASK) && + ibus_input_context_process_key_event(a, 'k', 0, 0) && + ibus_input_context_process_key_event(a, 'a', 0, 0) && + ibus_input_context_process_key_event(a, '0', 0, 0) && + waitflag(&log, &log.done); + b = ibus_bus_create_input_context(bus, "strans-libibus-smoke-b"); + if(b == NULL) 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); if(!ok){ 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.sawpreedit, log.sawcommit); + log.sawpreedit, log.sawcommit, log.atext, log.aclear); return 1; } - printf("official libibus client preedit and commit: ok\n"); + printf("official libibus client preedit, commit, and owner clear: ok\n"); return 0; } diff --git a/tests/ibus_test.c b/tests/ibus_test.c index 2e91fbb..f7313d2 100644 --- a/tests/ibus_test.c +++ b/tests/ibus_test.c @@ -199,6 +199,7 @@ ibusbegin(struct ct *t, Ibusfix *f) memset(contexts, 0, sizeof contexts); memset(conns, 0, sizeof conns); nconns = 0; + preowner = nil; while(channbrecv(drawc, &dc) > 0) ; testengineinit(LangEN); @@ -234,6 +235,7 @@ ibusend(Ibusfix *f) memset(contexts, 0, sizeof contexts); memset(conns, 0, sizeof conns); nconns = 0; + preowner = nil; testengineinit(LangEN); while(channbrecv(drawc, &dc) > 0) ;