Files
strans/tests/ibus_test.c
Hojun-Cho ef7fb627c6 compose: one Compose table for the XIM and IBus frontends
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.
2026-08-17 12:16:07 +09:00

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);
}