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

View File

@@ -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");

View File

@@ -129,6 +129,7 @@ runcontract(char *address)
"SetCapabilities",
"SetCursorLocation",
"SetCursorLocationRelative",
"SetSurroundingText",
};
DBusConnection *conn;
char path[96];