Two applications' focus events cross — ibus-daemon documents the case — so the first key of the new one can arrive before the old one says it lost focus. The engine dropped whatever that context was composing; it now keeps it and hands it back with the reset or release that follows, which every frontend already commits. Normal ordering is unchanged.
550 lines
13 KiB
C
550 lines
13 KiB
C
#include <errno.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "srv.c"
|
|
#include "test.h"
|
|
|
|
#undef recv
|
|
|
|
typedef struct Serverfix Serverfix;
|
|
typedef struct Testclient Testclient;
|
|
|
|
/* A held pump plus a private client-slot channel of the wanted depth. */
|
|
struct Serverfix
|
|
{
|
|
Pump pump;
|
|
Channel *oldclientc;
|
|
};
|
|
|
|
struct Testclient
|
|
{
|
|
int fd;
|
|
int peer;
|
|
dev_t dev;
|
|
ino_t ino;
|
|
Channel *done;
|
|
};
|
|
|
|
static void
|
|
serverbegin(Serverfix *f, int nclients)
|
|
{
|
|
Drawcmd dc;
|
|
uchar token;
|
|
|
|
f->oldclientc = clientc;
|
|
while(channbrecv(drawc, &dc) > 0)
|
|
;
|
|
clientc = chancreate(sizeof token, nclients);
|
|
testengineinit(LangJP);
|
|
pumpstart(&f->pump, 0);
|
|
pumphold(&f->pump, Pumpall);
|
|
}
|
|
|
|
static void
|
|
serverend(Serverfix *f)
|
|
{
|
|
Drawcmd dc;
|
|
|
|
pumpstop(&f->pump);
|
|
chanfree(clientc);
|
|
clientc = f->oldclientc;
|
|
while(channbrecv(drawc, &dc) > 0)
|
|
;
|
|
}
|
|
|
|
static void
|
|
clientproc(void *arg)
|
|
{
|
|
Testclient *client;
|
|
uchar token;
|
|
|
|
client = arg;
|
|
clientthread((void*)(uintptr)client->fd);
|
|
token = 0;
|
|
chansend(client->done, &token);
|
|
}
|
|
|
|
static int
|
|
startclient(struct ct *t, Testclient *client)
|
|
{
|
|
struct stat st;
|
|
int fd[2];
|
|
uchar token;
|
|
|
|
memset(client, 0, sizeof *client);
|
|
client->fd = -1;
|
|
client->peer = -1;
|
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0)
|
|
return CT_ERRORF(t, "socketpair failed: %s", strerror(errno));
|
|
client->fd = fd[0];
|
|
client->peer = fd[1];
|
|
if(fstat(client->fd, &st) < 0){
|
|
close(client->fd);
|
|
close(client->peer);
|
|
client->fd = -1;
|
|
client->peer = -1;
|
|
return CT_ERRORF(t, "socket stat failed: %s", strerror(errno));
|
|
}
|
|
client->dev = st.st_dev;
|
|
client->ino = st.st_ino;
|
|
client->done = chancreate(sizeof token, 0);
|
|
token = 0;
|
|
if(channbsend(clientc, &token) <= 0){
|
|
close(client->fd);
|
|
close(client->peer);
|
|
chanfree(client->done);
|
|
client->fd = -1;
|
|
client->peer = -1;
|
|
client->done = nil;
|
|
return CT_ERRORF(t, "client slot unavailable");
|
|
}
|
|
if(proccreate(clientproc, client, 8192) < 0){
|
|
chanrecv(clientc, &token);
|
|
close(client->fd);
|
|
close(client->peer);
|
|
chanfree(client->done);
|
|
client->fd = -1;
|
|
client->peer = -1;
|
|
client->done = nil;
|
|
return CT_ERRORF(t, "client worker creation failed");
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
sendkey(struct ct *t, Testclient *client, int want, u32int mod, u32int key)
|
|
{
|
|
uchar req[Ipcreqsz];
|
|
|
|
ipcpackreq(req, want, mod, key);
|
|
if(ipcsend(client->peer, req, sizeof req) == 0)
|
|
return 1;
|
|
return CT_ERRORF(t, "request send failed: %s", strerror(errno));
|
|
}
|
|
|
|
static int
|
|
sendreset(struct ct *t, Testclient *client, int want)
|
|
{
|
|
uchar req[Ipcreqsz];
|
|
|
|
ipcpackreset(req, want);
|
|
if(ipcsend(client->peer, req, sizeof req) == 0)
|
|
return 1;
|
|
return CT_ERRORF(t, "reset send failed: %s", strerror(errno));
|
|
}
|
|
|
|
static Keyreq
|
|
nextrequestcap(struct ct *t, Pump *p, int op, int cap)
|
|
{
|
|
Keyreq req;
|
|
|
|
memset(&req, 0, sizeof req);
|
|
chanrecv(p->trace, &req);
|
|
CT_EQ_INT(t, op, req.op);
|
|
CT_EQ_INT(t, cap, req.clientpre);
|
|
return req;
|
|
}
|
|
|
|
static Keyreq
|
|
nextrequest(struct ct *t, Pump *p, int op)
|
|
{
|
|
return nextrequestcap(t, p, op, 1);
|
|
}
|
|
|
|
static void
|
|
allowrequest(Pump *p)
|
|
{
|
|
uchar token;
|
|
|
|
token = 0;
|
|
chansend(p->go, &token);
|
|
}
|
|
|
|
static int
|
|
readreply(struct ct *t, Testclient *client, int want, char *wantcommit,
|
|
char *preedit)
|
|
{
|
|
char commit[Ipcfieldmax+1];
|
|
Ipcresp resp;
|
|
|
|
if(ipcreadresp(client->peer, want, commit, preedit, &resp) < 0)
|
|
return CT_ERRORF(t, "response read failed: %s", strerror(errno));
|
|
CT_EQ_STR(t, wantcommit, commit);
|
|
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)
|
|
{
|
|
struct stat st;
|
|
uchar token;
|
|
|
|
chanrecv(client->done, &token);
|
|
errno = 0;
|
|
if(fstat(client->fd, &st) == 0)
|
|
CT_CHECK(t, st.st_dev != client->dev || st.st_ino != client->ino);
|
|
else
|
|
CT_EQ_INT(t, EBADF, errno);
|
|
chanfree(client->done);
|
|
client->done = nil;
|
|
}
|
|
|
|
static void
|
|
disconnectclient(struct ct *t, Pump *p, Testclient *client, void *owner)
|
|
{
|
|
Keyreq req;
|
|
uchar token;
|
|
|
|
close(client->peer);
|
|
client->peer = -1;
|
|
req = nextrequest(t, p, Keyrelease);
|
|
if(owner != nil)
|
|
CT_EQ_PTR(t, owner, req.owner);
|
|
CT_CHECK(t, channbrecv(client->done, &token) <= 0);
|
|
allowrequest(p);
|
|
waitclient(t, client);
|
|
}
|
|
|
|
void
|
|
server_connection_ownership(struct ct *t)
|
|
{
|
|
Serverfix f;
|
|
Testclient a, b, c;
|
|
Keyreq req;
|
|
Str shown;
|
|
char preedit[Ipcfieldmax+1];
|
|
void *aowner, *bowner, *cowner;
|
|
uchar byte, token;
|
|
ssize_t n;
|
|
|
|
memset(&a, 0, sizeof a);
|
|
memset(&b, 0, sizeof b);
|
|
memset(&c, 0, sizeof c);
|
|
a.fd = a.peer = b.fd = b.peer = c.fd = c.peer = -1;
|
|
aowner = bowner = cowner = nil;
|
|
serverbegin(&f, 3);
|
|
if(!startclient(t, &a) || !startclient(t, &b))
|
|
goto cleanup;
|
|
|
|
if(!sendkey(t, &a, 1, 0, 'k'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
aowner = req.owner;
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
|
|
CT_EQ_STR(t, "k", preedit);
|
|
if(!sendkey(t, &a, 1, 0, 'a'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
CT_EQ_PTR(t, aowner, req.owner);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
|
|
CT_EQ_STR(t, "か", preedit);
|
|
CT_EQ_PTR(t, aowner, testengineowner());
|
|
|
|
if(!sendkey(t, &b, 1, 0, 'n'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
bowner = req.owner;
|
|
CT_CHECK(t, bowner != aowner);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
|
CT_EQ_STR(t, "ん", preedit);
|
|
CT_EQ_PTR(t, bowner, testengineowner());
|
|
|
|
if(!sendreset(t, &a, 1))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keyreset);
|
|
CT_EQ_PTR(t, aowner, req.owner);
|
|
allowrequest(&f.pump);
|
|
/* The reset hands back what the takeover took from this client. */
|
|
CT_CHECK(t, readreply(t, &a, 1, "か", preedit));
|
|
CT_EQ_STR(t, "", preedit);
|
|
CT_EQ_PTR(t, bowner, testengineowner());
|
|
if(!sendkey(t, &b, 1, 0, 0))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, !readreply(t, &b, 1, "", preedit));
|
|
CT_EQ_STR(t, "ん", preedit);
|
|
|
|
disconnectclient(t, &f.pump, &a, aowner);
|
|
CT_EQ_PTR(t, bowner, testengineowner());
|
|
if(!sendkey(t, &b, 1, 0, 'y'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
|
if(!sendkey(t, &b, 1, 0, 'a'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
|
CT_EQ_STR(t, "にゃ", preedit);
|
|
|
|
if(!sendreset(t, &b, 1))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keyreset);
|
|
CT_EQ_PTR(t, bowner, req.owner);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &b, 1, "にゃ", preedit));
|
|
CT_EQ_STR(t, "", preedit);
|
|
CT_EQ_PTR(t, bowner, testengineowner());
|
|
|
|
if(!sendkey(t, &b, 0, 0, 'k'))
|
|
goto cleanup;
|
|
req = nextrequestcap(t, &f.pump, Keypress, 0);
|
|
CT_EQ_PTR(t, bowner, req.owner);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &b, 0, "", preedit));
|
|
errno = 0;
|
|
n = recv(b.peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
|
|
CT_EQ_INT(t, -1, n);
|
|
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
|
|
CT_EQ_PTR(t, bowner, testengineowner());
|
|
if(!sendkey(t, &b, 1, 0, 'a'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
|
CT_EQ_STR(t, "か", preedit);
|
|
|
|
disconnectclient(t, &f.pump, &b, bowner);
|
|
CT_EQ_PTR(t, nil, testengineowner());
|
|
testenginepreedit(&shown);
|
|
CT_EQ_INT(t, 0, shown.n);
|
|
|
|
if(!startclient(t, &c))
|
|
goto cleanup;
|
|
if(!sendkey(t, &c, 1, 0, 'k'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
cowner = req.owner;
|
|
close(c.peer);
|
|
c.peer = -1;
|
|
allowrequest(&f.pump);
|
|
req = nextrequest(t, &f.pump, Keyrelease);
|
|
CT_EQ_PTR(t, cowner, req.owner);
|
|
CT_EQ_PTR(t, cowner, testengineowner());
|
|
CT_CHECK(t, channbrecv(c.done, &token) <= 0);
|
|
allowrequest(&f.pump);
|
|
waitclient(t, &c);
|
|
CT_EQ_PTR(t, nil, testengineowner());
|
|
CT_CHECK(t, channbrecv(clientc, &token) <= 0);
|
|
|
|
cleanup:
|
|
if(a.peer >= 0)
|
|
disconnectclient(t, &f.pump, &a, aowner);
|
|
if(b.peer >= 0)
|
|
disconnectclient(t, &f.pump, &b, bowner);
|
|
if(c.peer >= 0)
|
|
disconnectclient(t, &f.pump, &c, cowner);
|
|
serverend(&f);
|
|
}
|
|
|
|
void
|
|
server_extension_stream(struct ct *t)
|
|
{
|
|
Serverfix f;
|
|
Testclient a, b;
|
|
Keyreq req;
|
|
uchar frame[Ipccaretsz];
|
|
char preedit[Ipcfieldmax+1];
|
|
void *aowner, *bowner;
|
|
|
|
memset(&a, 0, sizeof a);
|
|
memset(&b, 0, sizeof b);
|
|
a.fd = a.peer = b.fd = b.peer = -1;
|
|
aowner = bowner = nil;
|
|
serverbegin(&f, 2);
|
|
if(!startclient(t, &a))
|
|
goto cleanup;
|
|
|
|
/* Capability and caret frames may be split at any byte boundary. */
|
|
ipcpackcap(frame, 1);
|
|
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keycap);
|
|
aowner = req.owner;
|
|
CT_EQ_PTR(t, nil, testengineowner());
|
|
allowrequest(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
|
CT_EQ_STR(t, "", preedit);
|
|
|
|
ipcpackcaret(frame, 1, -101, -7, 23);
|
|
if(!sendframe(t, &a, frame, Ipccaretsz, 1))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, 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(&f.pump);
|
|
checknoreply(t, &a);
|
|
|
|
ipcpackreq(frame, 1, 0, 'k');
|
|
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, 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(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", 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, &f.pump, Keycap, 0);
|
|
CT_EQ_PTR(t, aowner, req.owner);
|
|
CT_EQ_INT(t, 1, req.caret.valid);
|
|
allowrequest(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &a, 0, "", preedit));
|
|
|
|
/* 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, &f.pump, 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(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &a, 0, "k", preedit));
|
|
|
|
ipcpackcap(frame, 1);
|
|
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keycap);
|
|
allowrequest(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
|
CT_EQ_STR(t, "", preedit);
|
|
ipcpackreq(frame, 1, 0, 'n');
|
|
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
CT_EQ_INT(t, 1, req.caret.valid);
|
|
CT_EQ_INT(t, -101, req.caret.x);
|
|
allowrequest(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
|
CT_EQ_STR(t, "ん", preedit);
|
|
|
|
disconnectclient(t, &f.pump, &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, &f.pump, Keypress);
|
|
bowner = req.owner;
|
|
CT_EQ_INT(t, 0, req.caret.valid);
|
|
allowrequest(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &b, 1, "", preedit));
|
|
CT_EQ_STR(t, "k", preedit);
|
|
|
|
disconnectclient(t, &f.pump, &b, bowner);
|
|
|
|
cleanup:
|
|
if(a.peer >= 0)
|
|
disconnectclient(t, &f.pump, &a, aowner);
|
|
if(b.peer >= 0)
|
|
disconnectclient(t, &f.pump, &b, bowner);
|
|
serverend(&f);
|
|
}
|
|
|
|
void
|
|
server_rejects_unknown_extension(struct ct *t)
|
|
{
|
|
Serverfix f;
|
|
Testclient badver, badop, good;
|
|
Keyreq req;
|
|
uchar frame[Ipccaretsz];
|
|
char preedit[Ipcfieldmax+1];
|
|
void *owner;
|
|
|
|
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;
|
|
serverbegin(&f, 3);
|
|
|
|
if(!startclient(t, &badver))
|
|
goto cleanup;
|
|
ipcpackcaret(frame, 1, 3, 4, 5);
|
|
frame[1]++;
|
|
if(!sendframe(t, &badver, frame, sizeof frame, 0))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keyrelease);
|
|
allowrequest(&f.pump);
|
|
waitclient(t, &badver);
|
|
close(badver.peer);
|
|
badver.peer = -1;
|
|
|
|
if(!startclient(t, &badop))
|
|
goto cleanup;
|
|
ipcpackcaret(frame, 1, 3, 4, 5);
|
|
frame[2]++;
|
|
if(!sendframe(t, &badop, frame, sizeof frame, 1))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keyrelease);
|
|
allowrequest(&f.pump);
|
|
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, &f.pump, Keypress);
|
|
owner = req.owner;
|
|
allowrequest(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &good, 1, "", preedit));
|
|
CT_EQ_STR(t, "k", preedit);
|
|
disconnectclient(t, &f.pump, &good, owner);
|
|
|
|
cleanup:
|
|
if(badver.peer >= 0)
|
|
disconnectclient(t, &f.pump, &badver, nil);
|
|
if(badop.peer >= 0)
|
|
disconnectclient(t, &f.pump, &badop, nil);
|
|
if(good.peer >= 0)
|
|
disconnectclient(t, &f.pump, &good, owner);
|
|
serverend(&f);
|
|
}
|