ibus: the text before the cursor, and the runes to take back

RequireSurroundingText asks the client to send its text; it arrives
through SetSurroundingText as an IBusText and a cursor counted in
runes, and DeleteSurroundingText asks for runes back before the commit
that replaces them.  The official libibus client now proves the whole
turn: 한자 typed, the Hanja key, Enter, and 漢字 arrives with the 한
taken away.

A client that has set EffectivePostProcessKeyEvent is never asked for
its text.  It reads the key's commits back after ProcessKeyEvent
returns, and a DeleteSurroundingText signal is not one of the things
that reply can carry, so the deletion would land after the text it was
meant to make room for and eat the wrong runes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-18 14:31:44 +09:00
parent 910bf51347
commit 921e632eae
3 changed files with 179 additions and 7 deletions

92
ibus.c
View File

@@ -16,6 +16,7 @@ enum
Relmask = 1<<30,
/* IBus wire constants; the daemon does not link libibus. */
Ibuscappreedit = 1<<0,
Ibuscapsurround = 1<<5,
Ibusattrunderline = 1,
Ibusunderlinesingle = 1,
Ibuspreeditclear = 0,
@@ -51,6 +52,7 @@ struct Ictx
int npost;
Post post[Maxpost];
Caret caret;
Str surround; /* the client's own text just before the cursor */
};
static DBusWatch *watches[Maxwatches];
@@ -304,6 +306,11 @@ sendrequest(Ictx *ctx, int op, u32int ks, u32int mod, Keyres *res)
kr.ks = ks;
kr.mod = mod;
kr.caret = ctx->caret;
/* A client that reads the key's effects back afterwards takes a
* DeleteSurroundingText signal after them, which is too late to be
* a taking back: it is never asked for text it cannot lose. */
if(!ctx->postprocess)
kr.surround = ctx->surround;
kr.reply = replyc;
chansend(keyc, &kr);
chanrecv(replyc, res);
@@ -464,6 +471,34 @@ emitpreedit(Ictx *ctx, const char *text)
preowner = nil;
}
/* The client is to take back the runes the reading reached into. */
static void
emitdelete(Ictx *ctx, int n)
{
DBusMessage *sig;
dbus_int32_t off;
dbus_uint32_t nrune;
off = -n;
nrune = n;
sig = newsignal(ctx->path, "DeleteSurroundingText");
dbus_message_append_args(sig, DBUS_TYPE_INT32, &off,
DBUS_TYPE_UINT32, &nrune, DBUS_TYPE_INVALID);
dbus_connection_send(ctx->conn, sig, nil);
dbus_message_unref(sig);
}
/* Nothing arrives until the client is asked to send it. */
static void
emitrequiresurround(Ictx *ctx)
{
DBusMessage *sig;
sig = newsignal(ctx->path, "RequireSurroundingText");
dbus_connection_send(ctx->conn, sig, nil);
dbus_message_unref(sig);
}
/* Another frontend may have taken the engine; then our preedit is stale. */
static void
checkpreowner(void)
@@ -665,6 +700,8 @@ handlekey(DBusConnection *c, DBusMessage *m, Ictx *ctx)
ctx->keying = 1;
if(restart)
emitpreedit(ctx, "");
if(res.del > 0)
emitdelete(ctx, res.del);
if(commit[0] != '\0')
emitcommit(ctx, commit);
if(text[0] != '\0')
@@ -732,6 +769,7 @@ handlefocusout(DBusConnection *c, DBusMessage *m, Ictx *ctx)
flushcontext(ctx, Keyrelease);
ctx->focused = 0;
memset(&ctx->caret, 0, sizeof ctx->caret);
sclear(&ctx->surround);
return reply(c, m, DBUS_TYPE_INVALID, nil);
}
@@ -746,6 +784,8 @@ handlecap(DBusConnection *c, DBusMessage *m, Ictx *ctx)
dbus_message_get_args(m, nil, DBUS_TYPE_UINT32, &cap, DBUS_TYPE_INVALID);
old = ctx->cap;
ctx->cap = cap;
if((cap & ~old) & Ibuscapsurround)
emitrequiresurround(ctx);
if(ctx->focused){
sendrequest(ctx, Keycap, 0, 0, &res);
if((old & Ibuscappreedit) != (ctx->cap & Ibuscappreedit)){
@@ -759,6 +799,57 @@ handlecap(DBusConnection *c, DBusMessage *m, Ictx *ctx)
return reply(c, m, DBUS_TYPE_INVALID, nil);
}
/* IBusText is (sa{sv}sv); the string in it is all we want. */
static int
getibustext(DBusMessageIter *value, const char **s)
{
DBusMessageIter st;
int i;
if(dbus_message_iter_get_arg_type(value) != DBUS_TYPE_STRUCT)
return 0;
dbus_message_iter_recurse(value, &st);
for(i = 0; i < 2; i++)
if(!dbus_message_iter_next(&st))
return 0;
if(dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_STRING)
return 0;
dbus_message_iter_get_basic(&st, s);
return 1;
}
/* The bytes the first n runes of s take; IBus counts a cursor in runes. */
static int
runebytes(const char *s, int n)
{
Rune r;
char *p;
for(p = (char*)s; n > 0 && *p != '\0'; n--)
p += chartorune(&r, p);
return p - s;
}
/* The text around the cursor; a reading can use what comes before it. */
static DBusHandlerResult
handlesurround(DBusConnection *c, DBusMessage *m, Ictx *ctx)
{
DBusMessageIter it, v;
dbus_uint32_t cursor;
const char *s;
if(!dbus_message_iter_init(m, &it))
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
"SetSurroundingText expects text, cursor, and anchor");
dbus_message_iter_recurse(&it, &v);
if(!getibustext(&v, &s) || !dbus_message_iter_next(&it))
return handleerror(c, m, DBUS_ERROR_INVALID_ARGS,
"SetSurroundingText expects an IBusText");
dbus_message_iter_get_basic(&it, &cursor);
stail(&ctx->surround, (char*)s, runebytes(s, cursor));
return reply(c, m, DBUS_TYPE_INVALID, nil);
}
static int
getproperty(DBusMessage *m, const char **iface, const char **name,
DBusMessageIter *value)
@@ -913,6 +1004,7 @@ static struct {
{"SetCursorLocation", "iiii", handlecursor},
{"SetCursorLocationRelative", "iiii", handleok},
{"SetCapabilities", "u", handlecap},
{"SetSurroundingText", "vuu", handlesurround},
{"SetEngine", "s", handleok},
};