diff --git a/tests/test.h b/tests/test.h index 04ead8a..5411f5a 100644 --- a/tests/test.h +++ b/tests/test.h @@ -79,3 +79,10 @@ void ibus_active_release_lifecycle(struct ct*); void xim_adapter_key_contract(struct ct*); void xim_adapter_release_lifecycle(struct ct*); void xim_adapter_free_waits_for_release(struct ct*); +void xim_adapter_styles(struct ct*); +void xim_adapter_encoding_negotiation(struct ct*); +void xim_adapter_callback_replacement(struct ct*); +void xim_adapter_callback_unicode(struct ct*); +void xim_adapter_callback_cleanup(struct ct*); +void xim_adapter_callback_transfer(struct ct*); +void xim_adapter_commit_encoding(struct ct*); diff --git a/tests/unit_test.c b/tests/unit_test.c index 4201d0a..a3d77b0 100644 --- a/tests/unit_test.c +++ b/tests/unit_test.c @@ -133,6 +133,13 @@ static const struct ct_test tests[] = { { "xim/adapter-key-contract", xim_adapter_key_contract }, { "xim/adapter-release-lifecycle", xim_adapter_release_lifecycle }, { "xim/adapter-free-waits-release", xim_adapter_free_waits_for_release }, + { "xim/styles", xim_adapter_styles }, + { "xim/encoding-negotiation", xim_adapter_encoding_negotiation }, + { "xim/callback-replacement", xim_adapter_callback_replacement }, + { "xim/callback-unicode", xim_adapter_callback_unicode }, + { "xim/callback-cleanup", xim_adapter_callback_cleanup }, + { "xim/callback-transfer", xim_adapter_callback_transfer }, + { "xim/commit-encoding", xim_adapter_commit_encoding }, }; void diff --git a/tests/xim_adapter_test.c b/tests/xim_adapter_test.c index 7dc8e32..52647cc 100644 --- a/tests/xim_adapter_test.c +++ b/tests/xim_adapter_test.c @@ -1,6 +1,174 @@ +#include +#include + +static void wirecommit(xcb_im_t*, xcb_im_input_context_t*, uint32_t, + const char*, uint32_t, uint32_t); +static void wirestart(xcb_im_t*, xcb_im_input_context_t*); +static void wiredraw(xcb_im_t*, xcb_im_input_context_t*, + xcb_im_preedit_draw_fr_t*); +static void wiredone(xcb_im_t*, xcb_im_input_context_t*); +static void* wiregetdata(xcb_im_input_context_t*); +static int wireflush(xcb_connection_t*); + +#define xcb_im_commit_string wirecommit +#define xcb_im_preedit_start_callback wirestart +#define xcb_im_preedit_draw_callback wiredraw +#define xcb_im_preedit_done_callback wiredone +#define xcb_im_input_context_get_data wiregetdata +#define xcb_flush wireflush #include "xim/xim.c" +#undef xcb_im_commit_string +#undef xcb_im_preedit_start_callback +#undef xcb_im_preedit_draw_callback +#undef xcb_im_preedit_done_callback +#undef xcb_im_input_context_get_data +#undef xcb_flush #include "cutest/cutest.h" +enum +{ + Wstart, + Wdraw, + Wdone, + Wcommit, + Maxwire = 64, +}; + +typedef struct Wirecall Wirecall; +struct Wirecall +{ + int op; + xcb_im_input_context_t *ic; + u32int caret; + u32int first; + u32int changed; + u32int status; + u32int flag; + u32int keysym; + int nbyte; + int nfeedback; + uchar text[Maxutf]; + u32int feedback[Maxrunes]; +}; + +typedef struct Icbinding Icbinding; +struct Icbinding +{ + xcb_im_input_context_t *ic; + void *data; +}; + +static Wirecall wirecalls[Maxwire]; +static Icbinding icbindings[8]; +static int nwirecalls; +static int nflush; + +static Wirecall* +newwire(int op, xcb_im_input_context_t *ic) +{ + Wirecall *call; + + if(nwirecalls >= nelem(wirecalls)) + return nil; + call = &wirecalls[nwirecalls++]; + memset(call, 0, sizeof *call); + call->op = op; + call->ic = ic; + return call; +} + +static void +wirecommit(xcb_im_t *im, xcb_im_input_context_t *ic, uint32_t flag, + const char *text, uint32_t nbyte, uint32_t keysym) +{ + Wirecall *call; + + USED(im); + call = newwire(Wcommit, ic); + if(call == nil) + return; + call->flag = flag; + call->keysym = keysym; + call->nbyte = min(nbyte, sizeof call->text); + if(call->nbyte > 0) + memmove(call->text, text, call->nbyte); +} + +static void +wirestart(xcb_im_t *im, xcb_im_input_context_t *ic) +{ + USED(im); + newwire(Wstart, ic); +} + +static void +wiredraw(xcb_im_t *im, xcb_im_input_context_t *ic, + xcb_im_preedit_draw_fr_t *frame) +{ + Wirecall *call; + + USED(im); + call = newwire(Wdraw, ic); + if(call == nil) + return; + call->caret = frame->caret; + call->first = frame->chg_first; + call->changed = frame->chg_length; + call->status = frame->status; + call->nbyte = min(frame->length_of_preedit_string, + sizeof call->text); + if(call->nbyte > 0) + memmove(call->text, frame->preedit_string, call->nbyte); + call->nfeedback = min(frame->feedback_array.size, + nelem(call->feedback)); + if(call->nfeedback > 0) + memmove(call->feedback, frame->feedback_array.items, + call->nfeedback * sizeof call->feedback[0]); +} + +static void +wiredone(xcb_im_t *im, xcb_im_input_context_t *ic) +{ + USED(im); + newwire(Wdone, ic); +} + +static void* +wiregetdata(xcb_im_input_context_t *ic) +{ + int i; + + for(i = 0; i < nelem(icbindings); i++) + if(icbindings[i].ic == ic) + return icbindings[i].data; + return nil; +} + +static int +wireflush(xcb_connection_t *c) +{ + USED(c); + nflush++; + return 1; +} + +static void +wireclear(void) +{ + memset(wirecalls, 0, sizeof wirecalls); + memset(icbindings, 0, sizeof icbindings); + nwirecalls = 0; + nflush = 0; + preowner = nil; +} + +static void +wirebind(int n, xcb_im_input_context_t *ic, Ic *state) +{ + icbindings[n].ic = ic; + icbindings[n].data = state; +} + void testengineinit(int); void testenginehandle(Keyreq*); void* testengineowner(void); @@ -112,6 +280,7 @@ nexttrace(struct ct *t, Ximfix *f, int op, Ic *state) return req; CT_EQ_INT(t, op, req.op); CT_EQ_PTR(t, state, req.owner); + CT_EQ_INT(t, state->cap, req.cap); CT_EQ_PTR(t, replyc, req.reply); return req; } @@ -304,3 +473,308 @@ cleanup: chanfree(freed); ximend(&f); } + +void +xim_adapter_styles(struct ct *t) +{ + static u32int want[] = { + XCB_IM_PreeditPosition | XCB_IM_StatusNothing, + XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing, + XCB_IM_PreeditNothing | XCB_IM_StatusNothing, + }; + Ic state; + int cap, i; + + CT_EQ_SIZE(t, nelem(want), nelem(styles)); + for(i = 0; i < nelem(want); i++){ + CT_EQ_UINT(t, want[i], styles[i]); + CT_CHECK(t, stylecap(want[i], &cap)); + CT_EQ_INT(t, i == 1 ? Cclientpreedit : 0, cap); + CT_CHECK(t, !(styles[i] & XCB_IM_PreeditNone)); + } + CT_CHECK(t, !stylecap(XCB_IM_PreeditNone | XCB_IM_StatusNothing, + &cap)); + CT_CHECK(t, !stylecap(XCB_IM_PreeditCallbacks | XCB_IM_StatusCallbacks, + &cap)); + CT_EQ_INT(t, 0, cap); + memset(&state, 0, sizeof state); + setstyle(&state, XCB_IM_PreeditNone | XCB_IM_StatusNothing); + CT_EQ_UINT(t, XCB_IM_PreeditNothing | XCB_IM_StatusNothing, + state.style); + CT_EQ_INT(t, 0, state.cap); + setstyle(&state, XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing); + CT_EQ_INT(t, Cclientpreedit, state.cap); +} + +void +xim_adapter_encoding_negotiation(struct ct *t) +{ + xcb_im_packet_header_fr_t hdr; + xcb_im_client_t *a, *b; + u16int selected; + + a = (xcb_im_client_t*)(uintptr)1; + b = (xcb_im_client_t*)(uintptr)2; + memset(clientenc, 0, sizeof clientenc); + CT_EQ_SIZE(t, 2, nelem(encs)); + CT_EQ_STR(t, "COMPOUND_TEXT", encs[0]); + CT_EQ_STR(t, "UTF8_STRING", encs[1]); + memset(&hdr, 0, sizeof hdr); + hdr.major_opcode = XCB_XIM_ENCODING_NEGOTIATION; + selected = 1; + callback(nil, a, nil, &hdr, nil, &selected, nil); + selected = 0; + callback(nil, b, nil, &hdr, nil, &selected, nil); + CT_EQ_INT(t, Eutf8, getencoding(a)); + CT_EQ_INT(t, Ecompound, getencoding(b)); + hdr.major_opcode = XCB_XIM_DISCONNECT; + callback(nil, a, nil, &hdr, nil, nil, nil); + CT_EQ_INT(t, Ecompound, getencoding(a)); + CT_EQ_INT(t, Ecompound, getencoding(b)); + hdr.major_opcode = XCB_XIM_CLOSE; + callback(nil, b, nil, &hdr, nil, nil, nil); + CT_EQ_PTR(t, nil, clientenc[0].client); + CT_EQ_PTR(t, nil, clientenc[1].client); +} + +void +xim_adapter_callback_replacement(struct ct *t) +{ + Ic state; + Str pre; + xcb_im_input_context_t *ic; + int i; + + ic = (xcb_im_input_context_t*)(uintptr)3; + memset(&state, 0, sizeof state); + state.xic = ic; + state.style = XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing; + state.encoding = Eutf8; + wireclear(); + sinit(&pre, "k", strlen("k")); + updatepreedit(&state, &pre); + sinit(&pre, "ν•œπŸ˜€", strlen("ν•œπŸ˜€")); + updatepreedit(&state, &pre); + sclear(&pre); + updatepreedit(&state, &pre); + CT_EQ_INT(t, 5, nwirecalls); + CT_EQ_INT(t, Wstart, wirecalls[0].op); + CT_EQ_INT(t, Wdraw, wirecalls[1].op); + CT_EQ_UINT(t, 0, wirecalls[1].first); + CT_EQ_UINT(t, 0, wirecalls[1].changed); + CT_EQ_UINT(t, 1, wirecalls[1].caret); + CT_EQ_INT(t, 1, wirecalls[1].nfeedback); + CT_EQ_INT(t, Wdraw, wirecalls[2].op); + CT_EQ_UINT(t, 0, wirecalls[2].first); + CT_EQ_UINT(t, 1, wirecalls[2].changed); + CT_EQ_UINT(t, 2, wirecalls[2].caret); + CT_EQ_INT(t, strlen("ν•œπŸ˜€"), wirecalls[2].nbyte); + CT_EQ_MEM(t, "ν•œπŸ˜€", wirecalls[2].text, wirecalls[2].nbyte); + CT_EQ_INT(t, 2, wirecalls[2].nfeedback); + for(i = 0; i < wirecalls[2].nfeedback; i++) + CT_EQ_UINT(t, XCB_XIM_UNDERLINE, wirecalls[2].feedback[i]); + CT_EQ_INT(t, Wdraw, wirecalls[3].op); + CT_EQ_UINT(t, 2, wirecalls[3].changed); + CT_EQ_UINT(t, 1, wirecalls[3].status); + CT_EQ_INT(t, 0, wirecalls[3].nbyte); + CT_EQ_INT(t, Wdone, wirecalls[4].op); + CT_CHECK(t, !state.prestarted); + CT_EQ_INT(t, 0, state.nprerune); + CT_EQ_PTR(t, nil, preowner); + CT_EQ_INT(t, 1, nflush); +} + +void +xim_adapter_callback_unicode(struct ct *t) +{ + static char *text[] = { + "ν•œκΈ€", + "かγͺγ‚«γƒŠ", + "ζΌ’ε­—", + "𠀋", + "πŸ˜€", + "❀️", + "πŸ‘©β€πŸ’»", + }; + Ic state; + Str pre; + Wirecall *call; + char *want; + size_t nwant; + int i; + + xcb_compound_text_init(); + for(i = 0; i < nelem(text); i++){ + memset(&state, 0, sizeof state); + state.xic = (xcb_im_input_context_t*)(uintptr)(10+i); + state.style = XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing; + state.encoding = Eutf8; + sinit(&pre, text[i], strlen(text[i])); + wireclear(); + updatepreedit(&state, &pre); + CT_EQ_INT(t, 2, nwirecalls); + call = &wirecalls[1]; + CT_EQ_INT(t, Wdraw, call->op); + CT_EQ_UINT(t, pre.n, call->caret); + CT_EQ_INT(t, pre.n, call->nfeedback); + CT_EQ_INT(t, strlen(text[i]), call->nbyte); + CT_EQ_MEM(t, text[i], call->text, call->nbyte); + + memset(&state, 0, sizeof state); + state.xic = (xcb_im_input_context_t*)(uintptr)(20+i); + state.style = XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing; + state.encoding = Ecompound; + wireclear(); + updatepreedit(&state, &pre); + want = ximcompound(text[i], strlen(text[i]), &nwant); + if(!CT_CHECK(t, want != nil)) + continue; + call = &wirecalls[1]; + CT_EQ_SIZE(t, nwant, call->nbyte); + CT_EQ_MEM(t, want, call->text, nwant); + CT_EQ_UINT(t, pre.n, call->caret); + CT_EQ_INT(t, pre.n, call->nfeedback); + free(want); + } +} + +static void +startstate(Ic *state, xcb_im_input_context_t *ic) +{ + Str pre; + + memset(state, 0, sizeof *state); + state->xic = ic; + state->style = XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing; + state->encoding = Eutf8; + sinit(&pre, "あ", strlen("あ")); + updatepreedit(state, &pre); +} + +void +xim_adapter_callback_cleanup(struct ct *t) +{ + static int opcode[] = { + XCB_XIM_RESET_IC, + XCB_XIM_UNSET_IC_FOCUS, + XCB_XIM_DESTROY_IC, + }; + xcb_im_packet_header_fr_t hdr; + xcb_im_client_t *client; + xcb_im_input_context_t *ic; + Ic state, *dead; + int i; + + for(i = 0; i < nelem(opcode); i++){ + ic = (xcb_im_input_context_t*)(uintptr)(30+i); + wireclear(); + wirebind(0, ic, &state); + startstate(&state, ic); + memset(&hdr, 0, sizeof hdr); + hdr.major_opcode = opcode[i]; + callback(nil, nil, ic, &hdr, nil, nil, nil); + CT_EQ_INT(t, 4, nwirecalls); + CT_EQ_INT(t, Wdraw, wirecalls[2].op); + CT_EQ_UINT(t, 1, wirecalls[2].changed); + CT_EQ_INT(t, Wdone, wirecalls[3].op); + CT_CHECK(t, !state.prestarted); + CT_EQ_PTR(t, nil, preowner); + } + client = (xcb_im_client_t*)(uintptr)40; + ic = (xcb_im_input_context_t*)(uintptr)40; + wireclear(); + startstate(&state, ic); + state.client = client; + state.next = ics; + ics = &state; + hdr.major_opcode = XCB_XIM_DISCONNECT; + callback(nil, client, nil, &hdr, nil, nil, nil); + ics = state.next; + CT_EQ_INT(t, Wdraw, wirecalls[2].op); + CT_EQ_INT(t, Wdone, wirecalls[3].op); + CT_CHECK(t, !state.prestarted); + + ic = (xcb_im_input_context_t*)(uintptr)41; + wireclear(); + dead = calloc(1, sizeof *dead); + if(!CT_CHECK(t, dead != nil)) + return; + dead->next = ics; + ics = dead; + startstate(dead, ic); + icfree(dead); + CT_EQ_INT(t, 4, nwirecalls); + CT_EQ_INT(t, Wdraw, wirecalls[2].op); + CT_EQ_INT(t, Wdone, wirecalls[3].op); + CT_EQ_PTR(t, nil, preowner); +} + +void +xim_adapter_callback_transfer(struct ct *t) +{ + Ximfix f; + Ic a, b; + xcb_im_input_context_t *aic; + Keyres res; + Str pre; + + aic = (xcb_im_input_context_t*)(uintptr)50; + memset(&a, 0, sizeof a); + memset(&b, 0, sizeof b); + if(!ximbegin(t, &f, LangJP)) + goto cleanup; + wireclear(); + a.xic = aic; + a.style = XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing; + a.encoding = Eutf8; + sinit(&pre, "あ", strlen("あ")); + updatepreedit(&a, &pre); + keypress(&b, 'x', 0, &res); + nexttrace(t, &f, Keypress, &b); + CT_EQ_INT(t, 4, nwirecalls); + CT_EQ_INT(t, Wdraw, wirecalls[2].op); + CT_EQ_INT(t, Wdone, wirecalls[3].op); + CT_CHECK(t, !a.prestarted); + CT_EQ_PTR(t, nil, preowner); + release(&b); + nexttrace(t, &f, Keyrelease, &b); +cleanup: + preowner = nil; + ximend(&f); +} + +void +xim_adapter_commit_encoding(struct ct *t) +{ + static char text[] = "ν•œπ €‹β€οΈπŸ‘©β€πŸ’»"; + Ic state; + xcb_im_input_context_t *ic; + char *want; + size_t nwant; + + ic = (xcb_im_input_context_t*)(uintptr)60; + memset(&state, 0, sizeof state); + state.xic = ic; + state.encoding = Eutf8; + wireclear(); + commit(&state, text, strlen(text)); + CT_EQ_INT(t, 1, nwirecalls); + CT_EQ_INT(t, Wcommit, wirecalls[0].op); + CT_EQ_UINT(t, XCB_XIM_LOOKUP_CHARS, wirecalls[0].flag); + CT_EQ_UINT(t, 0, wirecalls[0].keysym); + CT_EQ_INT(t, strlen(text), wirecalls[0].nbyte); + CT_EQ_MEM(t, text, wirecalls[0].text, wirecalls[0].nbyte); + + xcb_compound_text_init(); + state.encoding = Ecompound; + wireclear(); + commit(&state, text, strlen(text)); + want = ximcompound(text, strlen(text), &nwant); + if(!CT_CHECK(t, want != nil)) + return; + CT_EQ_INT(t, 1, nwirecalls); + CT_EQ_INT(t, Wcommit, wirecalls[0].op); + CT_EQ_SIZE(t, nwant, wirecalls[0].nbyte); + CT_EQ_MEM(t, want, wirecalls[0].text, nwant); + free(want); +} diff --git a/tests/xim_test.c b/tests/xim_test.c index e51b796..a1464ce 100644 --- a/tests/xim_test.c +++ b/tests/xim_test.c @@ -48,6 +48,24 @@ checkutf8run(void) free(ct); } +static void +checkcorpus(void) +{ + static const char *text[] = { + "ν•œκΈ€", + "かγͺγ‚«γƒŠ", + "ζΌ’ε­—", + "𠀋", + "πŸ˜€", + "❀️", + "πŸ‘©β€πŸ’»", + }; + size_t i; + + for(i = 0; i < sizeof text / sizeof text[0]; i++) + checktext(text[i]); +} + int main(void) { @@ -73,9 +91,7 @@ main(void) assert(keymaplookup(punctuation, 2, LockMask|ShiftMask) == XKB_KEY_exclam); assert(keymaplookup(missing, 4, Group1) == XKB_KEY_a); assert(keymaplookup(NULL, 0, 0) == XKB_KEY_NoSymbol); - checktext("ν•œκΈ€"); - checktext("πŸ˜€"); - checktext("❀️"); + checkcorpus(); checktext("AπŸ˜€ν•œ"); checkutf8run(); return 0; diff --git a/xim/xim.c b/xim/xim.c index d8b4f55..3890aaa 100644 --- a/xim/xim.c +++ b/xim/xim.c @@ -12,8 +12,28 @@ char *ximcompound(const char*, size_t, size_t*); typedef struct Ic Ic; struct Ic { - Ic *next; - int engaged; + Ic *next; + xcb_im_input_context_t *xic; + xcb_im_client_t *client; + int engaged; + u32int style; + int cap; + int encoding; + int prestarted; + int nprerune; +}; + +typedef struct Clientenc Clientenc; +struct Clientenc +{ + xcb_im_client_t *client; + int encoding; +}; + +enum +{ + Ecompound, + Eutf8, }; static xcb_connection_t *conn; @@ -23,13 +43,76 @@ static uint8_t minkc, maxkc; static uint8_t symsper; static int opened; static Ic *ics; +static Ic *preowner; +static Clientenc clientenc[Maxclients]; static Channel *replyc; -static char *encs[] = {"COMPOUND_TEXT"}; +static char *encs[] = {"COMPOUND_TEXT", "UTF8_STRING"}; static u32int styles[] = { + XCB_IM_PreeditPosition | XCB_IM_StatusNothing, + XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing, XCB_IM_PreeditNothing | XCB_IM_StatusNothing, - XCB_IM_PreeditNone | XCB_IM_StatusNone, }; +static int stylecaps[] = {0, Cclientpreedit, 0}; + +static int +stylecap(u32int style, int *cap) +{ + int i; + + for(i = 0; i < nelem(styles); i++) + if(styles[i] == style){ + *cap = stylecaps[i]; + return 1; + } + *cap = 0; + return 0; +} + +static int +getencoding(xcb_im_client_t *client) +{ + int i; + + for(i = 0; i < nelem(clientenc); i++) + if(clientenc[i].client == client) + return clientenc[i].encoding; + return Ecompound; +} + +static void +setencoding(xcb_im_client_t *client, int encoding) +{ + int empty, i; + + if(client == nil) + return; + empty = -1; + for(i = 0; i < nelem(clientenc); i++){ + if(clientenc[i].client == client){ + clientenc[i].encoding = encoding; + return; + } + if(empty < 0 && clientenc[i].client == nil) + empty = i; + } + if(empty >= 0){ + clientenc[empty].client = client; + clientenc[empty].encoding = encoding; + } +} + +static void +clearencoding(xcb_im_client_t *client) +{ + int i; + + for(i = 0; i < nelem(clientenc); i++) + if(clientenc[i].client == client){ + memset(&clientenc[i], 0, sizeof clientenc[i]); + return; + } +} static void ximlog(char *msg) @@ -99,19 +182,86 @@ getscreen(int scr) } static void -commit(xcb_im_input_context_t *ic, char *s, int len) +commit(Ic *state, char *s, int nbyte) { - char *ct; - size_t clen; + char *wire; + size_t nwire; - if(len == 0) + if(nbyte == 0) return; - ct = ximcompound(s, len, &clen); - if(ct == nil) + wire = s; + nwire = nbyte; + if(state->encoding == Ecompound){ + wire = ximcompound(s, nbyte, &nwire); + if(wire == nil) + return; + } + xcb_im_commit_string(xim, state->xic, XCB_XIM_LOOKUP_CHARS, + wire, (u32int)nwire, 0); + if(wire != s) + free(wire); +} + +static void +clearpreedit(Ic *state) +{ + xcb_im_preedit_draw_fr_t frame; + + if(state == nil || !state->prestarted) return; - xcb_im_commit_string(xim, ic, XCB_XIM_LOOKUP_CHARS, ct, clen, 0); + memset(&frame, 0, sizeof frame); + frame.chg_length = state->nprerune; + frame.status = 1; + xcb_im_preedit_draw_callback(xim, state->xic, &frame); + xcb_im_preedit_done_callback(xim, state->xic); + state->prestarted = 0; + state->nprerune = 0; + if(preowner == state) + preowner = nil; xcb_flush(conn); - free(ct); +} + +static void +updatepreedit(Ic *state, Str *pre) +{ + xcb_im_preedit_draw_fr_t frame; + u32int feedback[Maxrunes]; + char utf[Maxutf], *wire; + size_t nwire; + int i, nbyte; + + if(!(state->style & XCB_IM_PreeditCallbacks)) + return; + if(pre->n == 0){ + clearpreedit(state); + return; + } + nbyte = stoutf(pre, utf, sizeof utf); + wire = utf; + nwire = nbyte; + if(state->encoding == Ecompound){ + wire = ximcompound(utf, nbyte, &nwire); + if(wire == nil) + return; + } + for(i = 0; i < pre->n; i++) + feedback[i] = XCB_XIM_UNDERLINE; + if(!state->prestarted){ + xcb_im_preedit_start_callback(xim, state->xic); + state->prestarted = 1; + } + memset(&frame, 0, sizeof frame); + frame.caret = pre->n; + frame.chg_length = state->nprerune; + frame.length_of_preedit_string = (u16int)nwire; + frame.preedit_string = (uchar*)wire; + frame.feedback_array.size = pre->n; + frame.feedback_array.items = feedback; + xcb_im_preedit_draw_callback(xim, state->xic, &frame); + state->nprerune = pre->n; + preowner = state; + if(wire != utf) + free(wire); } static int @@ -127,6 +277,7 @@ sendrequest(Ic *state, int op, u32int key, u32int mod, Keyres *res) memset(&kr, 0, sizeof kr); kr.owner = state; + kr.cap = state->cap; kr.op = op; kr.ks = key; kr.mod = mod & Mmask; @@ -138,8 +289,11 @@ sendrequest(Ic *state, int op, u32int key, u32int mod, Keyres *res) static void keypress(Ic *state, u32int key, u32int mod, Keyres *res) { - if(meaningful(key)) + if(meaningful(key)){ + if(preowner != nil && preowner != state) + clearpreedit(preowner); state->engaged = 1; + } sendrequest(state, Keypress, key, mod, res); } @@ -148,7 +302,10 @@ release(Ic *state) { Keyres res; - if(state == nil || !state->engaged) + if(state == nil) + return; + clearpreedit(state); + if(!state->engaged) return; sendrequest(state, Keyrelease, 0, 0, &res); state->engaged = 0; @@ -172,7 +329,8 @@ kpress(Ic *state, xcb_im_input_context_t *ic, xcb_key_press_event_t *ev) key = rune; keypress(state, key, ev->state, &res); n = stoutf(&res.commit, buf, sizeof buf); - commit(ic, buf, n); + commit(state, buf, n); + updatepreedit(state, &res.preedit); if(!res.eaten) xcb_im_forward_event(xim, ic, ev); xcb_flush(conn); @@ -203,7 +361,20 @@ icfree(void *p) } static void -iccreate(xcb_im_input_context_t *ic) +setstyle(Ic *state, u32int style) +{ + int cap; + + if(!stylecap(style, &cap)) + style = XCB_IM_PreeditNothing | XCB_IM_StatusNothing; + if(state->prestarted && !(style & XCB_IM_PreeditCallbacks)) + clearpreedit(state); + state->style = style; + state->cap = cap; +} + +static void +iccreate(xcb_im_client_t *client, xcb_im_input_context_t *ic) { Ic *state; @@ -212,6 +383,10 @@ iccreate(xcb_im_input_context_t *ic) state = calloc(1, sizeof *state); if(state == nil) return; + state->xic = ic; + state->client = client; + state->encoding = getencoding(client); + setstyle(state, xcb_im_input_context_get_input_style(ic)); state->next = ics; ics = state; xcb_im_input_context_set_data(ic, state, icfree); @@ -225,12 +400,25 @@ callback(xcb_im_t *im, xcb_im_client_t *client, xcb_im_input_context_t *ic, Ic *state; USED(im); - USED(client); USED(frame); USED(user); + if(hdr->major_opcode == XCB_XIM_ENCODING_NEGOTIATION){ + if(arg != nil) + setencoding(client, + *(u16int*)arg == 1 ? Eutf8 : Ecompound); + return; + } + if(hdr->major_opcode == XCB_XIM_DISCONNECT || + hdr->major_opcode == XCB_XIM_CLOSE){ + for(state = ics; state != nil; state = state->next) + if(state->client == client) + release(state); + clearencoding(client); + return; + } if(hdr->major_opcode == XCB_XIM_CREATE_IC){ - iccreate(ic); + iccreate(client, ic); return; } if(ic == nil) @@ -244,6 +432,7 @@ callback(xcb_im_t *im, xcb_im_client_t *client, xcb_im_input_context_t *ic, if(ev != nil && (ev->response_type & ~0x80) == XCB_KEY_PRESS) kpress(state, ic, ev); break; + case XCB_XIM_DESTROY_IC: case XCB_XIM_RESET_IC: case XCB_XIM_UNSET_IC_FOCUS: release(state); @@ -265,6 +454,8 @@ ximclose(void) xim = nil; } opened = 0; + preowner = nil; + memset(clientenc, 0, sizeof clientenc); for(state = ics; state != nil; state = next){ next = state->next; free(state);