ipc: negotiate preedit capability and caret messages

This commit is contained in:
2026-08-14 18:53:14 +09:00
parent b536573622
commit 6a58b63223
7 changed files with 538 additions and 17 deletions

View File

@@ -142,17 +142,23 @@ sendreset(struct ct *t, Testclient *client, int want)
}
static Keyreq
nextrequest(struct ct *t, Enginegate *g, int op)
nextrequestcap(struct ct *t, Enginegate *g, int op, int cap)
{
Keyreq req;
memset(&req, 0, sizeof req);
chanrecv(g->seen, &req);
CT_EQ_INT(t, op, req.op);
CT_EQ_INT(t, Cclientpreedit, req.cap);
CT_EQ_INT(t, cap, req.cap);
return req;
}
static Keyreq
nextrequest(struct ct *t, Enginegate *g, int op)
{
return nextrequestcap(t, g, op, Cclientpreedit);
}
static void
allowrequest(Enginegate *g)
{
@@ -175,6 +181,31 @@ readreply(struct ct *t, Testclient *client, int want, char *preedit, int npreedi
return resp.eaten;
}
static int
sendframe(struct ct *t, Testclient *client, uchar *frame, int n, int fragment)
{
int i, m;
for(i = 0; i < n; i += m){
m = fragment && n-i > 1 ? 1 : n-i;
if(ipcsend(client->peer, frame+i, m) < 0)
return CT_ERRORF(t, "frame send failed: %s", strerror(errno));
}
return 1;
}
static void
checknoreply(struct ct *t, Testclient *client)
{
uchar byte;
ssize_t n;
errno = 0;
n = recv(client->peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
CT_EQ_INT(t, -1, n);
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
}
static void
waitclient(struct ct *t, Testclient *client)
{
@@ -369,3 +400,236 @@ cleanup:
while(channbrecv(drawc, &dc) > 0)
;
}
void
server_extension_stream(struct ct *t)
{
Channel *oldclientc;
Enginegate gate;
Testclient a, b;
Keyreq req;
Drawcmd dc;
uchar frame[Ipccaretsz], token;
char preedit[Maxutf];
void *aowner, *bowner;
int gateactive;
memset(&gate, 0, sizeof gate);
memset(&a, 0, sizeof a);
memset(&b, 0, sizeof b);
a.fd = a.peer = b.fd = b.peer = -1;
aowner = bowner = nil;
oldclientc = clientc;
while(channbrecv(drawc, &dc) > 0)
;
clientc = chancreate(sizeof token, 2);
gate.seen = chancreate(sizeof(Keyreq), 0);
gate.go = chancreate(sizeof token, 0);
gate.stop = chancreate(sizeof token, 0);
gate.done = chancreate(sizeof token, 0);
testengineinit(LangJP);
gateactive = proccreate(enginegate, &gate, 8192) >= 0;
if(!CT_CHECK(t, gateactive))
goto cleanup;
if(!startclient(t, &a))
goto cleanup;
/* Capability and caret frames may be split at any byte boundary. */
ipcpackcap(frame, Cclientpreedit);
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
goto cleanup;
req = nextrequest(t, &gate, Keycap);
aowner = req.owner;
CT_EQ_PTR(t, nil, testengineowner());
allowrequest(&gate);
CT_EQ_INT(t, 1, readreply(t, &a, 1, preedit, sizeof preedit));
CT_EQ_STR(t, "", preedit);
CT_EQ_INT(t, 0, ipcpackcaret(frame, 1, -101, -7, 23));
if(!sendframe(t, &a, frame, Ipccaretsz, 1))
goto cleanup;
req = nextrequest(t, &gate, Keycaret);
CT_EQ_PTR(t, aowner, req.owner);
CT_EQ_INT(t, 1, req.caret.valid);
CT_EQ_INT(t, -101, req.caret.x);
CT_EQ_INT(t, -7, req.caret.y);
CT_EQ_INT(t, 23, req.caret.h);
CT_EQ_PTR(t, nil, testengineowner());
allowrequest(&gate);
checknoreply(t, &a);
ipcpackreq(frame, 1, 0, 'k');
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
CT_EQ_PTR(t, aowner, req.owner);
CT_EQ_INT(t, 1, req.caret.valid);
CT_EQ_INT(t, -101, req.caret.x);
CT_EQ_INT(t, -7, req.caret.y);
CT_EQ_INT(t, 23, req.caret.h);
allowrequest(&gate);
CT_EQ_INT(t, 1, readreply(t, &a, 1, preedit, sizeof preedit));
CT_EQ_STR(t, "k", preedit);
CT_EQ_PTR(t, aowner, testengineowner());
ipcpackcap(frame, 0);
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
goto cleanup;
req = nextrequestcap(t, &gate, Keycap, 0);
CT_EQ_PTR(t, aowner, req.owner);
CT_EQ_INT(t, 1, req.caret.valid);
allowrequest(&gate);
CT_EQ_INT(t, 1, readreply(t, &a, 0, nil, 0));
/* Reset is still the old six-byte frame and does not discard caret. */
ipcpackreset(frame, 0);
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
goto cleanup;
req = nextrequestcap(t, &gate, Keyreset, 0);
CT_EQ_PTR(t, aowner, req.owner);
CT_EQ_INT(t, 1, req.caret.valid);
CT_EQ_INT(t, -101, req.caret.x);
CT_EQ_INT(t, -7, req.caret.y);
CT_EQ_INT(t, 23, req.caret.h);
allowrequest(&gate);
CT_EQ_INT(t, 1, readreply(t, &a, 0, nil, 0));
ipcpackcap(frame, Cclientpreedit);
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
goto cleanup;
req = nextrequest(t, &gate, Keycap);
allowrequest(&gate);
CT_EQ_INT(t, 1, readreply(t, &a, 1, preedit, sizeof preedit));
CT_EQ_STR(t, "", preedit);
ipcpackreq(frame, 1, 0, 'n');
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
CT_EQ_INT(t, 1, req.caret.valid);
CT_EQ_INT(t, -101, req.caret.x);
allowrequest(&gate);
CT_EQ_INT(t, 1, readreply(t, &a, 1, preedit, sizeof preedit));
CT_EQ_STR(t, "", preedit);
disconnectclient(t, &gate, &a, aowner);
/* Disconnect drops state; a legacy client gets the historical defaults. */
if(!startclient(t, &b) || !sendkey(t, &b, 1, 0, 'k'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
bowner = req.owner;
CT_EQ_INT(t, 0, req.caret.valid);
allowrequest(&gate);
CT_EQ_INT(t, 1, readreply(t, &b, 1, preedit, sizeof preedit));
CT_EQ_STR(t, "k", preedit);
disconnectclient(t, &gate, &b, bowner);
cleanup:
if(a.peer >= 0)
disconnectclient(t, &gate, &a, aowner);
if(b.peer >= 0)
disconnectclient(t, &gate, &b, bowner);
if(gateactive){
token = 0;
chansend(gate.stop, &token);
chanrecv(gate.done, &token);
}
chanfree(gate.seen);
chanfree(gate.go);
chanfree(gate.stop);
chanfree(gate.done);
chanfree(clientc);
clientc = oldclientc;
while(channbrecv(drawc, &dc) > 0)
;
}
void
server_rejects_unknown_extension(struct ct *t)
{
Channel *oldclientc;
Enginegate gate;
Testclient badver, badop, good;
Keyreq req;
Drawcmd dc;
uchar frame[Ipccaretsz], token;
char preedit[Maxutf];
void *owner;
int gateactive;
memset(&gate, 0, sizeof gate);
memset(&badver, 0, sizeof badver);
memset(&badop, 0, sizeof badop);
memset(&good, 0, sizeof good);
badver.fd = badver.peer = badop.fd = badop.peer = -1;
good.fd = good.peer = -1;
owner = nil;
oldclientc = clientc;
while(channbrecv(drawc, &dc) > 0)
;
clientc = chancreate(sizeof token, 3);
gate.seen = chancreate(sizeof(Keyreq), 0);
gate.go = chancreate(sizeof token, 0);
gate.stop = chancreate(sizeof token, 0);
gate.done = chancreate(sizeof token, 0);
testengineinit(LangJP);
gateactive = proccreate(enginegate, &gate, 8192) >= 0;
if(!CT_CHECK(t, gateactive))
goto cleanup;
if(!startclient(t, &badver))
goto cleanup;
CT_EQ_INT(t, 0, ipcpackcaret(frame, 1, 3, 4, 5));
frame[1]++;
if(!sendframe(t, &badver, frame, sizeof frame, 0))
goto cleanup;
req = nextrequest(t, &gate, Keyrelease);
allowrequest(&gate);
waitclient(t, &badver);
close(badver.peer);
badver.peer = -1;
if(!startclient(t, &badop))
goto cleanup;
CT_EQ_INT(t, 0, ipcpackcaret(frame, 1, 3, 4, 5));
frame[2]++;
if(!sendframe(t, &badop, frame, sizeof frame, 1))
goto cleanup;
req = nextrequest(t, &gate, Keyrelease);
allowrequest(&gate);
waitclient(t, &badop);
close(badop.peer);
badop.peer = -1;
/* A malformed peer must not disturb another client connection. */
if(!startclient(t, &good) || !sendkey(t, &good, 1, 0, 'k'))
goto cleanup;
req = nextrequest(t, &gate, Keypress);
owner = req.owner;
allowrequest(&gate);
CT_EQ_INT(t, 1, readreply(t, &good, 1, preedit, sizeof preedit));
CT_EQ_STR(t, "k", preedit);
disconnectclient(t, &gate, &good, owner);
cleanup:
if(badver.peer >= 0)
disconnectclient(t, &gate, &badver, nil);
if(badop.peer >= 0)
disconnectclient(t, &gate, &badop, nil);
if(good.peer >= 0)
disconnectclient(t, &gate, &good, owner);
if(gateactive){
token = 0;
chansend(gate.stop, &token);
chanrecv(gate.done, &token);
}
chanfree(gate.seen);
chanfree(gate.go);
chanfree(gate.stop);
chanfree(gate.done);
chanfree(clientc);
clientc = oldclientc;
while(channbrecv(drawc, &dc) > 0)
;
}