diff --git a/gtk/main.c b/gtk/main.c index b47bfb8..9c15764 100644 --- a/gtk/main.c +++ b/gtk/main.c @@ -219,6 +219,7 @@ simplefilter(GtkIMContext *ctx, GdkEventKey *ev, int release) return r; } +/* The daemon hands the pending text back; it becomes committed text. */ static void sendreset(Im *im) { @@ -238,6 +239,8 @@ sendreset(Im *im) return; } setpreedit(im, "", 0); + if(commit[0] != '\0') + g_signal_emit_by_name(im, "commit", commit); } static gboolean @@ -331,7 +334,8 @@ focusout(GtkIMContext *ctx) if(parentim->focus_out != NULL) parentim->focus_out(ctx); im->simpleactive = 0; - /* Closing the connection releases the engine, which resets it. */ + /* Closing the connection releases the engine. */ + sendreset(im); srvclose(im); } diff --git a/ibus.c b/ibus.c index a8c7693..eb742b6 100644 --- a/ibus.c +++ b/ibus.c @@ -288,36 +288,6 @@ sendrequest(Ictx *ctx, int op, u32int ks, u32int mod, Keyres *res) chanrecv(replyc, res); } -static void -releasecontext(Ictx *ctx) -{ - Keyres res; - - if(ctx->focused) - sendrequest(ctx, Keyrelease, 0, 0, &res); - ctx->focused = 0; - memset(&ctx->caret, 0, sizeof ctx->caret); -} - -static void -dropcontext(Ictx *ctx) -{ - releasecontext(ctx); - if(preowner == ctx) - preowner = nil; - memset(ctx, 0, sizeof *ctx); -} - -static void -dropconncontexts(DBusConnection *conn) -{ - int i; - - for(i = 0; i < nelem(contexts); i++) - if(contexts[i].conn == conn) - dropcontext(&contexts[i]); -} - static int hidden(Ictx *ctx) { @@ -465,6 +435,52 @@ checkpreowner(void) emitpreedit(ctx, ""); } +/* A reset or a lost focus hands the pending text back as committed text. */ +static void +flushcontext(Ictx *ctx, int op) +{ + Keyres res; + char commit[Maxutf]; + + sendrequest(ctx, op, 0, 0, &res); + if(clientpreedit(ctx)) + emitpreedit(ctx, ""); + stoutf(&res.commit, commit, sizeof commit); + if(commit[0] != '\0') + emitcommit(ctx->conn, ctx->path, commit); +} + +/* Nobody is left to hand text to: the context or its connection is gone. */ +static void +releasecontext(Ictx *ctx) +{ + Keyres res; + + if(ctx->focused) + sendrequest(ctx, Keyrelease, 0, 0, &res); + ctx->focused = 0; + memset(&ctx->caret, 0, sizeof ctx->caret); +} + +static void +dropcontext(Ictx *ctx) +{ + releasecontext(ctx); + if(preowner == ctx) + preowner = nil; + memset(ctx, 0, sizeof *ctx); +} + +static void +dropconncontexts(DBusConnection *conn) +{ + int i; + + for(i = 0; i < nelem(contexts); i++) + if(contexts[i].conn == conn) + dropcontext(&contexts[i]); +} + static int processkey(Ictx *ctx, u32int sym, u32int state, Keyres *res) { @@ -603,11 +619,7 @@ handlekey(DBusConnection *c, DBusMessage *m, Ictx *ctx) static DBusHandlerResult handlereset(DBusConnection *c, DBusMessage *m, Ictx *ctx) { - Keyres res; - - sendrequest(ctx, Keyreset, 0, 0, &res); - if(clientpreedit(ctx)) - emitpreedit(ctx, ""); + flushcontext(ctx, Keyreset); return reply(c, m, DBUS_TYPE_INVALID, nil); } @@ -621,9 +633,10 @@ handlefocusin(DBusConnection *c, DBusMessage *m, Ictx *ctx) static DBusHandlerResult handlefocusout(DBusConnection *c, DBusMessage *m, Ictx *ctx) { - releasecontext(ctx); - if(clientpreedit(ctx)) - emitpreedit(ctx, ""); + if(ctx->focused) + flushcontext(ctx, Keyrelease); + ctx->focused = 0; + memset(&ctx->caret, 0, sizeof ctx->caret); return reply(c, m, DBUS_TYPE_INVALID, nil); } @@ -701,7 +714,6 @@ handlepropertyset(DBusConnection *c, DBusMessage *m, Ictx *ctx) DBusMessageIter value, st; dbus_bool_t b; u32int purpose; - Keyres res; int washidden; if(!getproperty(m, &iface, &name, &value)) @@ -716,11 +728,8 @@ handlepropertyset(DBusConnection *c, DBusMessage *m, Ictx *ctx) "ContentType expects (uu)"); washidden = hidden(ctx); ctx->purpose = purpose; - if(ctx->focused && !washidden && hidden(ctx)){ - sendrequest(ctx, Keyreset, 0, 0, &res); - if(clientpreedit(ctx)) - emitpreedit(ctx, ""); - } + if(ctx->focused && !washidden && hidden(ctx)) + flushcontext(ctx, Keyreset); return reply(c, m, DBUS_TYPE_INVALID, nil); } if(strcmp(name, "ClientCommitPreedit") == 0){ diff --git a/strans.c b/strans.c index 4b5fd07..24ed2d3 100644 --- a/strans.c +++ b/strans.c @@ -572,6 +572,22 @@ startsearch(int lang, Str *com) search.lang = lang; } +/* + * Whatever is pending becomes committed text, a moved-to candidate + * first: Enter does this, and so do a reset and a lost focus. + */ +static void +flush(Str *com) +{ + if(search.lang) + commitsearch(com); + else if(candidatechosen && im.sel >= 0 && im.sel < im.nkouho) + sappend(com, &im.kouho[im.sel]); + else + commit(com); + reset(); +} + static void picksearch(int n, Str *com) { @@ -679,17 +695,10 @@ transition(u32int ks, u32int mod, Str *com) return 1; } if(ks == Ktab || ks == Kret){ - if(candidatechosen && im.sel >= 0 && im.sel < im.nkouho){ - sappend(com, &im.kouho[im.sel]); - reset(); - return 1; - } - if(haspre(&im)){ - commit(com); - reset(); - return 1; - } - return 0; + if(!haspre(&im)) + return 0; + flush(com); + return 1; } if(ks == Kback){ if(!haspre(&im)) @@ -773,8 +782,10 @@ init(void) /* * The engine belongs to whichever context last typed a real key; only - * the owner's requests change state. Every request ends in redraw(), - * which sends the popup a picture only if something visible changed. + * the owner's requests change state. A reset or release hands the + * owner its pending text back as commit. Every request ends in + * redraw(), which sends the popup a picture only if something visible + * changed. */ static void imhandlekey(Keyreq *kr) @@ -787,14 +798,14 @@ imhandlekey(Keyreq *kr) switch(kr->op){ case Keyrelease: if(kr->owner == activeowner){ - reset(); + flush(&res.commit); activeowner = nil; activecap = 0; } break; case Keyreset: if(kr->owner == activeowner) - reset(); + flush(&res.commit); break; case Keycaret: if(kr->owner == activeowner) diff --git a/tests/engine_test.c b/tests/engine_test.c index 5afcaa7..73e59bc 100644 --- a/tests/engine_test.c +++ b/tests/engine_test.c @@ -244,7 +244,8 @@ engine_active_owner_lifecycle(struct ct *t) checkstr(t, "stale release preserves owner", "にゃ", &shown); CT_EQ_PTR(t, &b, activeowner); - ownerrequest(&b, Keyrelease, 0, 0); + res = ownerrequest(&b, Keyrelease, 0, 0); + checkstr(t, "release hands the reading back", "にゃ", &res.commit); CT_EQ_PTR(t, nil, activeowner); CT_EQ_INT(t, 0, im.pre.n); CT_EQ_INT(t, 0, im.raw.n); @@ -274,6 +275,7 @@ engine_active_owner_reset(struct ct *t) checkstr(t, "preedit before reset", "か", &res.preedit); res = ownerrequest(&owner, Keyreset, 0, 0); CT_EQ_PTR(t, &owner, activeowner); + checkstr(t, "reset hands the reading back", "か", &res.commit); CT_EQ_INT(t, 0, res.preedit.n); CT_CHECK(t, !caret.valid); res = ownerrequest(&owner, Keypress, 'n', 0); diff --git a/tests/gtk_live_test.c b/tests/gtk_live_test.c index 0f15d30..7fa685d 100644 --- a/tests/gtk_live_test.c +++ b/tests/gtk_live_test.c @@ -820,7 +820,9 @@ main(int argc, char **argv) closes = closecount(&srv); gtk_im_context_focus_out(ctx); Check(waitclose(&srv, closes + 1), "focus-out did not close connection"); - Check(eventcount(&srv) == first, "focus-out sent a request before closing"); + Check(eventcount(&srv) == first + 1 && + getevent(&srv, first).type == Ereset, + "focus-out did not hand the pending text back before closing"); Check(strcmp(log.event, "CE") == 0, "focus-out clear order was %s", log.event); /* Dispose is repeatable, closes the connection, and emits no preedit. */ diff --git a/tests/ibus_client_smoke.c b/tests/ibus_client_smoke.c index 7aedd8c..fd72421 100644 --- a/tests/ibus_client_smoke.c +++ b/tests/ibus_client_smoke.c @@ -27,6 +27,7 @@ struct Log int rfirst; int rcommit; int rdone; + int flushed; }; static void @@ -51,6 +52,11 @@ legacy(IBusInputContext *ctx, IBusText *text, guint cursor, log = arg; log->legacy++; s = ibus_text_get_text(text); + if(log->repeat == 3){ + if(s[0] != '\0' || cursor != 0 || visible) + log->invalid = 1; + return; + } if(log->repeat != 0){ revent(log, 'P'); if(log->repeat == 1){ @@ -134,6 +140,16 @@ commit(IBusInputContext *ctx, IBusText *text, void *arg) log->commit++; s = ibus_text_get_text(text); attrs = ibus_text_get_attributes(text); + if(log->repeat == 3){ + /* Focus loss hands the pending syllable back as a commit. */ + if(strcmp(s, "ㅋ") == 0) + log->flushed = 1; + else + log->invalid = 1; + if(log->loop != NULL && log->waiting != NULL && *log->waiting) + g_main_loop_quit(log->loop); + return; + } if(log->repeat != 0){ revent(log, 'K'); if(log->repeat != 2 || strcmp(s, "ㅋ") != 0 || attrs == NULL || @@ -253,9 +269,10 @@ main(int argc, char **argv) log.rn = 0; ok = ok && ibus_input_context_process_key_event(a, 'z', 0, 0) && waitflag(&log, &log.rdone) && strcmp(log.revent, "PKP") == 0; - log.repeat = 0; + log.repeat = 3; ibus_input_context_focus_out(a); - if(log.legacy == 0 || log.modern != 0 || log.commit != 2 || + ok = ok && waitflag(&log, &log.flushed); + if(log.legacy == 0 || log.modern != 0 || log.commit != 3 || log.invalid || !log.done || !log.atext || !log.aclear || log.clearctx != a || !log.rdone) ok = 0; @@ -265,12 +282,12 @@ main(int argc, char **argv) g_object_unref(bus); if(!ok){ fprintf(stderr, - "ibus_client_smoke: legacy=%d modern=%d commit=%d invalid=%d preedit=%d committed=%d transfer-text=%d a-clear=%d repeat=%s\n", + "ibus_client_smoke: legacy=%d modern=%d commit=%d invalid=%d preedit=%d committed=%d transfer-text=%d a-clear=%d repeat=%s flushed=%d\n", log.legacy, log.modern, log.commit, log.invalid, log.sawpreedit, log.sawcommit, log.atext, log.aclear, - log.revent); + log.revent, log.flushed); return 1; } - printf("official libibus client preedit, commit, and owner clear: ok\n"); + printf("official libibus client preedit, commit, owner clear, and focus-out hand-back: ok\n"); return 0; } diff --git a/tests/ipc_live_test.c b/tests/ipc_live_test.c index 7179885..7544193 100644 --- a/tests/ipc_live_test.c +++ b/tests/ipc_live_test.c @@ -135,11 +135,12 @@ expectresponse(int fd, int want, int eaten, char *commit, char *preedit, } static int -requestreset(int fd, int want, int eaten, char *preedit, char *where) +requestreset(int fd, int want, int eaten, char *commit, char *preedit, + char *where) { if(!sendreset(fd, want)) return fail("send %s: %s", where, strerror(errno)); - return expectresponse(fd, want, eaten, "", preedit, where); + return expectresponse(fd, want, eaten, commit, preedit, where); } static int @@ -257,7 +258,7 @@ runsmoke(char *path) requestkey(client, 1, Mctrl, 'n', 1, "", "", "select Japanese") && requestkey(client, 1, 0, 'k', 1, "", "k", "preedit") && - requestreset(client, 1, 1, "", "reset"); + requestreset(client, 1, 1, "k", "", "reset"); close(client); return ok; } diff --git a/tests/server_test.c b/tests/server_test.c index d42f474..0f9d288 100644 --- a/tests/server_test.c +++ b/tests/server_test.c @@ -163,14 +163,15 @@ allowrequest(Pump *p) } static int -readreply(struct ct *t, Testclient *client, int want, char *preedit) +readreply(struct ct *t, Testclient *client, int want, char *wantcommit, + char *preedit) { char commit[Ipcfieldmax+1]; Ipcresp resp; if(ipcreadresp(client->peer, want, commit, preedit, &resp) < 0) return CT_ERRORF(t, "response read failed: %s", strerror(errno)); - CT_EQ_STR(t, "", commit); + CT_EQ_STR(t, wantcommit, commit); return resp.eaten; } @@ -257,14 +258,14 @@ server_connection_ownership(struct ct *t) req = nextrequest(t, &f.pump, Keypress); aowner = req.owner; allowrequest(&f.pump); - CT_CHECK(t, readreply(t, &a, 1, preedit)); + CT_CHECK(t, readreply(t, &a, 1, "", preedit)); CT_EQ_STR(t, "k", preedit); if(!sendkey(t, &a, 1, 0, 'a')) goto cleanup; req = nextrequest(t, &f.pump, Keypress); CT_EQ_PTR(t, aowner, req.owner); allowrequest(&f.pump); - CT_CHECK(t, readreply(t, &a, 1, preedit)); + CT_CHECK(t, readreply(t, &a, 1, "", preedit)); CT_EQ_STR(t, "か", preedit); CT_EQ_PTR(t, aowner, testengineowner()); @@ -274,7 +275,7 @@ server_connection_ownership(struct ct *t) bowner = req.owner; CT_CHECK(t, bowner != aowner); allowrequest(&f.pump); - CT_CHECK(t, readreply(t, &b, 1, preedit)); + CT_CHECK(t, readreply(t, &b, 1, "", preedit)); CT_EQ_STR(t, "ん", preedit); CT_EQ_PTR(t, bowner, testengineowner()); @@ -283,14 +284,14 @@ server_connection_ownership(struct ct *t) req = nextrequest(t, &f.pump, Keyreset); CT_EQ_PTR(t, aowner, req.owner); allowrequest(&f.pump); - CT_CHECK(t, readreply(t, &a, 1, preedit)); + CT_CHECK(t, readreply(t, &a, 1, "", preedit)); CT_EQ_STR(t, "", preedit); CT_EQ_PTR(t, bowner, testengineowner()); if(!sendkey(t, &b, 1, 0, Kmodfirst)) goto cleanup; req = nextrequest(t, &f.pump, Keypress); allowrequest(&f.pump); - CT_CHECK(t, !readreply(t, &b, 1, preedit)); + CT_CHECK(t, !readreply(t, &b, 1, "", preedit)); CT_EQ_STR(t, "ん", preedit); disconnectclient(t, &f.pump, &a, aowner); @@ -299,12 +300,12 @@ server_connection_ownership(struct ct *t) goto cleanup; req = nextrequest(t, &f.pump, Keypress); allowrequest(&f.pump); - CT_CHECK(t, readreply(t, &b, 1, preedit)); + CT_CHECK(t, readreply(t, &b, 1, "", preedit)); if(!sendkey(t, &b, 1, 0, 'a')) goto cleanup; req = nextrequest(t, &f.pump, Keypress); allowrequest(&f.pump); - CT_CHECK(t, readreply(t, &b, 1, preedit)); + CT_CHECK(t, readreply(t, &b, 1, "", preedit)); CT_EQ_STR(t, "にゃ", preedit); if(!sendreset(t, &b, 1)) @@ -312,7 +313,7 @@ server_connection_ownership(struct ct *t) req = nextrequest(t, &f.pump, Keyreset); CT_EQ_PTR(t, bowner, req.owner); allowrequest(&f.pump); - CT_CHECK(t, readreply(t, &b, 1, preedit)); + CT_CHECK(t, readreply(t, &b, 1, "にゃ", preedit)); CT_EQ_STR(t, "", preedit); CT_EQ_PTR(t, bowner, testengineowner()); @@ -321,7 +322,7 @@ server_connection_ownership(struct ct *t) req = nextrequestcap(t, &f.pump, Keypress, 0); CT_EQ_PTR(t, bowner, req.owner); allowrequest(&f.pump); - CT_CHECK(t, readreply(t, &b, 0, preedit)); + CT_CHECK(t, readreply(t, &b, 0, "", preedit)); errno = 0; n = recv(b.peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT); CT_EQ_INT(t, -1, n); @@ -331,7 +332,7 @@ server_connection_ownership(struct ct *t) goto cleanup; req = nextrequest(t, &f.pump, Keypress); allowrequest(&f.pump); - CT_CHECK(t, readreply(t, &b, 1, preedit)); + CT_CHECK(t, readreply(t, &b, 1, "", preedit)); CT_EQ_STR(t, "か", preedit); disconnectclient(t, &f.pump, &b, bowner); @@ -393,7 +394,7 @@ server_extension_stream(struct ct *t) aowner = req.owner; CT_EQ_PTR(t, nil, testengineowner()); allowrequest(&f.pump); - CT_EQ_INT(t, 1, readreply(t, &a, 1, preedit)); + CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit)); CT_EQ_STR(t, "", preedit); ipcpackcaret(frame, 1, -101, -7, 23); @@ -419,7 +420,7 @@ server_extension_stream(struct ct *t) CT_EQ_INT(t, -7, req.caret.y); CT_EQ_INT(t, 23, req.caret.h); allowrequest(&f.pump); - CT_EQ_INT(t, 1, readreply(t, &a, 1, preedit)); + CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit)); CT_EQ_STR(t, "k", preedit); CT_EQ_PTR(t, aowner, testengineowner()); @@ -430,7 +431,7 @@ server_extension_stream(struct ct *t) CT_EQ_PTR(t, aowner, req.owner); CT_EQ_INT(t, 1, req.caret.valid); allowrequest(&f.pump); - CT_EQ_INT(t, 1, readreply(t, &a, 0, preedit)); + CT_EQ_INT(t, 1, readreply(t, &a, 0, "", preedit)); /* Reset is still the old six-byte frame and does not discard caret. */ ipcpackreset(frame, 0); @@ -443,14 +444,14 @@ server_extension_stream(struct ct *t) CT_EQ_INT(t, -7, req.caret.y); CT_EQ_INT(t, 23, req.caret.h); allowrequest(&f.pump); - CT_EQ_INT(t, 1, readreply(t, &a, 0, preedit)); + CT_EQ_INT(t, 1, readreply(t, &a, 0, "k", preedit)); ipcpackcap(frame, Cclientpreedit); if(!sendframe(t, &a, frame, Ipcreqsz, 0)) goto cleanup; req = nextrequest(t, &f.pump, Keycap); allowrequest(&f.pump); - CT_EQ_INT(t, 1, readreply(t, &a, 1, preedit)); + CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit)); CT_EQ_STR(t, "", preedit); ipcpackreq(frame, 1, 0, 'n'); if(!sendframe(t, &a, frame, Ipcreqsz, 0)) @@ -459,7 +460,7 @@ server_extension_stream(struct ct *t) CT_EQ_INT(t, 1, req.caret.valid); CT_EQ_INT(t, -101, req.caret.x); allowrequest(&f.pump); - CT_EQ_INT(t, 1, readreply(t, &a, 1, preedit)); + CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit)); CT_EQ_STR(t, "ん", preedit); disconnectclient(t, &f.pump, &a, aowner); @@ -471,7 +472,7 @@ server_extension_stream(struct ct *t) bowner = req.owner; CT_EQ_INT(t, 0, req.caret.valid); allowrequest(&f.pump); - CT_EQ_INT(t, 1, readreply(t, &b, 1, preedit)); + CT_EQ_INT(t, 1, readreply(t, &b, 1, "", preedit)); CT_EQ_STR(t, "k", preedit); disconnectclient(t, &f.pump, &b, bowner); @@ -532,7 +533,7 @@ server_rejects_unknown_extension(struct ct *t) req = nextrequest(t, &f.pump, Keypress); owner = req.owner; allowrequest(&f.pump); - CT_EQ_INT(t, 1, readreply(t, &good, 1, preedit)); + CT_EQ_INT(t, 1, readreply(t, &good, 1, "", preedit)); CT_EQ_STR(t, "k", preedit); disconnectclient(t, &f.pump, &good, owner); diff --git a/tests/xim_adapter_test.c b/tests/xim_adapter_test.c index b011170..8885b6d 100644 --- a/tests/xim_adapter_test.c +++ b/tests/xim_adapter_test.c @@ -901,7 +901,6 @@ xim_adapter_callback_cleanup(struct ct *t) memset(&reply, 0, sizeof reply); hdr.major_opcode = XCB_XIM_RESET_IC; callback(nil, nil, ic, &hdr, nil, &reply, nil); - nexttrace(t, &f, Keycap, &state); nexttrace(t, &f, Keyreset, &state); notrace(t, &f); checkct(t, "reset reply", reply.committed_string, diff --git a/xim/xim.c b/xim/xim.c index dc90091..6fc925a 100644 --- a/xim/xim.c +++ b/xim/xim.c @@ -311,23 +311,31 @@ keypress(Ic *state, u32int key, u32int mod, Keyres *res) sendrequest(state, Keypress, key, mod, res); } +/* Losing the engine commits the pending text; the release hands it back. */ static void release(Ic *state) { Keyres res; + char buf[Maxutf]; + int n; - clearpreedit(state); - if(!state->engaged) + if(!state->engaged){ + clearpreedit(state); return; + } sendrequest(state, Keyrelease, 0, 0, &res); state->engaged = 0; + n = stoutf(&res.commit, buf, sizeof buf); + commit(state, buf, n); + clearpreedit(state); + xcb_flush(conn); } -/* XIM reset hands the pending preedit back to the client as committed text. */ +/* XIM reset hands the pending text back to the client as committed text. */ static void resetic(Ic *state, xcb_im_reset_ic_reply_fr_t *reply) { - Keyres pending, res; + Keyres res; char utf[Maxutf], *wire; size_t nwire; int nbyte; @@ -336,12 +344,11 @@ resetic(Ic *state, xcb_im_reset_ic_reply_fr_t *reply) clearpreedit(state); return; } - sendrequest(state, Keycap, 0, 0, &pending); sendrequest(state, Keyreset, 0, 0, &res); clearpreedit(state); - if(reply == nil || !pending.eaten || pending.preedit.n == 0) + if(reply == nil || res.commit.n == 0) return; - nbyte = stoutf(&pending.preedit, utf, sizeof utf); + nbyte = stoutf(&res.commit, utf, sizeof utf); wire = xcb_utf8_to_compound_text(utf, nbyte, &nwire); if(wire == nil) return;