#include #include typedef struct Icbinding Icbinding; struct Icbinding { xcb_im_input_context_t *ic; void *data; uint32_t style; xcb_window_t clientwin; xcb_window_t focuswin; uint32_t preattrmask; xcb_im_preedit_attr_t preattr; }; static Icbinding icbindings[8]; static Icbinding* wirebinding(xcb_im_input_context_t *ic) { int i; for(i = 0; i < 8; i++) if(icbindings[i].ic == ic) return &icbindings[i]; return 0; } 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 wireforward(xcb_im_t*, xcb_im_input_context_t*, xcb_key_press_event_t*); static void wiresetdata(xcb_im_input_context_t*, void*, xcb_im_free_function); static xcb_get_geometry_cookie_t wiregeometry(xcb_connection_t*, xcb_drawable_t); static xcb_get_geometry_reply_t* wiregeometryreply(xcb_connection_t*, xcb_get_geometry_cookie_t, xcb_generic_error_t**); static xcb_translate_coordinates_cookie_t wiretranslate(xcb_connection_t*, xcb_window_t, xcb_window_t, int16_t, int16_t); static xcb_translate_coordinates_reply_t* wiretranslatereply(xcb_connection_t*, xcb_translate_coordinates_cookie_t, xcb_generic_error_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_forward_event wireforward #define xcb_im_input_context_set_data wiresetdata #define xcb_im_input_context_get_data(ic) \ (wirebinding(ic) == nil ? nil : wirebinding(ic)->data) #define xcb_im_input_context_get_input_style(ic) \ (wirebinding(ic) == nil ? 0 : wirebinding(ic)->style) #define xcb_im_input_context_get_client_window(ic) \ (wirebinding(ic) == nil ? XCB_NONE : wirebinding(ic)->clientwin) #define xcb_im_input_context_get_focus_window(ic) \ (wirebinding(ic) == nil ? XCB_NONE : wirebinding(ic)->focuswin) #define xcb_im_input_context_get_preedit_attr_mask(ic) \ (wirebinding(ic) == nil ? 0 : wirebinding(ic)->preattrmask) #define xcb_im_input_context_get_preedit_attr(ic) \ (wirebinding(ic) == nil ? nil : &wirebinding(ic)->preattr) #define xcb_get_geometry wiregeometry #define xcb_get_geometry_reply wiregeometryreply #define xcb_translate_coordinates wiretranslate #define xcb_translate_coordinates_reply wiretranslatereply #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_forward_event #undef xcb_im_input_context_set_data #undef xcb_im_input_context_get_data #undef xcb_im_input_context_get_input_style #undef xcb_im_input_context_get_client_window #undef xcb_im_input_context_get_focus_window #undef xcb_im_input_context_get_preedit_attr_mask #undef xcb_im_input_context_get_preedit_attr #undef xcb_get_geometry #undef xcb_get_geometry_reply #undef xcb_translate_coordinates #undef xcb_translate_coordinates_reply #undef xcb_flush #include "test.h" enum { Wstart, Wdraw, Wdone, Wcommit, Wforward, Maxwire = 8, }; 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 Wirewin Wirewin; struct Wirewin { xcb_window_t win; int geometryok; int translateok; int rootx; int rooty; int height; }; static Wirecall wirecalls[Maxwire]; static Wirewin wirewins[8]; static int nwirecalls; static int nflush; static int transx, transy; 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 wireforward(xcb_im_t *im, xcb_im_input_context_t *ic, xcb_key_press_event_t *ev) { Wirecall *call; USED(im); call = newwire(Wforward, ic); if(call != nil) call->keysym = ev->detail; } static void wiresetdata(xcb_im_input_context_t *ic, void *data, xcb_im_free_function freefn) { Icbinding *binding; USED(freefn); binding = wirebinding(ic); if(binding != nil) binding->data = data; } static Wirewin* wirewindow(xcb_window_t win) { int i; for(i = 0; i < nelem(wirewins); i++) if(wirewins[i].win == win) return &wirewins[i]; return nil; } static xcb_get_geometry_cookie_t wiregeometry(xcb_connection_t *c, xcb_drawable_t drawable) { xcb_get_geometry_cookie_t cookie; USED(c); cookie.sequence = drawable; return cookie; } static xcb_get_geometry_reply_t* wiregeometryreply(xcb_connection_t *c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t **error) { xcb_get_geometry_reply_t *reply; Wirewin *w; USED(c); USED(error); w = wirewindow(cookie.sequence); if(w == nil || !w->geometryok) return nil; reply = calloc(1, sizeof *reply); if(reply != nil) reply->height = w->height; return reply; } static xcb_translate_coordinates_cookie_t wiretranslate(xcb_connection_t *c, xcb_window_t src, xcb_window_t dst, int16_t x, int16_t y) { xcb_translate_coordinates_cookie_t cookie; USED(c); USED(dst); cookie.sequence = src; transx = x; transy = y; return cookie; } static xcb_translate_coordinates_reply_t* wiretranslatereply(xcb_connection_t *c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t **error) { xcb_translate_coordinates_reply_t *reply; Wirewin *w; USED(c); USED(error); w = wirewindow(cookie.sequence); if(w == nil || !w->translateok) return nil; reply = calloc(1, sizeof *reply); if(reply != nil){ reply->same_screen = 1; reply->dst_x = w->rootx + transx; reply->dst_y = w->rooty + transy; } return reply; } 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); memset(wirewins, 0, sizeof wirewins); nwirecalls = 0; nflush = 0; transx = transy = 0; preowner = nil; } /* Wire text is the COMPOUND_TEXT of what the engine produced. */ static int checkct(struct ct *t, char *where, uchar *got, size_t ngot, char *utf8) { char *want; size_t nwant; int ok; xcb_compound_text_init(); want = xcb_utf8_to_compound_text(utf8, strlen(utf8), &nwant); if(!CT_CHECK(t, want != nil)) return 0; ok = ngot == nwant && memcmp(got, want, nwant) == 0; if(!ok) CT_ERRORF(t, "%s: wire text differs from COMPOUND_TEXT of %s", where, utf8); free(want); return ok; } static void wirebind(int n, xcb_im_input_context_t *ic, Ic *state) { icbindings[n].ic = ic; icbindings[n].data = state; } static Wirewin* wirewin(int n, xcb_window_t win, int x, int y, int h) { Wirewin *w; w = &wirewins[n]; memset(w, 0, sizeof *w); w->win = win; w->geometryok = 1; w->translateok = 1; w->rootx = x; w->rooty = y; w->height = h; return w; } static struct xkb_state* testkeystate(char *layout) { struct xkb_context *context; struct xkb_keymap *keymap; struct xkb_rule_names names; struct xkb_state *state; memset(&names, 0, sizeof names); names.layout = layout; context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if(context == nil) return nil; keymap = xkb_keymap_new_from_names(context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); xkb_context_unref(context); if(keymap == nil) return nil; state = xkb_state_new(keymap); xkb_keymap_unref(keymap); return state; } typedef struct Ximfix Ximfix; struct Ximfix { Channel *oldreply; Pump pump; }; typedef struct Freejob Freejob; struct Freejob { Ic *state; Channel *done; }; static int ximbegin(struct ct *t, Ximfix *f, int lang) { Drawcmd dc; memset(f, 0, sizeof *f); while(channbrecv(drawc, &dc) > 0) ; testengineinit(lang); f->oldreply = replyc; replyc = chancreate(sizeof(Keyres), 0); pumpstart(&f->pump, 16); USED(t); return 1; } static void ximend(Ximfix *f) { Drawcmd dc; pumpstop(&f->pump); testengineinit(LangEN); while(channbrecv(drawc, &dc) > 0) ; chanfree(replyc); replyc = f->oldreply; } static Keyreq nexttracecap(struct ct *t, Ximfix *f, int op, Ic *owner, int cap) { Keyreq req; memset(&req, 0, sizeof req); if(!CT_CHECK(t, channbrecv(f->pump.trace, &req) > 0)) return req; CT_EQ_INT(t, op, req.op); CT_EQ_PTR(t, owner, req.owner); CT_EQ_INT(t, cap, req.cap); CT_EQ_PTR(t, replyc, req.reply); return req; } static Keyreq nexttrace(struct ct *t, Ximfix *f, int op, Ic *state) { return nexttracecap(t, f, op, state, state->cap); } static void notrace(struct ct *t, Ximfix *f) { Keyreq req; CT_CHECK(t, channbrecv(f->pump.trace, &req) <= 0); } void xim_adapter_key_contract(struct ct *t) { Ximfix f; Ic state; Keyreq req; Keyres res; u32int allmod; memset(&state, 0, sizeof state); if(!ximbegin(t, &f, LangJP)) goto cleanup; allmod = ~0; keypress(&state, 'x', allmod, &res); req = nexttrace(t, &f, Keypress, &state); CT_EQ_UINT(t, Mmask, req.mod); CT_CHECK(t, !res.eaten); keypress(&state, 'k', 0, &res); req = nexttrace(t, &f, Keypress, &state); CT_EQ_UINT(t, 0, req.mod); CT_CHECK(t, res.eaten); keypress(&state, 'a', 0, &res); nexttrace(t, &f, Keypress, &state); CT_CHECK(t, res.eaten); checkstr(t, "preedit", "か", &res.preedit); keypress(&state, Kspec|0x57, 0, &res); nexttrace(t, &f, Keypress, &state); CT_CHECK(t, !res.eaten); checkstr(t, "preedit", "か", &res.commit); checkstr(t, "preedit", "", &res.preedit); cleanup: ximend(&f); } void xim_adapter_release_lifecycle(struct ct *t) { Ximfix f; Ic a, b, *dead; Keyres res; int deadcap; memset(&a, 0, sizeof a); memset(&b, 0, sizeof b); dead = nil; if(!ximbegin(t, &f, LangJP)) goto cleanup; keypress(&a, 'k', 0, &res); nexttrace(t, &f, Keypress, &a); keypress(&a, 'a', 0, &res); nexttrace(t, &f, Keypress, &a); keypress(&b, 'n', 0, &res); nexttrace(t, &f, Keypress, &b); checkstr(t, "preedit", "ん", &res.preedit); CT_EQ_PTR(t, &b, testengineowner()); /* ResetIC and focus loss use the same idempotent release path. */ release(&a); nexttrace(t, &f, Keyrelease, &a); checkenginepreedit(t, "ん"); CT_EQ_PTR(t, &b, testengineowner()); release(&a); notrace(t, &f); release(&b); nexttrace(t, &f, Keyrelease, &b); CT_EQ_PTR(t, nil, testengineowner()); keypress(&b, 'k', 0, &res); nexttrace(t, &f, Keypress, &b); release(&b); nexttrace(t, &f, Keyrelease, &b); release(&b); notrace(t, &f); dead = calloc(1, sizeof *dead); if(!CT_CHECK(t, dead != nil)) goto cleanup; dead->next = ics; ics = dead; keypress(dead, 'n', 0, &res); nexttrace(t, &f, Keypress, dead); CT_EQ_PTR(t, dead, testengineowner()); deadcap = dead->cap; icfree(dead); nexttracecap(t, &f, Keyrelease, dead, deadcap); dead = nil; CT_EQ_PTR(t, nil, ics); CT_EQ_PTR(t, nil, testengineowner()); cleanup: if(dead != nil){ icunlink(dead); free(dead); } ximend(&f); } static void freeworker(void *arg) { Freejob *job; uchar token; job = arg; icfree(job->state); token = 0; chansend(job->done, &token); } void xim_adapter_free_waits_for_release(struct ct *t) { Ximfix f; Freejob job; Ic *state; Channel *freed; Keyreq req; Keyres res; uchar token; int workeractive; state = nil; freed = nil; workeractive = 0; if(!ximbegin(t, &f, LangJP)) goto cleanup; state = calloc(1, sizeof *state); freed = chancreate(sizeof(uchar), 0); if(!CT_CHECK(t, state != nil && freed != nil)) goto cleanup; state->next = ics; ics = state; keypress(state, 'n', 0, &res); nexttrace(t, &f, Keypress, state); /* Hold the release: the free must not complete before the engine * acknowledges it, since the engine still points at this Ic. */ pumphold(&f.pump, Keyrelease); job.state = state; job.done = freed; workeractive = threadcreate(freeworker, &job, 8192) >= 0; if(!CT_CHECK(t, workeractive)) goto cleanup; chanrecv(f.pump.trace, &req); CT_EQ_INT(t, Keyrelease, req.op); CT_EQ_PTR(t, state, req.owner); CT_CHECK(t, channbrecv(freed, &token) <= 0); CT_EQ_PTR(t, state, ics); CT_EQ_PTR(t, state, testengineowner()); chansend(f.pump.go, &token); chanrecv(freed, &token); workeractive = 0; state = nil; CT_EQ_PTR(t, nil, ics); CT_EQ_PTR(t, nil, testengineowner()); cleanup: if(workeractive){ chansend(f.pump.go, &token); chanrecv(freed, &token); state = nil; } if(state != nil){ icunlink(state); free(state); } if(freed != nil) 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, }; xcb_im_input_context_t *ic; Ic *state; int i; CT_EQ_SIZE(t, 1, nelem(encs)); CT_EQ_STR(t, "COMPOUND_TEXT", encs[0]); CT_EQ_SIZE(t, nelem(want), nelem(styles)); /* Only PreeditCallbacks clients draw the preedit themselves. */ for(i = 0; i < nelem(want); i++){ CT_EQ_UINT(t, want[i], styles[i]); wireclear(); ic = (xcb_im_input_context_t*)(uintptr)(80+i); wirebind(0, ic, nil); icbindings[0].style = want[i]; iccreate(nil, ic); state = icbindings[0].data; if(!CT_CHECK(t, state != nil && state == ics)) continue; CT_EQ_INT(t, i == 1 ? Cclientpreedit : 0, state->cap); CT_EQ_PTR(t, ic, state->xic); icunlink(state); free(state); } } void xim_adapter_placement(struct ct *t) { /* Spot location first, then the bottom of the focus window, then of * the client window; an unset focus window is the client window. */ static const struct { u32int mask; int focuswin; int fgeo; int ftrans; int cgeo; int ctrans; int valid; int x; int y; } cases[] = { {XCB_XIM_XNSpotLocation_MASK, 10, 1, 1, 1, 1, 1, 103, 204}, {XCB_XIM_XNSpotLocation_MASK, 10, 1, 0, 1, 1, 1, 300, 440}, {XCB_XIM_XNSpotLocation_MASK, 0, 1, 1, 1, 1, 1, 303, 404}, {0, 10, 1, 1, 1, 1, 1, 100, 230}, {0, 0, 1, 1, 1, 1, 1, 300, 440}, {0, 10, 0, 1, 1, 1, 1, 300, 440}, {0, 10, 0, 1, 0, 1, 0, 0, 0}, }; xcb_im_input_context_t *ic; Icbinding *b; Wirewin *focus, *client; Ic state; int i; ic = (xcb_im_input_context_t*)(uintptr)70; for(i = 0; i < nelem(cases); i++){ wireclear(); memset(&state, 0, sizeof state); state.xic = ic; state.caret.valid = 1; state.caret.x = state.caret.y = 9; wirebind(0, ic, &state); b = &icbindings[0]; b->focuswin = cases[i].focuswin; b->clientwin = 20; b->preattrmask = cases[i].mask; b->preattr.spot_location.x = 3; b->preattr.spot_location.y = 4; focus = wirewin(0, 10, 100, 200, 30); client = wirewin(1, 20, 300, 400, 40); focus->geometryok = cases[i].fgeo; focus->translateok = cases[i].ftrans; client->geometryok = cases[i].cgeo; client->translateok = cases[i].ctrans; place(&state); CT_EQ_INT(t, cases[i].valid, state.caret.valid); CT_EQ_INT(t, cases[i].x, state.caret.x); CT_EQ_INT(t, cases[i].y, state.caret.y); CT_EQ_INT(t, 0, state.caret.h); } } void xim_adapter_placement_updates(struct ct *t) { xcb_im_packet_header_fr_t hdr; xcb_im_input_context_t *ic; xcb_key_press_event_t ev; Icbinding *b; Ximfix f; Keyreq req; Ic state; struct xkb_state *oldstate; ic = (xcb_im_input_context_t*)(uintptr)71; memset(&state, 0, sizeof state); oldstate = kstate; if(!ximbegin(t, &f, LangJP)) goto cleanup; kstate = testkeystate("us"); if(!CT_CHECK(t, kstate != nil)) goto cleanup; wireclear(); state.xic = ic; wirebind(0, ic, &state); b = &icbindings[0]; b->focuswin = 11; b->clientwin = 21; b->preattrmask = XCB_XIM_XNSpotLocation_MASK; b->preattr.spot_location.x = 5; b->preattr.spot_location.y = 6; wirewin(0, 11, 100, 200, 30); memset(&ev, 0, sizeof ev); ev.detail = 57; kpress(&state, &ev); req = nexttrace(t, &f, Keypress, &state); CT_CHECK(t, req.caret.valid); CT_EQ_INT(t, 105, req.caret.x); CT_EQ_INT(t, 206, req.caret.y); wirewins[0].rootx = 150; wirewins[0].rooty = 250; kpress(&state, &ev); req = nexttrace(t, &f, Keypress, &state); CT_EQ_INT(t, 155, req.caret.x); CT_EQ_INT(t, 256, req.caret.y); b->preattr.spot_location.x = 15; b->preattr.spot_location.y = 16; memset(&hdr, 0, sizeof hdr); hdr.major_opcode = XCB_XIM_SET_IC_VALUES; callback(nil, nil, ic, &hdr, nil, nil, nil); req = nexttrace(t, &f, Keycaret, &state); CT_CHECK(t, req.caret.valid); CT_EQ_INT(t, 165, req.caret.x); CT_EQ_INT(t, 266, req.caret.y); b->preattrmask = 0; wirewins[0].geometryok = 0; hdr.major_opcode = XCB_XIM_SET_IC_FOCUS; callback(nil, nil, ic, &hdr, nil, nil, nil); req = nexttrace(t, &f, Keycaret, &state); CT_CHECK(t, !req.caret.valid); cleanup: if(kstate != oldstate) xkb_state_unref(kstate); kstate = oldstate; preowner = nil; ximend(&f); } 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.cap = Cclientpreedit; 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); checkct(t, "replacement", 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; int i; for(i = 0; i < nelem(text); i++){ memset(&state, 0, sizeof state); state.xic = (xcb_im_input_context_t*)(uintptr)(10+i); state.cap = Cclientpreedit; 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); checkct(t, text[i], call->text, call->nbyte, text[i]); } } static void startstate(Ic *state, xcb_im_input_context_t *ic) { Str pre; memset(state, 0, sizeof *state); state->xic = ic; state->cap = Cclientpreedit; sinit(&pre, "あ", strlen("あ")); updatepreedit(state, &pre); } void xim_adapter_callback_cleanup(struct ct *t) { static int opcode[] = { XCB_XIM_UNSET_IC_FOCUS, XCB_XIM_DESTROY_IC, }; xcb_im_packet_header_fr_t hdr; xcb_im_reset_ic_reply_fr_t reply; xcb_im_client_t *client; xcb_im_input_context_t *ic; Ximfix f; Keyres res; Ic state, *dead; int i; ic = (xcb_im_input_context_t*)(uintptr)29; memset(&state, 0, sizeof state); if(!ximbegin(t, &f, LangJP)) return; wireclear(); wirebind(0, ic, &state); state.xic = ic; state.cap = Cclientpreedit; keypress(&state, 'k', 0, &res); nexttrace(t, &f, Keypress, &state); keypress(&state, 'a', 0, &res); nexttrace(t, &f, Keypress, &state); updatepreedit(&state, &res.preedit); memset(&hdr, 0, sizeof hdr); memset(&reply, 0, sizeof reply); hdr.major_opcode = XCB_XIM_RESET_IC; callback(nil, nil, ic, &hdr, nil, &reply, nil); nexttrace(t, &f, Keyreset, &state); notrace(t, &f); checkct(t, "reset reply", reply.committed_string, reply.byte_length_of_committed_string, "か"); free(reply.committed_string); CT_CHECK(t, state.engaged); CT_EQ_PTR(t, &state, testengineowner()); CT_EQ_INT(t, Wdraw, wirecalls[2].op); CT_EQ_INT(t, Wdone, wirecalls[3].op); keypress(&state, 'n', 0, &res); nexttrace(t, &f, Keypress, &state); checkstr(t, "preedit", "ん", &res.preedit); release(&state); nexttrace(t, &f, Keyrelease, &state); preowner = nil; ximend(&f); 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.cap = Cclientpreedit; 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_callback_owner_loss(struct ct *t) { Ximfix f; Ic state; xcb_im_input_context_t *ic; xcb_im_packet_header_fr_t hdr; Keyreq req; Keyreq trace; Keyres res; int foreign; ic = (xcb_im_input_context_t*)(uintptr)51; memset(&state, 0, sizeof state); foreign = 0; if(!ximbegin(t, &f, LangJP)) goto cleanup; wireclear(); wirebind(0, ic, &state); state.xic = ic; state.cap = Cclientpreedit; keypress(&state, 'k', 0, &res); nexttrace(t, &f, Keypress, &state); updatepreedit(&state, &res.preedit); CT_EQ_INT(t, Wstart, wirecalls[0].op); CT_EQ_INT(t, Wdraw, wirecalls[1].op); CT_EQ_PTR(t, &state, preowner); /* While we still own the engine, the periodic check changes nothing. */ checkpreowner(); nexttrace(t, &f, Keycap, &state); CT_EQ_PTR(t, &state, preowner); CT_EQ_INT(t, 2, nwirecalls); /* Another frontend takes the engine; the next check clears our preedit. */ memset(&req, 0, sizeof req); req.owner = &foreign; req.op = Keypress; req.ks = 'n'; req.reply = replyc; chansend(keyc, &req); chanrecv(replyc, &res); memset(&trace, 0, sizeof trace); CT_CHECK(t, channbrecv(f.pump.trace, &trace) > 0); CT_EQ_INT(t, Keypress, trace.op); CT_EQ_PTR(t, &foreign, trace.owner); CT_EQ_PTR(t, &foreign, testengineowner()); checkpreowner(); nexttrace(t, &f, Keycap, &state); CT_EQ_PTR(t, &foreign, testengineowner()); 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); checkpreowner(); notrace(t, &f); /* Late XIM lifecycle messages remain idempotent after owner cleanup. */ release(&state); nexttrace(t, &f, Keyrelease, &state); release(&state); notrace(t, &f); memset(&hdr, 0, sizeof hdr); hdr.major_opcode = XCB_XIM_RESET_IC; callback(nil, nil, ic, &hdr, nil, nil, nil); notrace(t, &f); hdr.major_opcode = XCB_XIM_DESTROY_IC; callback(nil, nil, ic, &hdr, nil, nil, nil); notrace(t, &f); CT_EQ_INT(t, 4, nwirecalls); memset(&req, 0, sizeof req); req.owner = &foreign; req.op = Keyrelease; req.reply = replyc; chansend(keyc, &req); chanrecv(replyc, &res); memset(&trace, 0, sizeof trace); CT_CHECK(t, channbrecv(f.pump.trace, &trace) > 0); CT_EQ_INT(t, Keyrelease, trace.op); CT_EQ_PTR(t, &foreign, trace.owner); cleanup: preowner = nil; ximend(&f); } void xim_adapter_commit_encoding(struct ct *t) { static char text[] = "한𠀋❤️👩‍💻"; Ic state; xcb_im_input_context_t *ic; ic = (xcb_im_input_context_t*)(uintptr)60; memset(&state, 0, sizeof state); state.xic = ic; 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); checkct(t, "commit", wirecalls[0].text, wirecalls[0].nbyte, text); wireclear(); commit(&state, text, 0); CT_EQ_INT(t, 0, nwirecalls); }