diff --git a/ibus.c b/ibus.c index f1964c3..60efde1 100644 --- a/ibus.c +++ b/ibus.c @@ -316,6 +316,38 @@ dropcontext(Ictx *ctx) 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) +{ + if(state & Relmask || !ctx->focused) + return 0; + sendrequest(ctx, Keypress, kget(sym), mget(state), res); + return 1; +} + +static void +setcursor(Ictx *ctx, int x, int y, int h) +{ + Keyres res; + + ctx->caret.valid = 1; + ctx->caret.x = x; + ctx->caret.y = y; + ctx->caret.h = h; + if(ctx->focused) + sendrequest(ctx, Keycaret, 0, 0, &res); +} + static void appendibustext(DBusMessageIter *it, const char *s) { @@ -458,7 +490,6 @@ handlekey(DBusConnection *c, DBusMessage *m, Ictx *ctx) dbus_uint32_t sym, code, state; Keyres res; char commit[Maxutf], preedit[Maxutf]; - u32int ks, mod; dbus_error_init(&err); if(!dbus_message_get_args(m, &err, @@ -470,11 +501,8 @@ handlekey(DBusConnection *c, DBusMessage *m, Ictx *ctx) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } USED(code); - if(state & Relmask || !ctx->focused) + if(!processkey(ctx, sym, state, &res)) return handlebool(c, m, 0); - ks = kget(sym); - mod = mget(state); - sendrequest(ctx, Keypress, ks, mod, &res); stoutf(&res.commit, commit, sizeof commit); stoutf(&res.preedit, preedit, sizeof preedit); if(commit[0] != '\0') @@ -531,7 +559,6 @@ handlecursor(DBusConnection *c, DBusMessage *m, Ictx *ctx) { DBusError err; dbus_int32_t x, y, w, h; - Keyres res; dbus_error_init(&err); if(!dbus_message_get_args(m, &err, @@ -545,12 +572,7 @@ handlecursor(DBusConnection *c, DBusMessage *m, Ictx *ctx) if(w < 0 || h < 0) return handleerror(c, m, DBUS_ERROR_INVALID_ARGS, "cursor dimensions must not be negative"); - ctx->caret.valid = 1; - ctx->caret.x = x; - ctx->caret.y = y; - ctx->caret.h = h; - if(ctx->focused) - sendrequest(ctx, Keycaret, 0, 0, &res); + setcursor(ctx, x, y, h); return handlenoop(c, m); } @@ -671,7 +693,7 @@ newconn(DBusServer *s, DBusConnection *c, void *_) static void pruneconns(void) { - int i, j, k; + int i, j; j = 0; for(i = 0; i < nconns; i++){ @@ -679,9 +701,7 @@ pruneconns(void) conns[j++] = conns[i]; continue; } - for(k = 0; k < nelem(contexts); k++) - if(contexts[k].conn == conns[i]) - dropcontext(&contexts[k]); + dropconncontexts(conns[i]); dbus_connection_unref(conns[i]); } nconns = j; diff --git a/tests/engine_test.c b/tests/engine_test.c index 8148eb0..ec3e233 100644 --- a/tests/engine_test.c +++ b/tests/engine_test.c @@ -34,6 +34,12 @@ testenginepreedit(Str *preedit) impre(preedit); } +void +testenginecaret(Caret *at) +{ + *at = caret; +} + void vietnamese_state_lifetime(struct ct *t) { diff --git a/tests/ibus_test.c b/tests/ibus_test.c index 482c856..f55c5c6 100644 --- a/tests/ibus_test.c +++ b/tests/ibus_test.c @@ -1,71 +1,354 @@ #include "../ibus.c" #include "../cutest/cutest.h" +void testengineinit(int); +void testenginehandle(Keyreq*); +void* testengineowner(void); +void testenginepreedit(Str*); +void testenginecaret(Caret*); + +enum +{ + Testctrlmask = 1<<2, +}; + +typedef struct Ibusfix Ibusfix; +struct Ibusfix +{ + Channel *oldreply; + Channel *trace; + Channel *stop; + Channel *done; + DBusConnection *c1; + DBusConnection *c2; + int pumpactive; +}; + +static void +enginepump(void *arg) +{ + Ibusfix *f; + Keyreq req; + uchar token; + Alt alts[] = { + {keyc, &req, CHANRCV, nil}, + {nil, &token, CHANRCV, nil}, + {nil, nil, CHANEND, nil}, + }; + + f = arg; + alts[1].c = f->stop; + for(;;) + switch(alt(alts)){ + case 0: + chansend(f->trace, &req); + testenginehandle(&req); + break; + case 1: + chansend(f->done, &token); + return; + } +} + +static int +ibusbegin(struct ct *t, Ibusfix *f) +{ + Drawcmd dc; + + memset(f, 0, sizeof *f); + memset(contexts, 0, sizeof contexts); + memset(conns, 0, sizeof conns); + nconns = 0; + while(channbrecv(drawc, &dc) > 0) + ; + testengineinit(LangEN); + f->oldreply = replyc; + replyc = chancreate(sizeof(Keyres), 0); + f->trace = chancreate(sizeof(Keyreq), Maxcontexts); + f->stop = chancreate(sizeof(uchar), 0); + f->done = chancreate(sizeof(uchar), 0); + f->pumpactive = threadcreate(enginepump, f, 8192) >= 0; + f->c1 = malloc(1); + f->c2 = malloc(1); + return CT_CHECK(t, f->pumpactive && f->c1 != nil && f->c2 != nil); +} + +static void +ibusend(Ibusfix *f) +{ + Drawcmd dc; + Keyreq req; + uchar token; + int i; + + if(f->pumpactive){ + for(i = 0; i < nelem(contexts); i++) + if(contexts[i].conn != nil) + dropcontext(&contexts[i]); + while(channbrecv(f->trace, &req) > 0) + ; + token = 0; + chansend(f->stop, &token); + chanrecv(f->done, &token); + } + memset(contexts, 0, sizeof contexts); + memset(conns, 0, sizeof conns); + nconns = 0; + testengineinit(LangEN); + while(channbrecv(drawc, &dc) > 0) + ; + free(f->c1); + free(f->c2); + chanfree(replyc); + replyc = f->oldreply; + chanfree(f->trace); + chanfree(f->stop); + chanfree(f->done); +} + +static Keyreq +nexttrace(struct ct *t, Ibusfix *f, int op, Ictx *ctx) +{ + Keyreq req; + + memset(&req, 0, sizeof req); + if(!CT_CHECK(t, channbrecv(f->trace, &req) > 0)) + return req; + CT_EQ_INT(t, op, req.op); + CT_EQ_PTR(t, ctx, req.owner); + CT_EQ_PTR(t, replyc, req.reply); + return req; +} + +static void +notrace(struct ct *t, Ibusfix *f) +{ + Keyreq req; + + CT_CHECK(t, channbrecv(f->trace, &req) <= 0); +} + +static void +checkpreedit(struct ct *t, char *want, Keyres *res) +{ + char got[Maxutf]; + + stoutf(&res->preedit, got, sizeof got); + CT_EQ_STR(t, want, got); +} + +static void +checkenginepreedit(struct ct *t, char *want) +{ + char got[Maxutf]; + Str preedit; + + testenginepreedit(&preedit); + stoutf(&preedit, got, sizeof got); + CT_EQ_STR(t, want, got); +} + +static Keyres +contextkey(struct ct *t, Ibusfix *f, Ictx *ctx, u32int sym, u32int state) +{ + Keyres res; + + memset(&res, 0, sizeof res); + CT_CHECK(t, processkey(ctx, sym, state, &res)); + nexttrace(t, f, Keypress, ctx); + return res; +} + void ibus_context_lifecycle(struct ct *t) { - Channel *oldkeyc, *oldreplyc; - DBusConnection *c1, *c2; - Ictx *a, *b; + Ibusfix f; + Ictx *a, *b, *reused; Keyreq req; Keyres res; + Caret at; - oldkeyc = keyc; - oldreplyc = replyc; - keyc = chancreate(sizeof(Keyreq), 2); - replyc = chancreate(sizeof(Keyres), 2); - memset(contexts, 0, sizeof contexts); - c1 = malloc(1); - c2 = malloc(1); - if(!CT_CHECK(t, c1 != nil && c2 != nil)) + if(!ibusbegin(t, &f)) goto cleanup; - a = newcontext(c1, "/context/one"); - b = newcontext(c2, "/context/one"); + a = newcontext(f.c1, "/context/one"); + b = newcontext(f.c2, "/context/one"); if(!CT_CHECK(t, a != nil && b != nil && a != b)) goto cleanup; - CT_EQ_PTR(t, a, findcontext(c1, "/context/one")); - CT_EQ_PTR(t, b, findcontext(c2, "/context/one")); - CT_EQ_PTR(t, nil, findcontext(c1, "/context/missing")); + CT_EQ_PTR(t, a, findcontext(f.c1, "/context/one")); + CT_EQ_PTR(t, b, findcontext(f.c2, "/context/one")); + CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/missing")); - a->caret.valid = 1; - releasecontext(a); - CT_CHECK(t, channbrecv(keyc, &req) <= 0); - CT_CHECK(t, !a->caret.valid); - - a->focused = 1; - a->caret.valid = 1; memset(&res, 0, sizeof res); - chansend(replyc, &res); - releasecontext(a); - if(CT_CHECK(t, channbrecv(keyc, &req) > 0)){ - CT_EQ_PTR(t, a, req.owner); - CT_EQ_INT(t, Keyrelease, req.op); - CT_EQ_PTR(t, replyc, req.reply); - } - CT_CHECK(t, !a->focused); - CT_CHECK(t, !a->caret.valid); - releasecontext(a); - CT_CHECK(t, channbrecv(keyc, &req) <= 0); + CT_CHECK(t, !processkey(a, 'x', 0, &res)); + CT_CHECK(t, !res.eaten); + notrace(t, &f); + CT_EQ_PTR(t, nil, testengineowner()); - dropcontext(a); - CT_CHECK(t, channbrecv(keyc, &req) <= 0); - CT_EQ_PTR(t, nil, findcontext(c1, "/context/one")); - CT_EQ_PTR(t, a, newcontext(c1, "/context/reused")); + setcursor(a, 10, 20, 14); + notrace(t, &f); + CT_CHECK(t, a->caret.valid); + a->focused = 1; + memset(&res, 0, sizeof res); + CT_CHECK(t, !processkey(a, 'x', Relmask, &res)); + CT_CHECK(t, !res.eaten); + notrace(t, &f); + CT_EQ_PTR(t, nil, testengineowner()); + + memset(&res, 0, sizeof res); + CT_CHECK(t, processkey(a, 'x', 0, &res)); + req = nexttrace(t, &f, Keypress, a); + CT_CHECK(t, !res.eaten); + CT_CHECK(t, req.caret.valid); + CT_EQ_INT(t, 10, req.caret.x); + CT_EQ_INT(t, 20, req.caret.y); + CT_EQ_INT(t, 14, req.caret.h); + CT_EQ_PTR(t, a, testengineowner()); + testenginecaret(&at); + CT_CHECK(t, at.valid); + CT_EQ_INT(t, 10, at.x); + CT_EQ_INT(t, 20, at.y); + res = contextkey(t, &f, a, 'n', Testctrlmask); + CT_CHECK(t, res.eaten); + contextkey(t, &f, a, 'k', 0); + res = contextkey(t, &f, a, 'a', 0); + checkpreedit(t, "か", &res); b->focused = 1; - chansend(replyc, &res); - dropcontext(b); - if(CT_CHECK(t, channbrecv(keyc, &req) > 0)){ - CT_EQ_PTR(t, b, req.owner); - CT_EQ_INT(t, Keyrelease, req.op); - } - CT_EQ_PTR(t, nil, findcontext(c2, "/context/one")); + res = contextkey(t, &f, b, 'n', 0); + checkpreedit(t, "ん", &res); + CT_EQ_PTR(t, b, testengineowner()); + + setcursor(b, 50, 60, 12); + req = nexttrace(t, &f, Keycaret, b); + CT_CHECK(t, req.caret.valid); + CT_EQ_INT(t, 50, req.caret.x); + testenginecaret(&at); + CT_CHECK(t, at.valid); + CT_EQ_INT(t, 50, at.x); + + memset(&res, 0, sizeof res); + sendrequest(a, Keyreset, 0, 0, &res); + nexttrace(t, &f, Keyreset, a); + checkpreedit(t, "", &res); + checkenginepreedit(t, "ん"); + CT_EQ_PTR(t, b, testengineowner()); + + setcursor(a, 90, 100, 20); + nexttrace(t, &f, Keycaret, a); + testenginecaret(&at); + CT_EQ_INT(t, 50, at.x); + CT_EQ_INT(t, 60, at.y); + + releasecontext(a); + nexttrace(t, &f, Keyrelease, a); + CT_CHECK(t, !a->focused); + CT_CHECK(t, !a->caret.valid); + checkenginepreedit(t, "ん"); + CT_EQ_PTR(t, b, testengineowner()); + releasecontext(a); + notrace(t, &f); + dropcontext(a); + notrace(t, &f); + CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/one")); + + reused = newcontext(f.c1, "/context/stale-destroy"); + CT_EQ_PTR(t, a, reused); + reused->focused = 1; + dropcontext(reused); + nexttrace(t, &f, Keyrelease, reused); + notrace(t, &f); + CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/stale-destroy")); + checkenginepreedit(t, "ん"); + CT_EQ_PTR(t, b, testengineowner()); + + reused = newcontext(f.c1, "/context/reused"); + CT_EQ_PTR(t, a, reused); + reused->focused = 1; + dropconncontexts(f.c1); + nexttrace(t, &f, Keyrelease, reused); + notrace(t, &f); + CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/reused")); + checkenginepreedit(t, "ん"); + CT_EQ_PTR(t, b, testengineowner()); + dropconncontexts(f.c1); + notrace(t, &f); + + memset(&res, 0, sizeof res); + CT_CHECK(t, !processkey(b, 'x', Relmask, &res)); + CT_CHECK(t, !res.eaten); + notrace(t, &f); + CT_EQ_PTR(t, b, testengineowner()); cleanup: - memset(contexts, 0, sizeof contexts); - free(c1); - free(c2); - chanfree(keyc); - chanfree(replyc); - keyc = oldkeyc; - replyc = oldreplyc; + ibusend(&f); +} + +void +ibus_active_release_lifecycle(struct ct *t) +{ + Ibusfix f; + Ictx *ctx, *reused; + Keyres res; + + if(!ibusbegin(t, &f)) + goto cleanup; + ctx = newcontext(f.c1, "/context/reset"); + if(!CT_CHECK(t, ctx != nil)) + goto cleanup; + ctx->focused = 1; + contextkey(t, &f, ctx, 'n', Testctrlmask); + res = contextkey(t, &f, ctx, 'n', 0); + checkpreedit(t, "ん", &res); + + memset(&res, 0, sizeof res); + sendrequest(ctx, Keyreset, 0, 0, &res); + nexttrace(t, &f, Keyreset, ctx); + checkpreedit(t, "", &res); + checkenginepreedit(t, ""); + CT_CHECK(t, ctx->focused); + CT_EQ_PTR(t, ctx, testengineowner()); + res = contextkey(t, &f, ctx, 'k', 0); + checkpreedit(t, "k", &res); + CT_EQ_PTR(t, ctx, testengineowner()); + + releasecontext(ctx); + nexttrace(t, &f, Keyrelease, ctx); + CT_CHECK(t, !ctx->focused); + checkenginepreedit(t, ""); + CT_EQ_PTR(t, nil, testengineowner()); + releasecontext(ctx); + notrace(t, &f); + dropcontext(ctx); + notrace(t, &f); + CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/reset")); + + reused = newcontext(f.c1, "/context/destroy"); + CT_EQ_PTR(t, ctx, reused); + reused->focused = 1; + contextkey(t, &f, reused, 'n', 0); + CT_EQ_PTR(t, reused, testengineowner()); + dropcontext(reused); + nexttrace(t, &f, Keyrelease, reused); + CT_EQ_PTR(t, nil, testengineowner()); + notrace(t, &f); + CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/destroy")); + + reused = newcontext(f.c1, "/context/prune"); + CT_EQ_PTR(t, ctx, reused); + CT_EQ_PTR(t, nil, testengineowner()); + memset(&res, 0, sizeof res); + CT_CHECK(t, !processkey(reused, 'x', 0, &res)); + CT_CHECK(t, !res.eaten); + notrace(t, &f); + reused->focused = 1; + contextkey(t, &f, reused, 'n', 0); + CT_EQ_PTR(t, reused, testengineowner()); + dropconncontexts(f.c1); + nexttrace(t, &f, Keyrelease, reused); + CT_EQ_PTR(t, nil, testengineowner()); + notrace(t, &f); + CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/prune")); + dropconncontexts(f.c1); + notrace(t, &f); +cleanup: + ibusend(&f); } diff --git a/tests/test.h b/tests/test.h index 8267bfc..30d75b7 100644 --- a/tests/test.h +++ b/tests/test.h @@ -72,3 +72,4 @@ void ipc_response_fragmented_and_truncated(struct ct*); void ipc_broken_peer_send(struct ct*); void server_connection_ownership(struct ct*); void ibus_context_lifecycle(struct ct*); +void ibus_active_release_lifecycle(struct ct*); diff --git a/tests/unit_test.c b/tests/unit_test.c index 636f2ed..11a6f29 100644 --- a/tests/unit_test.c +++ b/tests/unit_test.c @@ -126,6 +126,7 @@ static const struct ct_test tests[] = { { "ipc/broken-peer-send", ipc_broken_peer_send }, { "server/connection-ownership", server_connection_ownership }, { "ibus/context-lifecycle", ibus_context_lifecycle }, + { "ibus/active-release-lifecycle", ibus_active_release_lifecycle }, }; void