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:
@@ -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
|
||||
|
||||
138
ibus.c
138
ibus.c
@@ -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,28 +427,33 @@ 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;
|
||||
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;
|
||||
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &mode);
|
||||
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);
|
||||
dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &cursor);
|
||||
dbus_message_iter_append_basic(&it, DBUS_TYPE_BOOLEAN, &visible);
|
||||
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);
|
||||
if(text[0] != '\0')
|
||||
preowner = ctx;
|
||||
else if(preowner == ctx)
|
||||
preowner = nil;
|
||||
}
|
||||
|
||||
/* Another frontend may have taken the engine; then our preedit is stale. */
|
||||
@@ -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)");
|
||||
ctx->clientcommitpreedit = b != FALSE;
|
||||
"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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user