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:
92
ibus.c
92
ibus.c
@@ -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},
|
||||
};
|
||||
|
||||
|
||||
@@ -30,6 +30,12 @@ struct Log
|
||||
int rcommit;
|
||||
int rdone;
|
||||
int flushed;
|
||||
int hanja;
|
||||
int required;
|
||||
int deleted;
|
||||
int delbefore;
|
||||
int delcount;
|
||||
int converted;
|
||||
};
|
||||
|
||||
static void
|
||||
@@ -142,6 +148,17 @@ commit(IBusInputContext *ctx, IBusText *text, void *arg)
|
||||
log->commit++;
|
||||
s = ibus_text_get_text(text);
|
||||
attrs = ibus_text_get_attributes(text);
|
||||
if(log->hanja){
|
||||
/* The word converted, with the client\'s own syllable taken back. */
|
||||
if(strcmp(s, "漢字") == 0 && log->deleted && log->delbefore == -1 &&
|
||||
log->delcount == 1)
|
||||
log->converted = 1;
|
||||
else
|
||||
log->invalid = 1;
|
||||
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||
g_main_loop_quit(log->loop);
|
||||
return;
|
||||
}
|
||||
if(log->repeat == 3){
|
||||
/* Focus loss hands the pending syllable back as a commit. */
|
||||
if(strcmp(s, "ㅋ") == 0)
|
||||
@@ -184,6 +201,32 @@ commit(IBusInputContext *ctx, IBusText *text, void *arg)
|
||||
g_main_loop_quit(log->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
required(IBusInputContext *ctx, void *arg)
|
||||
{
|
||||
Log *log;
|
||||
|
||||
(void)ctx;
|
||||
log = arg;
|
||||
log->required = 1;
|
||||
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||
g_main_loop_quit(log->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
deleted(IBusInputContext *ctx, gint offset, guint n, void *arg)
|
||||
{
|
||||
Log *log;
|
||||
|
||||
(void)ctx;
|
||||
log = arg;
|
||||
log->deleted = 1;
|
||||
log->delbefore = offset;
|
||||
log->delcount = n;
|
||||
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
||||
g_main_loop_quit(log->loop);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
timeout(void *arg)
|
||||
{
|
||||
@@ -214,7 +257,8 @@ int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
IBusBus *bus;
|
||||
IBusInputContext *a, *b;
|
||||
IBusInputContext *a, *b, *c;
|
||||
IBusText *around;
|
||||
Log log;
|
||||
int ok;
|
||||
|
||||
@@ -229,7 +273,7 @@ main(int argc, char **argv)
|
||||
return 1;
|
||||
}
|
||||
memset(&log, 0, sizeof log);
|
||||
b = NULL;
|
||||
b = c = NULL;
|
||||
ibus_init();
|
||||
bus = ibus_bus_new();
|
||||
if(bus == NULL || !ibus_bus_is_connected(bus)){
|
||||
@@ -286,20 +330,55 @@ main(int argc, char **argv)
|
||||
log.repeat = 3;
|
||||
ibus_input_context_focus_out(a);
|
||||
ok = ok && waitflag(&log, &log.flushed);
|
||||
if(log.legacy == 0 || log.modern != 0 || log.commit != 4 ||
|
||||
log.invalid || !log.done || !log.atext || !log.aclear ||
|
||||
!log.retook || log.clearctx != a || !log.rdone)
|
||||
/*
|
||||
* A word is committed a syllable at a time, so the Hanja key must
|
||||
* reach into what the client already holds and ask for it back.
|
||||
*/
|
||||
c = ibus_bus_create_input_context(bus, "strans-libibus-smoke-c");
|
||||
if(c == NULL)
|
||||
ok = 0;
|
||||
else{
|
||||
g_signal_connect(c, "commit-text", G_CALLBACK(commit), &log);
|
||||
g_signal_connect(c, "require-surrounding-text",
|
||||
G_CALLBACK(required), &log);
|
||||
g_signal_connect(c, "delete-surrounding-text",
|
||||
G_CALLBACK(deleted), &log);
|
||||
log.repeat = 0;
|
||||
ibus_input_context_set_capabilities(c,
|
||||
IBUS_CAP_PREEDIT_TEXT|IBUS_CAP_SURROUNDING_TEXT);
|
||||
ibus_input_context_focus_in(c);
|
||||
ok = ok && waitflag(&log, &log.required);
|
||||
around = ibus_text_new_from_string("저는 한");
|
||||
ibus_input_context_set_surrounding_text(c, around, 4, 4);
|
||||
log.hanja = 1;
|
||||
ok = ok && ibus_input_context_process_key_event(c, 's', 0,
|
||||
IBUS_CONTROL_MASK) &&
|
||||
ibus_input_context_process_key_event(c, 'w', 0, 0) &&
|
||||
ibus_input_context_process_key_event(c, 'k', 0, 0) &&
|
||||
ibus_input_context_process_key_event(c, 'h', 0,
|
||||
IBUS_CONTROL_MASK) &&
|
||||
ibus_input_context_process_key_event(c, IBUS_KEY_Return,
|
||||
0, 0) && waitflag(&log, &log.converted);
|
||||
log.hanja = 0;
|
||||
}
|
||||
|
||||
if(log.legacy == 0 || log.modern != 0 || log.commit != 5 ||
|
||||
log.invalid || !log.done || !log.atext || !log.aclear ||
|
||||
!log.retook || log.clearctx != a || !log.rdone || !log.converted)
|
||||
ok = 0;
|
||||
if(c != NULL)
|
||||
g_object_unref(c);
|
||||
if(b != NULL)
|
||||
g_object_unref(b);
|
||||
g_object_unref(a);
|
||||
g_object_unref(bus);
|
||||
if(!ok){
|
||||
fprintf(stderr,
|
||||
"ibus_client_smoke: legacy=%d modern=%d commit=%d invalid=%d preedit=%d committed=%d transfer-text=%d a-clear=%d retook=%d repeat=%s flushed=%d\n",
|
||||
"ibus_client_smoke: legacy=%d modern=%d commit=%d invalid=%d preedit=%d committed=%d transfer-text=%d a-clear=%d retook=%d repeat=%s flushed=%d required=%d deleted=%d,%d converted=%d\n",
|
||||
log.legacy, log.modern, log.commit, log.invalid,
|
||||
log.sawpreedit, log.sawcommit, log.atext, log.aclear,
|
||||
log.retook, log.revent, log.flushed);
|
||||
log.retook, log.revent, log.flushed, log.required,
|
||||
log.delbefore, log.delcount, log.converted);
|
||||
return 1;
|
||||
}
|
||||
printf("official libibus client preedit, commit, owner clear, take-back and focus-out hand-back: ok\n");
|
||||
|
||||
@@ -129,6 +129,7 @@ runcontract(char *address)
|
||||
"SetCapabilities",
|
||||
"SetCursorLocation",
|
||||
"SetCursorLocationRelative",
|
||||
"SetSurroundingText",
|
||||
};
|
||||
DBusConnection *conn;
|
||||
char path[96];
|
||||
|
||||
Reference in New Issue
Block a user