diff --git a/README.md b/README.md index 2269a77..d8d5c34 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/ibus.c b/ibus.c index 3f6dd13..2245d2e 100644 --- a/ibus.c +++ b/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); }