ibus: PostProcessKeyEvent for clients that process keys synchronously

GTK 4's IBus module, and GTK 3's with IBUS_ENABLE_SYNC_MODE=1, waits for
its ProcessKeyEvent reply and cannot take signals meanwhile; since IBus
1.5.29 it then reads the PostProcessKeyEvent property for what the key
produced.  strans answered that read with an error, so every key logged
a warning and a commit made by a key that passed on arrived after the
key: 한 and a comma became ,한.  A context that sets
EffectivePostProcessKeyEvent now has its commits and preedits held
during the key and handed over as the (yv) list IBus defines.
This commit is contained in:
2026-08-17 01:53:51 +09:00
parent 42568ab020
commit a03fb05324
2 changed files with 117 additions and 25 deletions

View File

@@ -178,7 +178,9 @@ doas make -C gtk uninstall
```
strans provides its own IBus endpoint; `ibus-daemon` and fcitx are not
required. Without `DISPLAY`, the daemon and IBus frontend still work, but XIM
required. IBus clients that process keys synchronously — GTK 4, or GTK 3
with `IBUS_ENABLE_SYNC_MODE=1` — get their commits and preedits through
`PostProcessKeyEvent`, in order with the key. Without `DISPLAY`, the daemon and IBus frontend still work, but XIM
is not started and the popup is disabled.
## Dictionary data

118
ibus.c
View File

@@ -21,6 +21,22 @@ enum
Ibusattrunderline = 1,
Ibusunderlinesingle = 1,
Ibuspreeditclear = 0,
Maxpost = 3,
};
/*
* A client in IBus's synchronous mode (GTK 4) cannot take signals while
* it waits for its ProcessKeyEvent reply: it asks afterwards, through the
* PostProcessKeyEvent property, for what the key produced, as typed
* (yv) pairs — 'c' a commit, 'u' or 'm' a preedit and then its
* "cursor,visible[,mode]".
*/
typedef struct Post Post;
struct Post
{
char type;
char text[Maxutf];
char pos[32];
};
typedef struct Ictx Ictx;
@@ -32,6 +48,10 @@ struct Ictx
u32int cap;
u32int purpose;
int clientcommitpreedit;
int postprocess;
int keying; /* inside ProcessKeyEvent: hold signals for the post */
int npost;
Post post[Maxpost];
Caret caret;
};
@@ -368,12 +388,30 @@ newsignal(const char *path, const char *name)
return sig;
}
static int
postpush(Ictx *ctx, char type, const char *text, const char *pos)
{
Post *p;
if(!ctx->postprocess || !ctx->keying)
return 0;
if(ctx->npost < Maxpost){
p = &ctx->post[ctx->npost++];
p->type = type;
strcpy(p->text, text);
strcpy(p->pos, pos);
}
return 1;
}
static void
emitcommit(Ictx *ctx, const char *text)
{
DBusMessage *sig;
DBusMessageIter it;
if(postpush(ctx, 'c', text, ""))
return;
sig = newsignal(ctx->path, "CommitText");
dbus_message_iter_init_append(sig, &it);
appendibustext(&it, text, 0);
@@ -389,29 +427,34 @@ emitpreedit(Ictx *ctx, const char *text)
DBusMessageIter it;
dbus_uint32_t cursor, mode;
dbus_bool_t visible;
char pos[32];
if(text[0] == '\0' && preowner != ctx)
return;
cursor = utflen((char*)text);
visible = text[0] != '\0' ? TRUE : FALSE;
mode = Ibuspreeditclear;
if(ctx->clientcommitpreedit)
snprintf(pos, sizeof pos, "%u,%u,%u", cursor, visible, mode);
else
snprintf(pos, sizeof pos, "%u,%u", cursor, visible);
if(!postpush(ctx, ctx->clientcommitpreedit ? 'm' : 'u', text, pos)){
sig = newsignal(ctx->path, ctx->clientcommitpreedit ?
"UpdatePreeditTextWithMode" : "UpdatePreeditText");
dbus_message_iter_init_append(sig, &it);
appendibustext(&it, text, 1);
cursor = utflen((char*)text);
visible = text[0] != '\0' ? TRUE : FALSE;
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &cursor);
dbus_message_iter_append_basic(&it, DBUS_TYPE_BOOLEAN, &visible);
if(ctx->clientcommitpreedit){
mode = Ibuspreeditclear;
if(ctx->clientcommitpreedit)
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &mode);
dbus_connection_send(ctx->conn, sig, nil);
dbus_message_unref(sig);
}
if(dbus_connection_send(ctx->conn, sig, nil)){
if(text[0] != '\0')
preowner = ctx;
else if(preowner == ctx)
preowner = nil;
}
dbus_message_unref(sig);
}
/* Another frontend may have taken the engine; then our preedit is stale. */
static void
@@ -600,15 +643,53 @@ handlekey(DBusConnection *c, DBusMessage *m, Ictx *ctx)
stoutf(&res.preedit, preedit, sizeof preedit);
/* A commit ends the preedit around it, so clients place it right. */
restart = clientpreedit(ctx) && commit[0] != '\0' && preowner == ctx;
ctx->keying = 1;
if(restart)
emitpreedit(ctx, "");
if(commit[0] != '\0')
emitcommit(ctx, commit);
if(clientpreedit(ctx) && (!restart || preedit[0] != '\0'))
emitpreedit(ctx, preedit);
ctx->keying = 0;
return replybool(c, m, res.eaten);
}
/* Properties.Get PostProcessKeyEvent: what the last key produced. */
static DBusHandlerResult
handlepost(DBusConnection *c, DBusMessage *m, Ictx *ctx)
{
DBusMessage *r;
DBusMessageIter it, v, a, st;
Post *p;
int i;
r = dbus_message_new_method_return(m);
if(r == nil)
die("ibus: out of memory");
dbus_message_iter_init_append(r, &it);
dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, "a(yv)", &v);
dbus_message_iter_open_container(&v, DBUS_TYPE_ARRAY, "(yv)", &a);
for(i = 0; i < ctx->npost; i++){
p = &ctx->post[i];
dbus_message_iter_open_container(&a, DBUS_TYPE_STRUCT, nil, &st);
dbus_message_iter_append_basic(&st, DBUS_TYPE_BYTE, &p->type);
appendibustext(&st, p->text, p->type != 'c');
dbus_message_iter_close_container(&a, &st);
if(p->type == 'c')
continue;
dbus_message_iter_open_container(&a, DBUS_TYPE_STRUCT, nil, &st);
dbus_message_iter_append_basic(&st, DBUS_TYPE_BYTE, &p->type);
appendibustext(&st, p->pos, 0);
dbus_message_iter_close_container(&a, &st);
}
dbus_message_iter_close_container(&v, &a);
dbus_message_iter_close_container(&it, &v);
dbus_connection_send(c, r, nil);
dbus_message_unref(r);
ctx->npost = 0;
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult
handlereset(DBusConnection *c, DBusMessage *m, Ictx *ctx)
{
@@ -699,7 +780,7 @@ getcontent(DBusMessageIter *value, u32int *purpose)
return 1;
}
/* Only ContentType and ClientCommitPreedit are settable; none is readable. */
/* ContentType, ClientCommitPreedit, and EffectivePostProcessKeyEvent are settable. */
static DBusHandlerResult
handlepropertyset(DBusConnection *c, DBusMessage *m, Ictx *ctx)
{
@@ -725,19 +806,23 @@ handlepropertyset(DBusConnection *c, DBusMessage *m, Ictx *ctx)
flushcontext(ctx, Keyreset);
return reply(c, m, DBUS_TYPE_INVALID, nil);
}
if(strcmp(name, "ClientCommitPreedit") == 0){
if(strcmp(name, "ClientCommitPreedit") == 0 ||
strcmp(name, "EffectivePostProcessKeyEvent") == 0){
if(dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_STRUCT)
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
"ClientCommitPreedit expects (b)");
"the property 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)");
"the property 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)");
"the property expects (b)");
if(name[0] == 'C')
ctx->clientcommitpreedit = b != FALSE;
else
ctx->postprocess = b != FALSE;
return reply(c, m, DBUS_TYPE_INVALID, nil);
}
return handleerror(c, m, DBUS_ERROR_UNKNOWN_PROPERTY,
@@ -848,9 +933,14 @@ onmsg(DBusConnection *c, DBusMessage *m, void*)
"unknown input context");
if(strcmp(member, "Set") == 0)
return handlepropertyset(c, m, ctx);
if(strcmp(member, "Get") == 0)
if(strcmp(member, "Get") == 0){
if(dbus_message_get_args(m, nil, DBUS_TYPE_STRING, &iface,
DBUS_TYPE_STRING, &member, DBUS_TYPE_INVALID) &&
strcmp(member, "PostProcessKeyEvent") == 0)
return handlepost(c, m, ctx);
return handleerror(c, m, DBUS_ERROR_UNKNOWN_PROPERTY,
"input context properties are write-only");
"unknown input context property");
}
if(strcmp(member, "GetAll") == 0)
return handlepropertygetall(c, m);
}