Cclientpreedit was a one-bit mask that every producer set as "want ? Cclientpreedit : 0" and every consumer masked out again; nothing else was ever going to join it. Keyreq carries clientpre, an int that says whether the client draws the preedit, and the engine and the XIM context keep it under that name.
447 lines
11 KiB
C
447 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);
|
|
|
|
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;
|
|
|
|
memset(&res, 0, sizeof res);
|
|
CT_CHECK(t, processkey(ctx, sym, state, &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;
|
|
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, &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;
|
|
|
|
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"));
|
|
|
|
memset(&res, 0, sizeof res);
|
|
CT_CHECK(t, !processkey(a, 'x', 0, &res));
|
|
CT_CHECK(t, !res.eaten);
|
|
notrace(t, &f);
|
|
CT_EQ_PTR(t, nil, testengineowner());
|
|
|
|
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);
|
|
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, &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());
|
|
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);
|
|
}
|