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

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@
/tests/*.o /tests/*.o
/tests/unit_test /tests/unit_test
/tests/ibus_live_test /tests/ibus_live_test
/tests/ibus_client_smoke
/tests/gtk_live_test /tests/gtk_live_test
/tests/ipc_live_test /tests/ipc_live_test
/tests/daemon_collision_test /tests/daemon_collision_test

View File

@@ -6,6 +6,7 @@ RUN pacman -Syu --noconfirm --needed \
diffutils \ diffutils \
gcc \ gcc \
gtk3 \ gtk3 \
ibus \
harfbuzz \ harfbuzz \
libxcb \ libxcb \
libxkbcommon \ libxkbcommon \

164
ibus.c
View File

@@ -22,6 +22,9 @@ enum
Ibuspurposepassword = 8, Ibuspurposepassword = 8,
Ibuspurposepin = 9, Ibuspurposepin = 9,
Ibushinthidden = 1<<12, Ibushinthidden = 1<<12,
Ibusattrunderline = 1,
Ibusunderlinesingle = 1,
Ibuspreeditclear = 0,
}; };
typedef struct Ictx Ictx; typedef struct Ictx Ictx;
@@ -33,6 +36,7 @@ struct Ictx
u32int cap; u32int cap;
u32int purpose; u32int purpose;
u32int hints; u32int hints;
int clientcommitpreedit;
Caret caret; Caret caret;
}; };
@@ -49,8 +53,11 @@ static int addrowned;
static int icctr; static int icctr;
static int busctr; static int busctr;
static Channel *replyc; static Channel *replyc;
static const char ibusowner[] = ":1.0";
static DBusHandlerResult onmsg(DBusConnection*, DBusMessage*, void*); static DBusHandlerResult onmsg(DBusConnection*, DBusMessage*, void*);
static DBusHandlerResult handleerror(DBusConnection*, DBusMessage*,
const char*, const char*);
static void static void
unlinkaddr(void) unlinkaddr(void)
@@ -380,11 +387,14 @@ setcursor(Ictx *ctx, int x, int y, int h)
} }
static void static void
appendibustext(DBusMessageIter *it, const char *s) appendibustext(DBusMessageIter *it, const char *s, int underline)
{ {
DBusMessageIter v, st, attach, alv, ali, attr, alist; DBusMessageIter v, st, attach, alv, ali, attr, alist;
DBusMessageIter av, ai, aattach;
const char *name = "IBusText"; const char *name = "IBusText";
const char *aname = "IBusAttrList"; const char *aname = "IBusAttrList";
const char *iname = "IBusAttribute";
dbus_uint32_t type, value, start, end;
dbus_message_iter_open_container(it, DBUS_TYPE_VARIANT, "(sa{sv}sv)", &v); dbus_message_iter_open_container(it, DBUS_TYPE_VARIANT, "(sa{sv}sv)", &v);
dbus_message_iter_open_container(&v, DBUS_TYPE_STRUCT, nil, &st); dbus_message_iter_open_container(&v, DBUS_TYPE_STRUCT, nil, &st);
@@ -398,6 +408,25 @@ appendibustext(DBusMessageIter *it, const char *s)
dbus_message_iter_open_container(&ali, DBUS_TYPE_ARRAY, "{sv}", &attr); dbus_message_iter_open_container(&ali, DBUS_TYPE_ARRAY, "{sv}", &attr);
dbus_message_iter_close_container(&ali, &attr); dbus_message_iter_close_container(&ali, &attr);
dbus_message_iter_open_container(&ali, DBUS_TYPE_ARRAY, "v", &alist); dbus_message_iter_open_container(&ali, DBUS_TYPE_ARRAY, "v", &alist);
if(underline && s[0] != '\0'){
type = Ibusattrunderline;
value = Ibusunderlinesingle;
start = 0;
end = utflen((char*)s);
dbus_message_iter_open_container(&alist, DBUS_TYPE_VARIANT,
"(sa{sv}uuuu)", &av);
dbus_message_iter_open_container(&av, DBUS_TYPE_STRUCT, nil, &ai);
dbus_message_iter_append_basic(&ai, DBUS_TYPE_STRING, &iname);
dbus_message_iter_open_container(&ai, DBUS_TYPE_ARRAY, "{sv}",
&aattach);
dbus_message_iter_close_container(&ai, &aattach);
dbus_message_iter_append_basic(&ai, DBUS_TYPE_UINT32, &type);
dbus_message_iter_append_basic(&ai, DBUS_TYPE_UINT32, &value);
dbus_message_iter_append_basic(&ai, DBUS_TYPE_UINT32, &start);
dbus_message_iter_append_basic(&ai, DBUS_TYPE_UINT32, &end);
dbus_message_iter_close_container(&av, &ai);
dbus_message_iter_close_container(&alist, &av);
}
dbus_message_iter_close_container(&ali, &alist); dbus_message_iter_close_container(&ali, &alist);
dbus_message_iter_close_container(&alv, &ali); dbus_message_iter_close_container(&alv, &ali);
dbus_message_iter_close_container(&st, &alv); dbus_message_iter_close_container(&st, &alv);
@@ -415,30 +444,39 @@ emitcommit(DBusConnection *c, const char *path, const char *text)
if(sig == nil) if(sig == nil)
return; return;
dbus_message_iter_init_append(sig, &it); dbus_message_iter_init_append(sig, &it);
appendibustext(&it, text); appendibustext(&it, text, 0);
dbus_message_set_sender(sig, ibusowner);
dbus_connection_send(c, sig, nil); dbus_connection_send(c, sig, nil);
dbus_message_unref(sig); dbus_message_unref(sig);
} }
static void static void
emitpreedit(DBusConnection *c, const char *path, const char *text) emitpreedit(Ictx *ctx, const char *text)
{ {
DBusMessage *sig; DBusMessage *sig;
DBusMessageIter it; DBusMessageIter it;
dbus_uint32_t cursor; dbus_uint32_t cursor, mode;
dbus_bool_t visible; dbus_bool_t visible;
const char *name;
sig = dbus_message_new_signal(path, name = ctx->clientcommitpreedit ?
"org.freedesktop.IBus.InputContext", "UpdatePreeditText"); "UpdatePreeditTextWithMode" : "UpdatePreeditText";
sig = dbus_message_new_signal(ctx->path,
"org.freedesktop.IBus.InputContext", name);
if(sig == nil) if(sig == nil)
return; return;
dbus_message_iter_init_append(sig, &it); dbus_message_iter_init_append(sig, &it);
appendibustext(&it, text); appendibustext(&it, text, 1);
cursor = utflen((char*)text); cursor = utflen((char*)text);
visible = text[0] != '\0' ? TRUE : FALSE; visible = text[0] != '\0' ? TRUE : FALSE;
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &cursor); dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &cursor);
dbus_message_iter_append_basic(&it, DBUS_TYPE_BOOLEAN, &visible); dbus_message_iter_append_basic(&it, DBUS_TYPE_BOOLEAN, &visible);
dbus_connection_send(c, sig, nil); if(ctx->clientcommitpreedit){
mode = Ibuspreeditclear;
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &mode);
}
dbus_message_set_sender(sig, ibusowner);
dbus_connection_send(ctx->conn, sig, nil);
dbus_message_unref(sig); dbus_message_unref(sig);
} }
@@ -459,6 +497,33 @@ handlehello(DBusConnection *c, DBusMessage *m)
return DBUS_HANDLER_RESULT_HANDLED; return DBUS_HANDLER_RESULT_HANDLED;
} }
static DBusHandlerResult
handlenameowner(DBusConnection *c, DBusMessage *m)
{
DBusError err;
DBusMessage *r;
const char *name, *owner;
dbus_error_init(&err);
if(!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID)){
dbus_error_free(&err);
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
"GetNameOwner expects one bus name");
}
if(strcmp(name, "org.freedesktop.IBus") != 0)
return handleerror(c, m, DBUS_ERROR_NAME_HAS_NO_OWNER,
"bus name has no owner");
owner = ibusowner;
r = dbus_message_new_method_return(m);
if(r == nil)
return DBUS_HANDLER_RESULT_NEED_MEMORY;
dbus_message_append_args(r, DBUS_TYPE_STRING, &owner, DBUS_TYPE_INVALID);
dbus_connection_send(c, r, nil);
dbus_message_unref(r);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult static DBusHandlerResult
handleerror(DBusConnection *c, DBusMessage *m, const char *name, handleerror(DBusConnection *c, DBusMessage *m, const char *name,
const char *text) const char *text)
@@ -539,7 +604,7 @@ handlekey(DBusConnection *c, DBusMessage *m, Ictx *ctx)
if(commit[0] != '\0') if(commit[0] != '\0')
emitcommit(c, dbus_message_get_path(m), commit); emitcommit(c, dbus_message_get_path(m), commit);
if(ctx->cap & Ibuscappreedit) if(ctx->cap & Ibuscappreedit)
emitpreedit(c, dbus_message_get_path(m), preedit); emitpreedit(ctx, preedit);
return handlebool(c, m, res.eaten); return handlebool(c, m, res.eaten);
} }
@@ -560,7 +625,7 @@ handlereset(DBusConnection *c, DBusMessage *m, Ictx *ctx)
Keyres res; Keyres res;
sendrequest(ctx, Keyreset, 0, 0, &res); sendrequest(ctx, Keyreset, 0, 0, &res);
emitpreedit(c, dbus_message_get_path(m), ""); emitpreedit(ctx, "");
return handlenoop(c, m); return handlenoop(c, m);
} }
@@ -575,7 +640,7 @@ static DBusHandlerResult
handlefocusout(DBusConnection *c, DBusMessage *m, Ictx *ctx) handlefocusout(DBusConnection *c, DBusMessage *m, Ictx *ctx)
{ {
releasecontext(ctx); releasecontext(ctx);
emitpreedit(c, dbus_message_get_path(m), ""); emitpreedit(ctx, "");
return handlenoop(c, m); return handlenoop(c, m);
} }
@@ -602,20 +667,19 @@ handlecap(DBusConnection *c, DBusMessage *m, Ictx *ctx)
if((old & Ibuscappreedit) != (ctx->cap & Ibuscappreedit)){ if((old & Ibuscappreedit) != (ctx->cap & Ibuscappreedit)){
if((ctx->cap & Ibuscappreedit) && res.eaten){ if((ctx->cap & Ibuscappreedit) && res.eaten){
stoutf(&res.preedit, preedit, sizeof preedit); stoutf(&res.preedit, preedit, sizeof preedit);
emitpreedit(c, ctx->path, preedit); emitpreedit(ctx, preedit);
}else if(!(ctx->cap & Ibuscappreedit)) }else if(!(ctx->cap & Ibuscappreedit))
emitpreedit(c, ctx->path, ""); emitpreedit(ctx, "");
} }
} }
return handlenoop(c, m); return handlenoop(c, m);
} }
static int static int
getcontent(DBusMessage *m, const char **iface, const char **name, getproperty(DBusMessage *m, const char **iface, const char **name,
u32int *purpose, u32int *hints) DBusMessageIter *value)
{ {
DBusMessageIter it, v, st; DBusMessageIter it;
dbus_uint32_t p, h;
if(!dbus_message_has_signature(m, "ssv") || if(!dbus_message_has_signature(m, "ssv") ||
!dbus_message_iter_init(m, &it)) !dbus_message_iter_init(m, &it))
@@ -624,10 +688,19 @@ getcontent(DBusMessage *m, const char **iface, const char **name,
dbus_message_iter_next(&it); dbus_message_iter_next(&it);
dbus_message_iter_get_basic(&it, name); dbus_message_iter_get_basic(&it, name);
dbus_message_iter_next(&it); dbus_message_iter_next(&it);
dbus_message_iter_recurse(&it, &v); dbus_message_iter_recurse(&it, value);
if(dbus_message_iter_get_arg_type(&v) != DBUS_TYPE_STRUCT) return 1;
}
static int
getcontent(DBusMessageIter *value, u32int *purpose, u32int *hints)
{
DBusMessageIter st;
dbus_uint32_t p, h;
if(dbus_message_iter_get_arg_type(value) != DBUS_TYPE_STRUCT)
return 0; return 0;
dbus_message_iter_recurse(&v, &st); dbus_message_iter_recurse(value, &st);
if(dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_UINT32) if(dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_UINT32)
return 0; return 0;
dbus_message_iter_get_basic(&st, &p); dbus_message_iter_get_basic(&st, &p);
@@ -646,27 +719,48 @@ static DBusHandlerResult
handlepropertyset(DBusConnection *c, DBusMessage *m, Ictx *ctx) handlepropertyset(DBusConnection *c, DBusMessage *m, Ictx *ctx)
{ {
const char *iface, *name; const char *iface, *name;
DBusMessageIter value, st;
dbus_bool_t b;
u32int purpose, hints; u32int purpose, hints;
Keyres res; Keyres res;
int washidden; int washidden;
if(!getcontent(m, &iface, &name, &purpose, &hints)) if(!getproperty(m, &iface, &name, &value))
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS, return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
"Properties.Set expects ContentType as (uu)"); "Properties.Set expects interface, property, and value");
if(strcmp(iface, "org.freedesktop.IBus.InputContext") != 0) if(strcmp(iface, "org.freedesktop.IBus.InputContext") != 0)
return handleerror(c, m, DBUS_ERROR_UNKNOWN_INTERFACE, return handleerror(c, m, DBUS_ERROR_UNKNOWN_INTERFACE,
"unknown property interface"); "unknown property interface");
if(strcmp(name, "ContentType") != 0) if(strcmp(name, "ContentType") == 0){
return handleerror(c, m, DBUS_ERROR_UNKNOWN_PROPERTY, if(!getcontent(&value, &purpose, &hints))
"unknown input context property"); return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
washidden = hidden(ctx); "ContentType expects (uu)");
ctx->purpose = purpose; washidden = hidden(ctx);
ctx->hints = hints; ctx->purpose = purpose;
if(ctx->focused && !washidden && hidden(ctx)){ ctx->hints = hints;
sendrequest(ctx, Keyreset, 0, 0, &res); if(ctx->focused && !washidden && hidden(ctx)){
emitpreedit(c, ctx->path, ""); sendrequest(ctx, Keyreset, 0, 0, &res);
emitpreedit(ctx, "");
}
return handlenoop(c, m);
} }
return handlenoop(c, m); if(strcmp(name, "ClientCommitPreedit") == 0){
if(dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_STRUCT)
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
"ClientCommitPreedit expects (b)");
dbus_message_iter_recurse(&value, &st);
if(dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_BOOLEAN)
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
"ClientCommitPreedit expects (b)");
dbus_message_iter_get_basic(&st, &b);
if(dbus_message_iter_next(&st))
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
"ClientCommitPreedit expects (b)");
ctx->clientcommitpreedit = b != FALSE;
return handlenoop(c, m);
}
return handleerror(c, m, DBUS_ERROR_UNKNOWN_PROPERTY,
"unknown input context property");
} }
static DBusHandlerResult static DBusHandlerResult
@@ -778,9 +872,13 @@ static const char introspectxml[] =
"<arg direction=\"in\" type=\"i\"/><arg direction=\"in\" type=\"i\"/></method>\n" "<arg direction=\"in\" type=\"i\"/><arg direction=\"in\" type=\"i\"/></method>\n"
" <method name=\"SetEngine\"><arg direction=\"in\" type=\"s\"/></method>\n" " <method name=\"SetEngine\"><arg direction=\"in\" type=\"s\"/></method>\n"
" <property name=\"ContentType\" type=\"(uu)\" access=\"write\"/>\n" " <property name=\"ContentType\" type=\"(uu)\" access=\"write\"/>\n"
" <property name=\"ClientCommitPreedit\" type=\"(b)\" access=\"write\"/>\n"
" <signal name=\"CommitText\"><arg type=\"v\"/></signal>\n" " <signal name=\"CommitText\"><arg type=\"v\"/></signal>\n"
" <signal name=\"UpdatePreeditText\">" " <signal name=\"UpdatePreeditText\">"
"<arg type=\"v\"/><arg type=\"u\"/><arg type=\"b\"/></signal>\n" "<arg type=\"v\"/><arg type=\"u\"/><arg type=\"b\"/></signal>\n"
" <signal name=\"UpdatePreeditTextWithMode\">"
"<arg type=\"v\"/><arg type=\"u\"/><arg type=\"b\"/>"
"<arg type=\"u\"/></signal>\n"
" </interface>\n" " </interface>\n"
" <interface name=\"org.freedesktop.DBus.Properties\">\n" " <interface name=\"org.freedesktop.DBus.Properties\">\n"
" <method name=\"Get\"><arg direction=\"in\" type=\"s\"/>" " <method name=\"Get\"><arg direction=\"in\" type=\"s\"/>"
@@ -825,6 +923,8 @@ onmsg(DBusConnection *c, DBusMessage *m, void *_)
if(strcmp(iface, "org.freedesktop.DBus") == 0){ if(strcmp(iface, "org.freedesktop.DBus") == 0){
if(strcmp(member, "Hello") == 0) if(strcmp(member, "Hello") == 0)
return handlehello(c, m); return handlehello(c, m);
if(strcmp(member, "GetNameOwner") == 0)
return handlenameowner(c, m);
if(strcmp(member, "AddMatch") == 0 if(strcmp(member, "AddMatch") == 0
|| strcmp(member, "RemoveMatch") == 0) || strcmp(member, "RemoveMatch") == 0)
return handlenoop(c, m); return handlenoop(c, m);

View File

@@ -10,13 +10,15 @@ GTK_CFLAGS = $(shell pkg-config --cflags gtk+-3.0)
GTK_LIBS = $(shell pkg-config --libs gtk+-3.0) GTK_LIBS = $(shell pkg-config --libs gtk+-3.0)
IBUS_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon) IBUS_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
IBUS_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon) IBUS_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
IBUS_CLIENT_CFLAGS = $(shell pkg-config --cflags ibus-1.0)
IBUS_CLIENT_LIBS = $(shell pkg-config --libs ibus-1.0)
XIM_CFLAGS = $(shell pkg-config --cflags xcb-imdkit xkbcommon) XIM_CFLAGS = $(shell pkg-config --cflags xcb-imdkit xkbcommon)
XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon) XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon)
LIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS) LIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS)
PROG = unit_test PROG = unit_test
LIVE = ibus_live_test gtk_live_test ipc_live_test daemon_collision_test daemon_failure_test \ LIVE = ibus_live_test ibus_client_smoke gtk_live_test ipc_live_test \
daemon_restart_test daemon_collision_test daemon_failure_test daemon_restart_test
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \ TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \ ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
popup_test.c font_test.c ibus_test.c server_test.c xim_adapter_test.c popup_test.c font_test.c ibus_test.c server_test.c xim_adapter_test.c
@@ -31,7 +33,7 @@ all: $(PROG) $(LIVE)
check test: $(PROG) $(LIVE) check test: $(PROG) $(LIVE)
./$(PROG) $(TESTARGS) ./$(PROG) $(TESTARGS)
./ibus_live_test ../strans ../map ./ibus_live_test ../strans ../map ./ibus_client_smoke
./gtk_live_test ../gtk/im-strans.so ./gtk_live_test ../gtk/im-strans.so
./ipc_live_test ../strans ../map ./ipc_live_test ../strans ../map
./daemon_collision_test ../strans ../map ./daemon_collision_test ../strans ../map
@@ -44,6 +46,10 @@ $(PROG): $(OBJS)
ibus_live_test: ibus_live_test.c ibus_live_test: ibus_live_test.c
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g $(DBUS_CFLAGS) -o $@ $< $(DBUS_LIBS) $(HOSTCC) -std=c99 -Wall -Wextra -O2 -g $(DBUS_CFLAGS) -o $@ $< $(DBUS_LIBS)
ibus_client_smoke: ibus_client_smoke.c
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g $(IBUS_CLIENT_CFLAGS) -o $@ $< \
$(IBUS_CLIENT_LIBS)
gtk_live_test: gtk_live_test.c ../ipc.c ../ipc.h gtk_live_test: gtk_live_test.c ../ipc.c ../ipc.h
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(GTK_CFLAGS) -o $@ \ $(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(GTK_CFLAGS) -o $@ \
gtk_live_test.c ../ipc.c $(GTK_LIBS) -pthread gtk_live_test.c ../ipc.c $(GTK_LIBS) -pthread

154
tests/ibus_client_smoke.c Normal file
View File

@@ -0,0 +1,154 @@
#define _POSIX_C_SOURCE 200809L
#include <ibus.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Log Log;
struct Log
{
GMainLoop *loop;
int want;
int legacy;
int modern;
int invalid;
int sawtext;
int sawempty;
};
static void
legacy(IBusInputContext *ctx, IBusText *text, guint cursor,
gboolean visible, void *arg)
{
Log *log;
(void)ctx;
(void)text;
(void)cursor;
(void)visible;
log = arg;
log->legacy++;
}
static void
modern(IBusInputContext *ctx, IBusText *text, guint cursor,
gboolean visible, guint mode, void *arg)
{
IBusAttrList *attrs;
IBusAttribute *a;
Log *log;
const char *s;
(void)ctx;
log = arg;
log->modern++;
s = ibus_text_get_text(text);
attrs = ibus_text_get_attributes(text);
a = ibus_attr_list_get(attrs, 0);
if(s[0] != '\0'){
if(strcmp(s, "k") != 0 || cursor != 1 || !visible ||
mode != IBUS_ENGINE_PREEDIT_CLEAR || a == NULL ||
a->type != IBUS_ATTR_TYPE_UNDERLINE ||
a->value != IBUS_ATTR_UNDERLINE_SINGLE ||
a->start_index != 0 || a->end_index != 1 ||
ibus_attr_list_get(attrs, 1) != NULL)
log->invalid = 1;
else
log->sawtext = 1;
}else{
if(cursor != 0 || visible || mode != IBUS_ENGINE_PREEDIT_CLEAR ||
a != NULL)
log->invalid = 1;
else
log->sawempty = 1;
}
if(log->loop != NULL && log->modern >= log->want)
g_main_loop_quit(log->loop);
}
static gboolean
timeout(void *arg)
{
g_main_loop_quit(arg);
return G_SOURCE_REMOVE;
}
static int
waitmodern(Log *log, int want)
{
guint timer;
log->want = want;
if(log->modern >= want)
return 1;
log->loop = g_main_loop_new(NULL, FALSE);
timer = g_timeout_add_seconds(4, timeout, log->loop);
g_main_loop_run(log->loop);
if(g_main_context_find_source_by_id(NULL, timer) != NULL)
g_source_remove(timer);
g_main_loop_unref(log->loop);
log->loop = NULL;
return log->modern >= want;
}
int
main(int argc, char **argv)
{
IBusBus *bus;
IBusInputContext *ctx;
Log log;
int ok;
if(argc != 2){
fprintf(stderr, "usage: ibus_client_smoke address\n");
return 2;
}
if(setenv("IBUS_ADDRESS", argv[1], 1) < 0 ||
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 || unsetenv("DISPLAY") < 0){
perror("ibus_client_smoke: environment");
return 1;
}
memset(&log, 0, sizeof log);
ibus_init();
bus = ibus_bus_new();
if(bus == NULL || !ibus_bus_is_connected(bus)){
fprintf(stderr, "ibus_client_smoke: cannot connect to private bus\n");
if(bus != NULL) g_object_unref(bus);
return 1;
}
ctx = ibus_bus_create_input_context(bus, "strans-libibus-smoke");
if(ctx == NULL){
fprintf(stderr, "ibus_client_smoke: cannot create input context\n");
g_object_unref(bus);
return 1;
}
g_signal_connect(ctx, "update-preedit-text", G_CALLBACK(legacy), &log);
g_signal_connect(ctx, "update-preedit-text-with-mode", G_CALLBACK(modern),
&log);
ibus_input_context_set_capabilities(ctx, IBUS_CAP_PREEDIT_TEXT);
ibus_input_context_set_client_commit_preedit(ctx, TRUE);
ibus_input_context_focus_in(ctx);
ok = ibus_input_context_process_key_event(ctx, 'n', 0,
IBUS_CONTROL_MASK) && waitmodern(&log, 1) &&
ibus_input_context_process_key_event(ctx, 'k', 0, 0) &&
waitmodern(&log, 2);
ibus_input_context_reset(ctx);
if(ok)
ok = waitmodern(&log, 3);
ibus_input_context_focus_out(ctx);
if(ok)
ok = waitmodern(&log, 4);
if(log.legacy != 0 || log.modern != 4 || log.invalid ||
!log.sawtext || !log.sawempty)
ok = 0;
g_object_unref(ctx);
g_object_unref(bus);
if(!ok){
fprintf(stderr,
"ibus_client_smoke: legacy=%d modern=%d invalid=%d text=%d empty=%d\n",
log.legacy, log.modern, log.invalid, log.sawtext, log.sawempty);
return 1;
}
printf("official libibus client preedit: ok\n");
return 0;
}

View File

@@ -35,6 +35,7 @@ enum
}; };
typedef struct Daemon Daemon; typedef struct Daemon Daemon;
typedef struct Attrlog Attrlog;
typedef struct Siglog Siglog; typedef struct Siglog Siglog;
struct Daemon struct Daemon
@@ -54,17 +55,31 @@ struct Daemon
size_t nerr; 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 struct Siglog
{ {
int invalid; int invalid;
int npreedit; int npreedit;
int nlegacy;
int nmodern;
int ncommit; int ncommit;
char preeditpath[96]; char preeditpath[96];
char commitpath[96]; char commitpath[96];
char preedit[256]; char preedit[256];
char commit[256]; char commit[256];
dbus_uint32_t cursor; dbus_uint32_t cursor;
dbus_uint32_t mode;
dbus_bool_t visible; dbus_bool_t visible;
Attrlog preeditattrs;
Attrlog commitattrs;
}; };
static int static int
@@ -497,10 +512,44 @@ emptyarray(DBusMessageIter *it, int element)
} }
static int static int
ibusattrs(DBusMessageIter *it) ibusattribute(DBusMessageIter *it, Attrlog *log)
{ {
DBusMessageIter v, st; DBusMessageIter v, st;
const char *name; 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) if(dbus_message_iter_get_arg_type(it) != DBUS_TYPE_VARIANT)
return 0; return 0;
@@ -512,15 +561,23 @@ ibusattrs(DBusMessageIter *it)
return 0; return 0;
dbus_message_iter_get_basic(&st, &name); dbus_message_iter_get_basic(&st, &name);
if(strcmp(name, "IBusAttrList") != 0 || !dbus_message_iter_next(&st) || if(strcmp(name, "IBusAttrList") != 0 || !dbus_message_iter_next(&st) ||
!emptyarray(&st, DBUS_TYPE_DICT_ENTRY) || !emptyarray(&st, DBUS_TYPE_DICT_ENTRY) || !dbus_message_iter_next(&st) ||
!dbus_message_iter_next(&st) || !emptyarray(&st, DBUS_TYPE_VARIANT) || dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_ARRAY ||
dbus_message_iter_next(&st)) 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 0;
return !dbus_message_iter_next(&v); return !dbus_message_iter_next(&v);
} }
static int static int
ibustext(DBusMessageIter *it, char *text, size_t ntext) ibustext(DBusMessageIter *it, char *text, size_t ntext, Attrlog *attrs)
{ {
DBusMessageIter v, st; DBusMessageIter v, st;
const char *name, *s; const char *name, *s;
@@ -540,7 +597,7 @@ ibustext(DBusMessageIter *it, char *text, size_t ntext)
return 0; return 0;
dbus_message_iter_get_basic(&st, &s); dbus_message_iter_get_basic(&st, &s);
if(snprintf(text, ntext, "%s", s) >= (int)ntext || 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)) dbus_message_iter_next(&st))
return 0; return 0;
return !dbus_message_iter_next(&v); return !dbus_message_iter_next(&v);
@@ -560,13 +617,15 @@ onsignal(DBusConnection *conn, DBusMessage *m, void *arg)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
if(dbus_message_has_member(m, "UpdatePreeditText")){ if(dbus_message_has_member(m, "UpdatePreeditText")){
log->npreedit++; log->npreedit++;
log->nlegacy++;
path = dbus_message_get_path(m); path = dbus_message_get_path(m);
if(path == NULL || snprintf(log->preeditpath, if(path == NULL || snprintf(log->preeditpath,
sizeof log->preeditpath, "%s", path) >= sizeof log->preeditpath, "%s", path) >=
(int)sizeof log->preeditpath || (int)sizeof log->preeditpath ||
!dbus_message_has_signature(m, "vub") || !dbus_message_has_signature(m, "vub") ||
!dbus_message_iter_init(m, &it) || !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_next(&it) ||
dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_UINT32){ dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_UINT32){
log->invalid = 1; log->invalid = 1;
@@ -583,6 +642,38 @@ onsignal(DBusConnection *conn, DBusMessage *m, void *arg)
log->invalid = 1; log->invalid = 1;
return DBUS_HANDLER_RESULT_HANDLED; 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")){ if(dbus_message_has_member(m, "CommitText")){
log->ncommit++; log->ncommit++;
path = dbus_message_get_path(m); path = dbus_message_get_path(m);
@@ -591,7 +682,8 @@ onsignal(DBusConnection *conn, DBusMessage *m, void *arg)
(int)sizeof log->commitpath || (int)sizeof log->commitpath ||
!dbus_message_has_signature(m, "v") || !dbus_message_has_signature(m, "v") ||
!dbus_message_iter_init(m, &it) || !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)) dbus_message_iter_next(&it))
log->invalid = 1; log->invalid = 1;
return DBUS_HANDLER_RESULT_HANDLED; return DBUS_HANDLER_RESULT_HANDLED;
@@ -903,6 +995,43 @@ contentcall(DBusConnection *conn, Siglog *log, char *path,
return 1; 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 static int
keycall(DBusConnection *conn, Siglog *log, char *path, dbus_uint32_t sym, keycall(DBusConnection *conn, Siglog *log, char *path, dbus_uint32_t sym,
dbus_uint32_t state, int *eaten) dbus_uint32_t state, int *eaten)
@@ -1040,11 +1169,22 @@ nosignal(Siglog *log, char *where)
return 1; 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 static int
preedit(Siglog *log, char *path, char *text, unsigned int cursor, int visible, preedit(Siglog *log, char *path, char *text, unsigned int cursor, int visible,
char *where) 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->preeditpath, path) != 0 ||
strcmp(log->preedit, text) != 0 || strcmp(log->preedit, text) != 0 ||
log->cursor != cursor || (log->visible != FALSE) != (visible != 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; 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 static int
committed(Siglog *log, char *path, char *text, char *where) 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->preeditpath, path) != 0 ||
strcmp(log->commitpath, path) != 0 || strcmp(log->commitpath, path) != 0 ||
strcmp(log->commit, text) != 0 || strcmp(log->commit, text) != 0 ||
strcmp(log->preedit, "") != 0 || log->cursor != 0 || strcmp(log->preedit, "") != 0 || log->cursor != 0 ||
log->preeditattrs.n != 0 || log->commitattrs.n != 0 ||
log->visible != FALSE) log->visible != FALSE)
return fail("%s signals: preedit=%d commit=%d text=%s/%s", return fail("%s signals: preedit=%d commit=%d text=%s/%s",
where, log->npreedit, log->ncommit, log->commit, log->preedit); where, log->npreedit, log->ncommit, log->commit, log->preedit);
return 1; 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 static int
expectkey(DBusConnection *conn, Siglog *log, char *path, expectkey(DBusConnection *conn, Siglog *log, char *path,
dbus_uint32_t sym, dbus_uint32_t state, int eaten, char *text, char *where) 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") || !nosignal(&s1, "free-form property") ||
!expectkey(c1, &s1, a, 'n', 0, 1, "", "free-form resumes")) !expectkey(c1, &s1, a, 'n', 0, 1, "", "free-form resumes"))
goto out; 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; ok = 1;
out: out:
closebus(&c1, &s1); closebus(&c1, &s1);
@@ -1377,14 +1576,61 @@ out:
return ok; 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 int
main(int argc, char **argv) main(int argc, char **argv)
{ {
Daemon daemon; Daemon daemon;
int ok; int ok;
if(argc != 3){ if(argc != 4){
fprintf(stderr, "usage: ibus_live_test strans mapdir\n"); fprintf(stderr, "usage: ibus_live_test strans mapdir libibus-client\n");
return 2; return 2;
} }
ok = startdaemon(&daemon, argv[1], argv[2]); ok = startdaemon(&daemon, argv[1], argv[2]);
@@ -1394,6 +1640,8 @@ main(int argc, char **argv)
ok = runlifecycle(&daemon); ok = runlifecycle(&daemon);
if(ok) if(ok)
ok = runpolicy(&daemon); ok = runpolicy(&daemon);
if(ok)
ok = runclient(&daemon, argv[3]);
if(!ok) if(!ok)
showerrors(&daemon); showerrors(&daemon);
if(!stopdaemon(&daemon)) if(!stopdaemon(&daemon))