Clicking elsewhere, changing focus, or any client reset dropped the composition in the GTK module and IBus (only XIM ResetIC handed it back), so typing 안녕 and clicking Send lost 녕. The engine now returns the pending text — a moved-to candidate first, as Enter would — on Keyreset and Keyrelease; the GTK module asks for it before closing on focus-out and commits it, IBus commits it on FocusOut, Reset and a switch to a password field, and XIM commits it on focus loss and hands it back on ResetIC without a separate capability probe.
294 lines
7.4 KiB
C
294 lines
7.4 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
#include <ibus.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
typedef struct Log Log;
|
|
struct Log
|
|
{
|
|
GMainLoop *loop;
|
|
int *waiting;
|
|
IBusInputContext *a;
|
|
IBusInputContext *clearctx;
|
|
int legacy;
|
|
int modern;
|
|
int commit;
|
|
int invalid;
|
|
int sawpreedit;
|
|
int sawcommit;
|
|
int done;
|
|
int transfer;
|
|
int atext;
|
|
int aclear;
|
|
char revent[16];
|
|
int rn;
|
|
int repeat;
|
|
int rfirst;
|
|
int rcommit;
|
|
int rdone;
|
|
int flushed;
|
|
};
|
|
|
|
static void
|
|
revent(Log *log, char c)
|
|
{
|
|
if(log->rn + 1 < (int)sizeof log->revent){
|
|
log->revent[log->rn++] = c;
|
|
log->revent[log->rn] = '\0';
|
|
}
|
|
}
|
|
|
|
static void
|
|
legacy(IBusInputContext *ctx, IBusText *text, guint cursor,
|
|
gboolean visible, void *arg)
|
|
{
|
|
IBusAttrList *attrs;
|
|
IBusAttribute *a;
|
|
Log *log;
|
|
const char *s;
|
|
|
|
(void)ctx;
|
|
log = arg;
|
|
log->legacy++;
|
|
s = ibus_text_get_text(text);
|
|
if(log->repeat == 3){
|
|
if(s[0] != '\0' || cursor != 0 || visible)
|
|
log->invalid = 1;
|
|
return;
|
|
}
|
|
if(log->repeat != 0){
|
|
revent(log, 'P');
|
|
if(log->repeat == 1){
|
|
if(s[0] == '\0'){
|
|
if(cursor != 0 || visible)
|
|
log->invalid = 1;
|
|
}else if(strcmp(s, "ㅋ") != 0 || cursor != 1 || !visible)
|
|
log->invalid = 1;
|
|
else
|
|
log->rfirst = 1;
|
|
}else if(s[0] == '\0'){
|
|
if(cursor != 0 || visible || log->rcommit)
|
|
log->invalid = 1;
|
|
}else if(strcmp(s, "ㅋ") != 0 || cursor != 1 || !visible ||
|
|
!log->rcommit)
|
|
log->invalid = 1;
|
|
else
|
|
log->rdone = 1;
|
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
|
g_main_loop_quit(log->loop);
|
|
return;
|
|
}
|
|
if(strcmp(s, "k") == 0){
|
|
attrs = ibus_text_get_attributes(text);
|
|
a = attrs == NULL ? NULL : ibus_attr_list_get(attrs, 0);
|
|
if(cursor != 1 || !visible || attrs == NULL || a == NULL ||
|
|
a->type != IBUS_ATTR_TYPE_UNDERLINE ||
|
|
a->value != IBUS_ATTR_UNDERLINE_SINGLE ||
|
|
a->start_index != 0 || a->end_index != 1 ||
|
|
ibus_attr_list_get(attrs, 1) != NULL)
|
|
log->invalid = 1;
|
|
else{
|
|
log->sawpreedit = 1;
|
|
if(log->transfer && ctx == log->a)
|
|
log->atext = 1;
|
|
}
|
|
}
|
|
if(log->transfer && log->atext && !log->aclear && s[0] == '\0'){
|
|
if(cursor != 0 || visible)
|
|
log->invalid = 1;
|
|
else{
|
|
log->clearctx = ctx;
|
|
if(ctx == log->a)
|
|
log->aclear = 1;
|
|
else
|
|
log->invalid = 1;
|
|
}
|
|
}
|
|
log->done = log->sawpreedit && log->sawcommit;
|
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
|
g_main_loop_quit(log->loop);
|
|
}
|
|
|
|
static void
|
|
modern(IBusInputContext *ctx, IBusText *text, guint cursor,
|
|
gboolean visible, guint mode, void *arg)
|
|
{
|
|
Log *log;
|
|
|
|
(void)ctx;
|
|
(void)text;
|
|
(void)cursor;
|
|
(void)visible;
|
|
(void)mode;
|
|
log = arg;
|
|
log->modern++;
|
|
log->invalid = 1;
|
|
if(log->loop != NULL)
|
|
g_main_loop_quit(log->loop);
|
|
}
|
|
|
|
static void
|
|
commit(IBusInputContext *ctx, IBusText *text, void *arg)
|
|
{
|
|
IBusAttrList *attrs;
|
|
Log *log;
|
|
const char *s;
|
|
|
|
(void)ctx;
|
|
log = arg;
|
|
log->commit++;
|
|
s = ibus_text_get_text(text);
|
|
attrs = ibus_text_get_attributes(text);
|
|
if(log->repeat == 3){
|
|
/* Focus loss hands the pending syllable back as a commit. */
|
|
if(strcmp(s, "ㅋ") == 0)
|
|
log->flushed = 1;
|
|
else
|
|
log->invalid = 1;
|
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
|
g_main_loop_quit(log->loop);
|
|
return;
|
|
}
|
|
if(log->repeat != 0){
|
|
revent(log, 'K');
|
|
if(log->repeat != 2 || strcmp(s, "ㅋ") != 0 || attrs == NULL ||
|
|
ibus_attr_list_get(attrs, 0) != NULL)
|
|
log->invalid = 1;
|
|
else
|
|
log->rcommit = 1;
|
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
|
g_main_loop_quit(log->loop);
|
|
return;
|
|
}
|
|
if(strcmp(s, "か") != 0 || attrs == NULL ||
|
|
ibus_attr_list_get(attrs, 0) != NULL)
|
|
log->invalid = 1;
|
|
else
|
|
log->sawcommit = 1;
|
|
log->done = log->sawpreedit && log->sawcommit;
|
|
if(log->loop != NULL && log->waiting != NULL && *log->waiting)
|
|
g_main_loop_quit(log->loop);
|
|
}
|
|
|
|
static gboolean
|
|
timeout(void *arg)
|
|
{
|
|
g_main_loop_quit(arg);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static int
|
|
waitflag(Log *log, int *flag)
|
|
{
|
|
guint timer;
|
|
|
|
if(*flag)
|
|
return 1;
|
|
log->waiting = flag;
|
|
log->loop = g_main_loop_new(NULL, FALSE);
|
|
timer = g_timeout_add_seconds(4, timeout, log->loop);
|
|
g_main_loop_run(log->loop);
|
|
if(g_main_context_find_source_by_id(NULL, timer) != NULL)
|
|
g_source_remove(timer);
|
|
g_main_loop_unref(log->loop);
|
|
log->loop = NULL;
|
|
log->waiting = NULL;
|
|
return *flag;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
IBusBus *bus;
|
|
IBusInputContext *a, *b;
|
|
Log log;
|
|
int ok;
|
|
|
|
if(argc != 2){
|
|
fprintf(stderr, "usage: ibus_client_smoke address\n");
|
|
return 2;
|
|
}
|
|
if(setenv("IBUS_ADDRESS", argv[1], 1) < 0 ||
|
|
unsetenv("IBUS_ADDRESS_FILE") < 0 ||
|
|
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 || unsetenv("DISPLAY") < 0){
|
|
perror("ibus_client_smoke: environment");
|
|
return 1;
|
|
}
|
|
memset(&log, 0, sizeof log);
|
|
b = NULL;
|
|
ibus_init();
|
|
bus = ibus_bus_new();
|
|
if(bus == NULL || !ibus_bus_is_connected(bus)){
|
|
fprintf(stderr, "ibus_client_smoke: cannot connect to private bus\n");
|
|
if(bus != NULL) g_object_unref(bus);
|
|
return 1;
|
|
}
|
|
a = ibus_bus_create_input_context(bus, "strans-libibus-smoke-a");
|
|
if(a == NULL){
|
|
fprintf(stderr, "ibus_client_smoke: cannot create input context\n");
|
|
g_object_unref(bus);
|
|
return 1;
|
|
}
|
|
log.a = a;
|
|
g_signal_connect(a, "update-preedit-text", G_CALLBACK(legacy), &log);
|
|
g_signal_connect(a, "update-preedit-text-with-mode", G_CALLBACK(modern),
|
|
&log);
|
|
g_signal_connect(a, "commit-text", G_CALLBACK(commit), &log);
|
|
ibus_input_context_set_capabilities(a, IBUS_CAP_PREEDIT_TEXT);
|
|
ibus_input_context_focus_in(a);
|
|
ok = ibus_input_context_process_key_event(a, 'n', 0, IBUS_CONTROL_MASK) &&
|
|
ibus_input_context_process_key_event(a, 'k', 0, 0) &&
|
|
ibus_input_context_process_key_event(a, 'a', 0, 0) &&
|
|
ibus_input_context_process_key_event(a, '0', 0, 0) &&
|
|
waitflag(&log, &log.done);
|
|
b = ibus_bus_create_input_context(bus, "strans-libibus-smoke-b");
|
|
if(b == NULL)
|
|
ok = 0;
|
|
else{
|
|
g_signal_connect(b, "update-preedit-text", G_CALLBACK(legacy), &log);
|
|
g_signal_connect(b, "update-preedit-text-with-mode",
|
|
G_CALLBACK(modern), &log);
|
|
ibus_input_context_set_capabilities(b, IBUS_CAP_PREEDIT_TEXT);
|
|
log.transfer = 1;
|
|
ok = ok && ibus_input_context_process_key_event(a, 'k', 0, 0) &&
|
|
waitflag(&log, &log.atext);
|
|
ibus_input_context_focus_in(b);
|
|
ok = ok && ibus_input_context_process_key_event(b, 'n', 0, 0) &&
|
|
waitflag(&log, &log.aclear);
|
|
ibus_input_context_focus_out(b);
|
|
}
|
|
ibus_input_context_focus_in(a);
|
|
ok = ok && ibus_input_context_process_key_event(a, 's', 0,
|
|
IBUS_CONTROL_MASK);
|
|
log.repeat = 1;
|
|
ok = ok && ibus_input_context_process_key_event(a, 'z', 0, 0) &&
|
|
waitflag(&log, &log.rfirst);
|
|
log.repeat = 2;
|
|
log.revent[0] = '\0';
|
|
log.rn = 0;
|
|
ok = ok && ibus_input_context_process_key_event(a, 'z', 0, 0) &&
|
|
waitflag(&log, &log.rdone) && strcmp(log.revent, "PKP") == 0;
|
|
log.repeat = 3;
|
|
ibus_input_context_focus_out(a);
|
|
ok = ok && waitflag(&log, &log.flushed);
|
|
if(log.legacy == 0 || log.modern != 0 || log.commit != 3 ||
|
|
log.invalid || !log.done || !log.atext || !log.aclear ||
|
|
log.clearctx != a || !log.rdone)
|
|
ok = 0;
|
|
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 repeat=%s flushed=%d\n",
|
|
log.legacy, log.modern, log.commit, log.invalid,
|
|
log.sawpreedit, log.sawcommit, log.atext, log.aclear,
|
|
log.revent, log.flushed);
|
|
return 1;
|
|
}
|
|
printf("official libibus client preedit, commit, owner clear, and focus-out hand-back: ok\n");
|
|
return 0;
|
|
}
|