diff --git a/ibus.c b/ibus.c index 6c291af..f6452f3 100644 --- a/ibus.c +++ b/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}, }; diff --git a/tests/ibus_client_smoke.c b/tests/ibus_client_smoke.c index 673d013..7892fbc 100644 --- a/tests/ibus_client_smoke.c +++ b/tests/ibus_client_smoke.c @@ -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"); diff --git a/tests/ibus_live_test.c b/tests/ibus_live_test.c index 4209884..1359c11 100644 --- a/tests/ibus_live_test.c +++ b/tests/ibus_live_test.c @@ -129,6 +129,7 @@ runcontract(char *address) "SetCapabilities", "SetCursorLocation", "SetCursorLocationRelative", + "SetSurroundingText", }; DBusConnection *conn; char path[96];