ibus: support modern client preedit

This commit is contained in:
2026-08-14 19:59:52 +09:00
parent a841894015
commit 7619d2bd9d
6 changed files with 557 additions and 47 deletions

View File

@@ -35,6 +35,7 @@ enum
};
typedef struct Daemon Daemon;
typedef struct Attrlog Attrlog;
typedef struct Siglog Siglog;
struct Daemon
@@ -54,17 +55,31 @@ struct Daemon
size_t nerr;
};
struct Attrlog
{
int n;
dbus_uint32_t type;
dbus_uint32_t value;
dbus_uint32_t start;
dbus_uint32_t end;
};
struct Siglog
{
int invalid;
int npreedit;
int nlegacy;
int nmodern;
int ncommit;
char preeditpath[96];
char commitpath[96];
char preedit[256];
char commit[256];
dbus_uint32_t cursor;
dbus_uint32_t mode;
dbus_bool_t visible;
Attrlog preeditattrs;
Attrlog commitattrs;
};
static int
@@ -497,10 +512,44 @@ emptyarray(DBusMessageIter *it, int element)
}
static int
ibusattrs(DBusMessageIter *it)
ibusattribute(DBusMessageIter *it, Attrlog *log)
{
DBusMessageIter v, st;
const char *name;
dbus_uint32_t *field[] = {
&log->type, &log->value, &log->start, &log->end,
};
int i;
if(log->n != 0 || dbus_message_iter_get_arg_type(it) != DBUS_TYPE_VARIANT)
return 0;
dbus_message_iter_recurse(it, &v);
if(dbus_message_iter_get_arg_type(&v) != DBUS_TYPE_STRUCT)
return 0;
dbus_message_iter_recurse(&v, &st);
if(dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_STRING)
return 0;
dbus_message_iter_get_basic(&st, &name);
if(strcmp(name, "IBusAttribute") != 0 || !dbus_message_iter_next(&st) ||
!emptyarray(&st, DBUS_TYPE_DICT_ENTRY))
return 0;
for(i = 0; i < 4; i++){
if(!dbus_message_iter_next(&st) ||
dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_UINT32)
return 0;
dbus_message_iter_get_basic(&st, field[i]);
}
if(dbus_message_iter_next(&st) || dbus_message_iter_next(&v))
return 0;
log->n++;
return 1;
}
static int
ibusattrs(DBusMessageIter *it, Attrlog *log)
{
DBusMessageIter v, st, a;
const char *name;
if(dbus_message_iter_get_arg_type(it) != DBUS_TYPE_VARIANT)
return 0;
@@ -512,15 +561,23 @@ ibusattrs(DBusMessageIter *it)
return 0;
dbus_message_iter_get_basic(&st, &name);
if(strcmp(name, "IBusAttrList") != 0 || !dbus_message_iter_next(&st) ||
!emptyarray(&st, DBUS_TYPE_DICT_ENTRY) ||
!dbus_message_iter_next(&st) || !emptyarray(&st, DBUS_TYPE_VARIANT) ||
dbus_message_iter_next(&st))
!emptyarray(&st, DBUS_TYPE_DICT_ENTRY) || !dbus_message_iter_next(&st) ||
dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_ARRAY ||
dbus_message_iter_get_element_type(&st) != DBUS_TYPE_VARIANT)
return 0;
dbus_message_iter_recurse(&st, &a);
while(dbus_message_iter_get_arg_type(&a) != DBUS_TYPE_INVALID){
if(!ibusattribute(&a, log))
return 0;
dbus_message_iter_next(&a);
}
if(dbus_message_iter_next(&st))
return 0;
return !dbus_message_iter_next(&v);
}
static int
ibustext(DBusMessageIter *it, char *text, size_t ntext)
ibustext(DBusMessageIter *it, char *text, size_t ntext, Attrlog *attrs)
{
DBusMessageIter v, st;
const char *name, *s;
@@ -540,7 +597,7 @@ ibustext(DBusMessageIter *it, char *text, size_t ntext)
return 0;
dbus_message_iter_get_basic(&st, &s);
if(snprintf(text, ntext, "%s", s) >= (int)ntext ||
!dbus_message_iter_next(&st) || !ibusattrs(&st) ||
!dbus_message_iter_next(&st) || !ibusattrs(&st, attrs) ||
dbus_message_iter_next(&st))
return 0;
return !dbus_message_iter_next(&v);
@@ -560,13 +617,15 @@ onsignal(DBusConnection *conn, DBusMessage *m, void *arg)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
if(dbus_message_has_member(m, "UpdatePreeditText")){
log->npreedit++;
log->nlegacy++;
path = dbus_message_get_path(m);
if(path == NULL || snprintf(log->preeditpath,
sizeof log->preeditpath, "%s", path) >=
(int)sizeof log->preeditpath ||
!dbus_message_has_signature(m, "vub") ||
!dbus_message_iter_init(m, &it) ||
!ibustext(&it, log->preedit, sizeof log->preedit) ||
!ibustext(&it, log->preedit, sizeof log->preedit,
&log->preeditattrs) ||
!dbus_message_iter_next(&it) ||
dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_UINT32){
log->invalid = 1;
@@ -583,6 +642,38 @@ onsignal(DBusConnection *conn, DBusMessage *m, void *arg)
log->invalid = 1;
return DBUS_HANDLER_RESULT_HANDLED;
}
if(dbus_message_has_member(m, "UpdatePreeditTextWithMode")){
log->npreedit++;
log->nmodern++;
path = dbus_message_get_path(m);
if(path == NULL || snprintf(log->preeditpath,
sizeof log->preeditpath, "%s", path) >=
(int)sizeof log->preeditpath ||
!dbus_message_has_signature(m, "vubu") ||
!dbus_message_iter_init(m, &it) ||
!ibustext(&it, log->preedit, sizeof log->preedit,
&log->preeditattrs) || !dbus_message_iter_next(&it) ||
dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_UINT32){
log->invalid = 1;
return DBUS_HANDLER_RESULT_HANDLED;
}
dbus_message_iter_get_basic(&it, &log->cursor);
if(!dbus_message_iter_next(&it) ||
dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_BOOLEAN){
log->invalid = 1;
return DBUS_HANDLER_RESULT_HANDLED;
}
dbus_message_iter_get_basic(&it, &log->visible);
if(!dbus_message_iter_next(&it) ||
dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_UINT32){
log->invalid = 1;
return DBUS_HANDLER_RESULT_HANDLED;
}
dbus_message_iter_get_basic(&it, &log->mode);
if(dbus_message_iter_next(&it))
log->invalid = 1;
return DBUS_HANDLER_RESULT_HANDLED;
}
if(dbus_message_has_member(m, "CommitText")){
log->ncommit++;
path = dbus_message_get_path(m);
@@ -591,7 +682,8 @@ onsignal(DBusConnection *conn, DBusMessage *m, void *arg)
(int)sizeof log->commitpath ||
!dbus_message_has_signature(m, "v") ||
!dbus_message_iter_init(m, &it) ||
!ibustext(&it, log->commit, sizeof log->commit) ||
!ibustext(&it, log->commit, sizeof log->commit,
&log->commitattrs) ||
dbus_message_iter_next(&it))
log->invalid = 1;
return DBUS_HANDLER_RESULT_HANDLED;
@@ -903,6 +995,43 @@ contentcall(DBusConnection *conn, Siglog *log, char *path,
return 1;
}
static int
clientcommitcall(DBusConnection *conn, Siglog *log, char *path, int enabled)
{
DBusMessage *m, *reply;
DBusMessageIter it, v, st;
const char *iface, *property;
dbus_bool_t b;
resetsig(log);
iface = "org.freedesktop.IBus.InputContext";
property = "ClientCommitPreedit";
b = enabled ? TRUE : FALSE;
m = method(path, "org.freedesktop.DBus.Properties", "Set");
if(m == NULL)
return 0;
dbus_message_iter_init_append(m, &it);
if(!dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &iface) ||
!dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &property) ||
!dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, "(b)", &v) ||
!dbus_message_iter_open_container(&v, DBUS_TYPE_STRUCT, NULL, &st) ||
!dbus_message_iter_append_basic(&st, DBUS_TYPE_BOOLEAN, &b) ||
!dbus_message_iter_close_container(&v, &st) ||
!dbus_message_iter_close_container(&it, &v)){
dbus_message_unref(m);
return fail("build Properties.Set ClientCommitPreedit call");
}
reply = sendcall(conn, m);
if(reply == NULL)
return 0;
if(!dbus_message_has_signature(reply, "")){
dbus_message_unref(reply);
return fail("Properties.Set ClientCommitPreedit returned a nonempty reply");
}
dbus_message_unref(reply);
return 1;
}
static int
keycall(DBusConnection *conn, Siglog *log, char *path, dbus_uint32_t sym,
dbus_uint32_t state, int *eaten)
@@ -1040,11 +1169,22 @@ nosignal(Siglog *log, char *where)
return 1;
}
static int
preeditattrs(Siglog *log)
{
if(log->preedit[0] == '\0')
return log->preeditattrs.n == 0;
return log->preeditattrs.n == 1 && log->preeditattrs.type == 1 &&
log->preeditattrs.value == 1 && log->preeditattrs.start == 0 &&
log->preeditattrs.end == log->cursor;
}
static int
preedit(Siglog *log, char *path, char *text, unsigned int cursor, int visible,
char *where)
{
if(log->invalid || log->npreedit != 1 || log->ncommit != 0 ||
if(log->invalid || log->npreedit != 1 || log->nlegacy != 1 ||
log->nmodern != 0 || log->ncommit != 0 || !preeditattrs(log) ||
strcmp(log->preeditpath, path) != 0 ||
strcmp(log->preedit, text) != 0 ||
log->cursor != cursor || (log->visible != FALSE) != (visible != 0))
@@ -1054,20 +1194,55 @@ preedit(Siglog *log, char *path, char *text, unsigned int cursor, int visible,
return 1;
}
static int
modernpreedit(Siglog *log, char *path, char *text, unsigned int cursor,
int visible, char *where)
{
if(log->invalid || log->npreedit != 1 || log->nlegacy != 0 ||
log->nmodern != 1 || log->ncommit != 0 || log->mode != 0 ||
!preeditattrs(log) || strcmp(log->preeditpath, path) != 0 ||
strcmp(log->preedit, text) != 0 || log->cursor != cursor ||
(log->visible != FALSE) != (visible != 0))
return fail("%s modern preedit: legacy=%d modern=%d commit=%d text=%s cursor=%u visible=%d mode=%u attrs=%d",
where, log->nlegacy, log->nmodern, log->ncommit, log->preedit,
(unsigned)log->cursor, log->visible != FALSE,
(unsigned)log->mode, log->preeditattrs.n);
return 1;
}
static int
committed(Siglog *log, char *path, char *text, char *where)
{
if(log->invalid || log->npreedit != 1 || log->ncommit != 1 ||
if(log->invalid || log->npreedit != 1 || log->nlegacy != 1 ||
log->nmodern != 0 || log->ncommit != 1 ||
strcmp(log->preeditpath, path) != 0 ||
strcmp(log->commitpath, path) != 0 ||
strcmp(log->commit, text) != 0 ||
strcmp(log->preedit, "") != 0 || log->cursor != 0 ||
log->preeditattrs.n != 0 || log->commitattrs.n != 0 ||
log->visible != FALSE)
return fail("%s signals: preedit=%d commit=%d text=%s/%s",
where, log->npreedit, log->ncommit, log->commit, log->preedit);
return 1;
}
static int
moderncommitted(Siglog *log, char *path, char *text, char *where)
{
if(log->invalid || log->npreedit != 1 || log->nlegacy != 0 ||
log->nmodern != 1 || log->ncommit != 1 || log->mode != 0 ||
strcmp(log->preeditpath, path) != 0 ||
strcmp(log->commitpath, path) != 0 || strcmp(log->commit, text) != 0 ||
strcmp(log->preedit, "") != 0 || log->cursor != 0 ||
log->preeditattrs.n != 0 || log->commitattrs.n != 0 ||
log->visible != FALSE)
return fail("%s modern signals: legacy=%d modern=%d commit=%d text=%s/%s attrs=%d/%d",
where, log->nlegacy, log->nmodern, log->ncommit,
log->commit, log->preedit, log->commitattrs.n,
log->preeditattrs.n);
return 1;
}
static int
expectkey(DBusConnection *conn, Siglog *log, char *path,
dbus_uint32_t sym, dbus_uint32_t state, int eaten, char *text, char *where)
@@ -1370,6 +1545,30 @@ runpolicy(Daemon *d)
!nosignal(&s1, "free-form property") ||
!expectkey(c1, &s1, a, 'n', 0, 1, "", "free-form resumes"))
goto out;
/* Modern clients receive only WithMode and code-point attribute ranges. */
if(!emptycall(c1, &s1, a, "Reset") ||
!preedit(&s1, a, "", 0, 0, "modern setup reset") ||
!clientcommitcall(c1, &s1, a, 1) ||
!nosignal(&s1, "enable client preedit commit") ||
!keycall(c1, &s1, a, 'k', 0, &eaten) || !eaten ||
!modernpreedit(&s1, a, "k", 1, 1, "modern key k") ||
!keycall(c1, &s1, a, 'a', 0, &eaten) || !eaten ||
!modernpreedit(&s1, a, "", 1, 1, "modern key a") ||
!keycall(c1, &s1, a, 'n', 0, &eaten) || !eaten ||
!modernpreedit(&s1, a, "かん", 2, 1, "modern multibyte") ||
!emptycall(c1, &s1, a, "Reset") ||
!modernpreedit(&s1, a, "", 0, 0, "modern empty clear") ||
!keycall(c1, &s1, a, 'k', 0, &eaten) || !eaten ||
!modernpreedit(&s1, a, "k", 1, 1, "modern commit key k") ||
!keycall(c1, &s1, a, 'a', 0, &eaten) || !eaten ||
!modernpreedit(&s1, a, "", 1, 1, "modern commit key a") ||
!keycall(c1, &s1, a, '0', 0, &eaten) || !eaten ||
!moderncommitted(&s1, a, "", "modern commit") ||
!clientcommitcall(c1, &s1, a, 0) ||
!nosignal(&s1, "disable client preedit commit") ||
!expectkey(c1, &s1, a, 'k', 0, 1, "k", "legacy restored"))
goto out;
ok = 1;
out:
closebus(&c1, &s1);
@@ -1377,14 +1576,61 @@ out:
return ok;
}
static int
runclient(Daemon *d, char *program)
{
struct timespec pause;
pid_t pid, n;
int status;
int64_t deadline;
pid = fork();
if(pid < 0)
return fail("fork official libibus client: %s", strerror(errno));
if(pid == 0){
if(setenv("XDG_RUNTIME_DIR", d->runtime, 1) < 0 ||
setenv("XDG_CONFIG_HOME", d->config, 1) < 0 ||
setenv("HOME", d->home, 1) < 0)
_exit(126);
execl(program, program, d->address, (char*)0);
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
_exit(127);
}
pause.tv_sec = 0;
pause.tv_nsec = 10000000;
deadline = nowms() + Starttimeout;
for(;;){
n = waitpid(pid, &status, WNOHANG);
if(n == pid)
break;
if(n < 0 && errno == EINTR)
continue;
if(n < 0)
return fail("wait official libibus client: %s", strerror(errno));
if(leftms(deadline) == 0){
fail("official libibus client timed out");
kill(pid, SIGKILL);
do
n = waitpid(pid, &status, 0);
while(n < 0 && errno == EINTR);
return 0;
}
nanosleep(&pause, NULL);
}
if(!WIFEXITED(status) || WEXITSTATUS(status) != 0)
return fail("official libibus client exited with wait status %#x",
status);
return 1;
}
int
main(int argc, char **argv)
{
Daemon daemon;
int ok;
if(argc != 3){
fprintf(stderr, "usage: ibus_live_test strans mapdir\n");
if(argc != 4){
fprintf(stderr, "usage: ibus_live_test strans mapdir libibus-client\n");
return 2;
}
ok = startdaemon(&daemon, argv[1], argv[2]);
@@ -1394,6 +1640,8 @@ main(int argc, char **argv)
ok = runlifecycle(&daemon);
if(ok)
ok = runpolicy(&daemon);
if(ok)
ok = runclient(&daemon, argv[3]);
if(!ok)
showerrors(&daemon);
if(!stopdaemon(&daemon))