An IBus client throws away a dead key its engine did not take — the GTK module's own comment says so, and Qt does the same — so é and ü were lost in every IBus application, while XIM composed them with a table of its own. That table moves to compose.c, which both frontends now use; xim.c is the shorter for it. A finished sequence is text, not a key: the engine is asked to hand back what it had pending, and the composed text follows it, so the composed character can no longer land before the syllable typed before it.
445 lines
11 KiB
C
445 lines
11 KiB
C
#include "ibus.c"
|
|
#include "test.h"
|
|
|
|
#include <errno.h>
|
|
|
|
enum
|
|
{
|
|
Testctrlmask = 1<<2,
|
|
};
|
|
|
|
typedef struct Ibusfix Ibusfix;
|
|
struct Ibusfix
|
|
{
|
|
Channel *oldreply;
|
|
Pump pump;
|
|
DBusConnection *c1;
|
|
DBusConnection *c2;
|
|
};
|
|
|
|
void
|
|
ibus_machine_id_fallback(struct ct *t)
|
|
{
|
|
char root[] = "/tmp/strans-machine-id.XXXXXX";
|
|
char primary[128], fallback[128], override[128], got[512];
|
|
char *path[2], *old, *saved;
|
|
FILE *fp;
|
|
int madeprimary, madefallback;
|
|
|
|
madeprimary = 0;
|
|
madefallback = 0;
|
|
if(!CT_CHECK(t, mkdtemp(root) != nil))
|
|
return;
|
|
if(!CT_CHECK(t, snprintf(primary, sizeof primary, "%s/primary", root)
|
|
< (int)sizeof primary) ||
|
|
!CT_CHECK(t, snprintf(fallback, sizeof fallback, "%s/fallback", root)
|
|
< (int)sizeof fallback))
|
|
goto cleanup;
|
|
if(!CT_CHECK(t, mkdir(primary, 0700) == 0))
|
|
goto cleanup;
|
|
madeprimary = 1;
|
|
fp = fopen(fallback, "w");
|
|
if(!CT_CHECK(t, fp != nil))
|
|
goto cleanup;
|
|
madefallback = 1;
|
|
if(!CT_CHECK(t, fputs("fallback-machine-id\n", fp) >= 0)){
|
|
fclose(fp);
|
|
goto cleanup;
|
|
}
|
|
if(!CT_CHECK(t, fclose(fp) == 0))
|
|
goto cleanup;
|
|
path[0] = primary;
|
|
path[1] = fallback;
|
|
machineidfiles(got, sizeof got, path, nelem(path));
|
|
CT_EQ_STR(t, "fallback-machine-id", got);
|
|
path[1] = primary;
|
|
machineidfiles(got, sizeof got, path, nelem(path));
|
|
CT_EQ_STR(t, "", got);
|
|
|
|
old = getenv("IBUS_ADDRESS_FILE");
|
|
saved = old == nil ? nil : strdup(old);
|
|
if(old == nil || CT_CHECK(t, saved != nil)){
|
|
if(CT_CHECK(t, snprintf(override, sizeof override, "%s/address", root)
|
|
< (int)sizeof override) &&
|
|
CT_CHECK(t, setenv("IBUS_ADDRESS_FILE", override, 1) == 0)){
|
|
CT_EQ_INT(t, 0, buildaddrpath(got, sizeof got));
|
|
CT_EQ_STR(t, override, got);
|
|
CT_CHECK(t, setenv("IBUS_ADDRESS_FILE", "", 1) == 0);
|
|
CT_EQ_INT(t, -1, buildaddrpath(got, sizeof got));
|
|
}
|
|
if(saved != nil){
|
|
setenv("IBUS_ADDRESS_FILE", saved, 1);
|
|
free(saved);
|
|
}else
|
|
unsetenv("IBUS_ADDRESS_FILE");
|
|
}
|
|
cleanup:
|
|
if(madefallback)
|
|
CT_CHECK(t, unlink(fallback) == 0);
|
|
if(madeprimary)
|
|
CT_CHECK(t, rmdir(primary) == 0);
|
|
CT_CHECK(t, rmdir(root) == 0);
|
|
}
|
|
|
|
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;
|
|
preowner = nil;
|
|
while(channbrecv(drawc, &dc) > 0)
|
|
;
|
|
testengineinit(LangEN);
|
|
f->oldreply = replyc;
|
|
replyc = chancreate(sizeof(Keyres), 0);
|
|
pumpstart(&f->pump, Maxcontexts);
|
|
f->c1 = malloc(1);
|
|
f->c2 = malloc(1);
|
|
return CT_CHECK(t, f->c1 != nil && f->c2 != nil);
|
|
}
|
|
|
|
static void
|
|
ibusend(Ibusfix *f)
|
|
{
|
|
Drawcmd dc;
|
|
int i;
|
|
|
|
for(i = 0; i < nelem(contexts); i++)
|
|
if(contexts[i].conn != nil)
|
|
dropcontext(&contexts[i]);
|
|
pumpstop(&f->pump);
|
|
memset(contexts, 0, sizeof contexts);
|
|
memset(conns, 0, sizeof conns);
|
|
nconns = 0;
|
|
preowner = nil;
|
|
testengineinit(LangEN);
|
|
while(channbrecv(drawc, &dc) > 0)
|
|
;
|
|
free(f->c1);
|
|
free(f->c2);
|
|
chanfree(replyc);
|
|
replyc = f->oldreply;
|
|
}
|
|
|
|
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->pump.trace, &req) > 0))
|
|
return req;
|
|
CT_EQ_INT(t, op, req.op);
|
|
CT_EQ_PTR(t, ctx, req.owner);
|
|
if(ctx != nil)
|
|
CT_EQ_INT(t, clientpreedit(ctx), req.clientpre);
|
|
CT_EQ_PTR(t, replyc, req.reply);
|
|
return req;
|
|
}
|
|
|
|
static void
|
|
notrace(struct ct *t, Ibusfix *f)
|
|
{
|
|
Keyreq req;
|
|
|
|
CT_CHECK(t, channbrecv(f->pump.trace, &req) <= 0);
|
|
}
|
|
|
|
static Keyres
|
|
contextkey(struct ct *t, Ibusfix *f, Ictx *ctx, u32int sym, u32int state)
|
|
{
|
|
Keyres res;
|
|
char text[Maxutf];
|
|
|
|
memset(&res, 0, sizeof res);
|
|
CT_CHECK(t, processkey(ctx, sym, state, text, sizeof text, &res));
|
|
nexttrace(t, f, Keypress, ctx);
|
|
return res;
|
|
}
|
|
|
|
void
|
|
ibus_capability_policy(struct ct *t)
|
|
{
|
|
Ibusfix f;
|
|
Ictx *a, *b;
|
|
Keyres res;
|
|
|
|
if(!ibusbegin(t, &f))
|
|
goto cleanup;
|
|
a = newcontext(f.c1, "/context/cap-a");
|
|
b = newcontext(f.c2, "/context/cap-b");
|
|
if(!CT_CHECK(t, a != nil && b != nil))
|
|
goto cleanup;
|
|
CT_EQ_INT(t, 0, a->cap);
|
|
a->focused = 1;
|
|
res = contextkey(t, &f, a, 'n', Testctrlmask);
|
|
CT_CHECK(t, res.eaten);
|
|
res = contextkey(t, &f, a, 'k', 0);
|
|
checkstr(t, "preedit", "k", &res.preedit);
|
|
CT_EQ_PTR(t, a, testengineowner());
|
|
|
|
a->cap = Ibuscappreedit;
|
|
memset(&res, 0, sizeof res);
|
|
sendrequest(a, Keycap, 0, 0, &res);
|
|
nexttrace(t, &f, Keycap, a);
|
|
CT_CHECK(t, res.eaten);
|
|
checkstr(t, "preedit", "k", &res.preedit);
|
|
|
|
b->focused = 1;
|
|
b->cap = Ibuscappreedit;
|
|
memset(&res, 0, sizeof res);
|
|
sendrequest(b, Keycap, 0, 0, &res);
|
|
nexttrace(t, &f, Keycap, b);
|
|
CT_CHECK(t, !res.eaten);
|
|
CT_EQ_PTR(t, a, testengineowner());
|
|
checkenginepreedit(t, "k");
|
|
|
|
a->cap = 0;
|
|
memset(&res, 0, sizeof res);
|
|
sendrequest(a, Keycap, 0, 0, &res);
|
|
nexttrace(t, &f, Keycap, a);
|
|
CT_CHECK(t, res.eaten);
|
|
checkstr(t, "preedit", "k", &res.preedit);
|
|
cleanup:
|
|
ibusend(&f);
|
|
}
|
|
|
|
void
|
|
ibus_private_input_policy(struct ct *t)
|
|
{
|
|
static const struct { u32int purpose; int want; } cases[] = {
|
|
{ 0, 0 },
|
|
{ Ibuspurposepassword, 1 },
|
|
{ Ibuspurposepin, 1 },
|
|
{ 37, 0 },
|
|
};
|
|
Ibusfix f;
|
|
Ictx *ctx;
|
|
Keyres res;
|
|
char text[Maxutf];
|
|
int i;
|
|
|
|
if(!ibusbegin(t, &f))
|
|
goto cleanup;
|
|
ctx = newcontext(f.c1, "/context/private");
|
|
if(!CT_CHECK(t, ctx != nil))
|
|
goto cleanup;
|
|
for(i = 0; i < nelem(cases); i++){
|
|
ctx->purpose = cases[i].purpose;
|
|
CT_EQ_INT(t, cases[i].want, hidden(ctx));
|
|
}
|
|
|
|
ctx->focused = 1;
|
|
ctx->cap = 1;
|
|
ctx->purpose = 0;
|
|
contextkey(t, &f, ctx, 'n', Testctrlmask);
|
|
res = contextkey(t, &f, ctx, 'k', 0);
|
|
checkstr(t, "preedit", "k", &res.preedit);
|
|
ctx->purpose = Ibuspurposepassword;
|
|
memset(&res, 0, sizeof res);
|
|
CT_CHECK(t, !processkey(ctx, 'x', 0, text, sizeof text, &res));
|
|
CT_CHECK(t, !res.eaten);
|
|
notrace(t, &f);
|
|
checkenginepreedit(t, "k");
|
|
|
|
ctx->purpose = 0;
|
|
res = contextkey(t, &f, ctx, 'a', 0);
|
|
checkstr(t, "preedit", "か", &res.preedit);
|
|
cleanup:
|
|
ibusend(&f);
|
|
}
|
|
|
|
/* A second release is silent, and dropping frees the slot. */
|
|
static void
|
|
checkdrop(struct ct *t, Ibusfix *f, Ictx *ctx, char *path)
|
|
{
|
|
releasecontext(ctx);
|
|
notrace(t, f);
|
|
dropcontext(ctx);
|
|
notrace(t, f);
|
|
CT_EQ_PTR(t, nil, findcontext(f->c1, path));
|
|
}
|
|
|
|
void
|
|
ibus_context_lifecycle(struct ct *t)
|
|
{
|
|
Ibusfix f;
|
|
Ictx *a, *b, *reused;
|
|
Keyreq req;
|
|
Keyres res;
|
|
Caret at;
|
|
char text[Maxutf];
|
|
|
|
if(!ibusbegin(t, &f))
|
|
goto cleanup;
|
|
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(f.c1, "/context/one"));
|
|
CT_EQ_PTR(t, b, findcontext(f.c2, "/context/one"));
|
|
CT_EQ_PTR(t, nil, findcontext(f.c1, "/context/missing"));
|
|
|
|
setcursor(a, -10, -20, 14);
|
|
notrace(t, &f);
|
|
CT_CHECK(t, a->caret.valid);
|
|
memset(&res, 0, sizeof res);
|
|
CT_CHECK(t, !processkey(a, 'x', Relmask, text, sizeof text, &res));
|
|
CT_CHECK(t, !res.eaten);
|
|
notrace(t, &f);
|
|
CT_CHECK(t, !a->focused);
|
|
CT_EQ_PTR(t, nil, testengineowner());
|
|
|
|
/* A key stands for the FocusIn a client may never send. */
|
|
memset(&res, 0, sizeof res);
|
|
CT_CHECK(t, processkey(a, 'x', 0, text, sizeof text, &res));
|
|
req = nexttrace(t, &f, Keypress, a);
|
|
CT_CHECK(t, a->focused);
|
|
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);
|
|
checkstr(t, "preedit", "か", &res.preedit);
|
|
|
|
b->focused = 1;
|
|
res = contextkey(t, &f, b, 'n', 0);
|
|
checkstr(t, "preedit", "ん", &res.preedit);
|
|
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);
|
|
checkstr(t, "preedit", "", &res.preedit);
|
|
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());
|
|
checkdrop(t, &f, a, "/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, text, sizeof text, &res));
|
|
CT_CHECK(t, !res.eaten);
|
|
notrace(t, &f);
|
|
CT_EQ_PTR(t, b, testengineowner());
|
|
cleanup:
|
|
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);
|
|
checkstr(t, "preedit", "ん", &res.preedit);
|
|
|
|
memset(&res, 0, sizeof res);
|
|
sendrequest(ctx, Keyreset, 0, 0, &res);
|
|
nexttrace(t, &f, Keyreset, ctx);
|
|
checkstr(t, "preedit", "", &res.preedit);
|
|
checkenginepreedit(t, "");
|
|
CT_CHECK(t, ctx->focused);
|
|
CT_EQ_PTR(t, ctx, testengineowner());
|
|
res = contextkey(t, &f, ctx, 'k', 0);
|
|
checkstr(t, "preedit", "k", &res.preedit);
|
|
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());
|
|
checkdrop(t, &f, ctx, "/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());
|
|
contextkey(t, &f, reused, 'n', 0);
|
|
CT_CHECK(t, reused->focused);
|
|
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);
|
|
}
|