#include #include typedef struct Icbinding Icbinding; struct Icbinding { xcb_im_input_context_t *ic; void *data; 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 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_input_context_get_data(ic) \ (wirebinding(ic) == nil ? nil : wirebinding(ic)->data) #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_input_context_get_data #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 "cutest/cutest.h" enum { Wstart, Wdraw, Wdone, Wcommit, 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 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; } 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; } void testengineinit(int); void testenginehandle(Keyreq*); void* testengineowner(void); void testenginepreedit(Str*); typedef struct Ximfix Ximfix; struct Ximfix { Channel *oldreply; Channel *trace; Channel *stop; Channel *done; Channel *waiting; Channel *ack; int holdrelease; int pumpactive; }; typedef struct Freejob Freejob; struct Freejob { Ic *state; Channel *done; }; static void enginepump(void *arg) { Ximfix *f; Keyreq req; uchar token; Alt alts[] = { {keyc, &req, CHANRCV, nil}, {nil, &token, CHANRCV, nil}, {nil, nil, CHANEND, nil}, }; f = arg; alts[1].c = f->stop; for(;;) switch(alt(alts)){ case 0: chansend(f->trace, &req); if(f->holdrelease && req.op == Keyrelease){ token = 0; chansend(f->waiting, &token); chanrecv(f->ack, &token); } testenginehandle(&req); break; case 1: chansend(f->done, &token); return; } } 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); f->trace = chancreate(sizeof(Keyreq), 16); f->stop = chancreate(sizeof(uchar), 0); f->done = chancreate(sizeof(uchar), 0); f->waiting = chancreate(sizeof(uchar), 0); f->ack = chancreate(sizeof(uchar), 0); f->pumpactive = threadcreate(enginepump, f, 8192) >= 0; return CT_CHECK(t, replyc != nil && f->trace != nil && f->stop != nil && f->done != nil && f->waiting != nil && f->ack != nil && f->pumpactive); } static void ximend(Ximfix *f) { Drawcmd dc; uchar token; if(f->pumpactive){ token = 0; chansend(f->stop, &token); chanrecv(f->done, &token); } testengineinit(LangEN); while(channbrecv(drawc, &dc) > 0) ; chanfree(replyc); replyc = f->oldreply; chanfree(f->trace); chanfree(f->stop); chanfree(f->done); chanfree(f->waiting); chanfree(f->ack); } static Keyreq nexttrace(struct ct *t, Ximfix *f, int op, Ic *state) { Keyreq req; memset(&req, 0, sizeof req); if(!CT_CHECK(t, channbrecv(f->trace, &req) > 0)) 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; } static void notrace(struct ct *t, Ximfix *f) { Keyreq req; CT_CHECK(t, channbrecv(f->trace, &req) <= 0); } static void checkstr(struct ct *t, char *want, Str *got) { char buf[Maxutf]; stoutf(got, buf, sizeof buf); CT_EQ_STR(t, want, buf); } static void checkenginepreedit(struct ct *t, char *want) { Str preedit; testenginepreedit(&preedit); checkstr(t, want, &preedit); } 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, "か", &res.preedit); keypress(&state, Kspec|0x57, 0, &res); nexttrace(t, &f, Keypress, &state); CT_CHECK(t, !res.eaten); checkstr(t, "か", &res.commit); checkstr(t, "", &res.preedit); cleanup: ximend(&f); } void xim_adapter_release_lifecycle(struct ct *t) { Ximfix f; Ic a, b, *dead; Keyres res; 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, "ん", &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()); icfree(dead); nexttrace(t, &f, Keyrelease, dead); 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; 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); f.holdrelease = 1; job.state = state; job.done = freed; workeractive = threadcreate(freeworker, &job, 8192) >= 0; if(!CT_CHECK(t, workeractive)) goto cleanup; chanrecv(f.waiting, &token); nexttrace(t, &f, Keyrelease, state); CT_CHECK(t, channbrecv(freed, &token) <= 0); CT_EQ_PTR(t, state, ics); CT_EQ_PTR(t, state, testengineowner()); chansend(f.ack, &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.ack, &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, }; 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, !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_placement(struct ct *t) { static const struct { u32int style; u32int mask; int fgeo; int ftrans; int cgeo; int ctrans; int valid; int x; int y; } cases[] = { {XCB_IM_PreeditPosition | XCB_IM_StatusNothing, XCB_XIM_XNSpotLocation_MASK, 1, 1, 1, 1, 1, 103, 204}, {XCB_IM_PreeditPosition | XCB_IM_StatusNothing, XCB_XIM_XNSpotLocation_MASK, 1, 0, 1, 1, 1, 300, 440}, {XCB_IM_PreeditPosition | XCB_IM_StatusNothing, 0, 1, 1, 1, 1, 1, 100, 230}, {XCB_IM_PreeditNothing | XCB_IM_StatusNothing, XCB_XIM_XNSpotLocation_MASK, 1, 1, 1, 1, 1, 100, 230}, {XCB_IM_PreeditPosition | XCB_IM_StatusNothing, 0, 0, 1, 1, 1, 1, 300, 440}, {XCB_IM_PreeditPosition | XCB_IM_StatusNothing, 0, 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.style = cases[i].style; state.caret.valid = 1; state.caret.x = state.caret.y = 9; wirebind(0, ic, &state); b = &icbindings[0]; b->focuswin = 10; 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; refreshplace(&state); CT_EQ_UINT(t, 10, state.focuswin); CT_EQ_UINT(t, 20, state.clientwin); CT_EQ_INT(t, cases[i].style == (XCB_IM_PreeditPosition | XCB_IM_StatusNothing) && cases[i].mask != 0, state.hasspot); 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; xcb_keysym_t key, *oldkmap; uint8_t oldmin, oldmax, oldper; ic = (xcb_im_input_context_t*)(uintptr)71; memset(&state, 0, sizeof state); oldkmap = kmap; oldmin = minkc; oldmax = maxkc; oldper = symsper; if(!ximbegin(t, &f, LangJP)) goto cleanup; wireclear(); state.xic = ic; state.style = XCB_IM_PreeditPosition | XCB_IM_StatusNothing; 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); key = 'n'; kmap = &key; minkc = maxkc = 8; symsper = 1; memset(&ev, 0, sizeof ev); ev.detail = 8; 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); CT_EQ_INT(t, 15, state.spot.x); CT_EQ_INT(t, 16, state.spot.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: kmap = oldkmap; minkc = oldmin; maxkc = oldmax; symsper = oldper; preowner = nil; ximend(&f); } 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_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; uchar token; int foreign, i, n; ic = (xcb_im_input_context_t*)(uintptr)51; memset(&state, 0, sizeof state); foreign = 0; if(!ximbegin(t, &f, LangJP)) goto cleanup; if(!CT_CHECK(t, wakeinit() == 0)) goto cleanup; wakedrain(); wireclear(); wirebind(0, ic, &state); state.xic = ic; state.style = XCB_IM_PreeditCallbacks | XCB_IM_StatusNothing; state.cap = Cclientpreedit; state.encoding = Eutf8; 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); checkownerwake(); nexttrace(t, &f, Keycap, &state); CT_EQ_PTR(t, &state, preowner); 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.trace, &trace) > 0); CT_EQ_INT(t, Keypress, trace.op); CT_EQ_PTR(t, &foreign, trace.owner); CT_EQ_PTR(t, &foreign, testengineowner()); /* A full pipe cannot block imthread; all bytes coalesce into one probe. */ token = 0; for(i = 0; i < 1<<20; i++){ n = write(wakefd[1], &token, 1); if(n < 0 && errno == EINTR){ i--; continue; } if(n < 0) break; } CT_CHECK(t, i < 1<<20); CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK); ximownernotify(); CT_CHECK(t, wakedrain() > 0); 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); /* 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.trace, &trace) > 0); CT_EQ_INT(t, Keyrelease, trace.op); CT_EQ_PTR(t, &foreign, trace.owner); wakedrain(); 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); }