ipc: negotiate preedit capability and caret messages
This commit is contained in:
103
ipc.c
103
ipc.c
@@ -20,6 +20,32 @@ getlen(const unsigned char p[Ipclensz])
|
|||||||
return p[0] | (p[1] << 8);
|
return p[0] | (p[1] << 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
put32(unsigned char *p, int32_t v)
|
||||||
|
{
|
||||||
|
uint32_t u;
|
||||||
|
|
||||||
|
u = v;
|
||||||
|
p[0] = u;
|
||||||
|
p[1] = u >> 8;
|
||||||
|
p[2] = u >> 16;
|
||||||
|
p[3] = u >> 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t
|
||||||
|
get32(const unsigned char *p)
|
||||||
|
{
|
||||||
|
uint32_t u;
|
||||||
|
|
||||||
|
u = (uint32_t)p[0] |
|
||||||
|
((uint32_t)p[1] << 8) |
|
||||||
|
((uint32_t)p[2] << 16) |
|
||||||
|
((uint32_t)p[3] << 24);
|
||||||
|
if(u <= INT32_MAX)
|
||||||
|
return u;
|
||||||
|
return -(int32_t)(~u) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
ipcpath(char *dst, size_t cap)
|
ipcpath(char *dst, size_t cap)
|
||||||
{
|
{
|
||||||
@@ -82,6 +108,37 @@ ipcpackreset(unsigned char req[Ipcreqsz], int want)
|
|||||||
req[0] = Ipcreqreset | (want ? Ipcreqwant : 0);
|
req[0] = Ipcreqreset | (want ? Ipcreqwant : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* An old server sees this reserved frame as a key-zero request. */
|
||||||
|
void
|
||||||
|
ipcpackcap(unsigned char req[Ipcreqsz], int want)
|
||||||
|
{
|
||||||
|
memset(req, 0, Ipcreqsz);
|
||||||
|
req[0] = Ipcext | (want ? Ipcreqwant : 0);
|
||||||
|
req[1] = Ipcversion;
|
||||||
|
req[2] = Ipcopcap;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ipcpackcaret(unsigned char req[Ipccaretsz], int valid, int32_t x,
|
||||||
|
int32_t y, int32_t h)
|
||||||
|
{
|
||||||
|
if(valid != 0 && valid != 1)
|
||||||
|
return -1;
|
||||||
|
if(valid && h < 0)
|
||||||
|
return -1;
|
||||||
|
memset(req, 0, Ipccaretsz);
|
||||||
|
req[0] = Ipcext;
|
||||||
|
req[1] = Ipcversion;
|
||||||
|
req[2] = Ipcopcaret;
|
||||||
|
req[3] = valid;
|
||||||
|
if(!valid)
|
||||||
|
return 0;
|
||||||
|
put32(req + 4, x);
|
||||||
|
put32(req + 8, y);
|
||||||
|
put32(req + 12, h);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
||||||
uint32_t *key)
|
uint32_t *key)
|
||||||
@@ -94,6 +151,52 @@ ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod,
|
|||||||
((uint32_t)req[5] << 24);
|
((uint32_t)req[5] << 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ipcreqtype(const unsigned char req[Ipcreqsz])
|
||||||
|
{
|
||||||
|
if((req[0] & Ipcext) == 0)
|
||||||
|
return Ipclegacy;
|
||||||
|
if(req[1] != Ipcversion)
|
||||||
|
return Ipcunknown;
|
||||||
|
switch(req[2]){
|
||||||
|
case Ipcopcap:
|
||||||
|
if((req[0] & ~(Ipcext|Ipcreqwant)) != 0 ||
|
||||||
|
req[3] != 0 || req[4] != 0 || req[5] != 0)
|
||||||
|
return Ipcunknown;
|
||||||
|
return Ipccap;
|
||||||
|
case Ipcopcaret:
|
||||||
|
if(req[0] != Ipcext || req[3] > 1)
|
||||||
|
return Ipcunknown;
|
||||||
|
return Ipccaret;
|
||||||
|
}
|
||||||
|
return Ipcunknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ipcunpackcaret(const unsigned char req[Ipccaretsz], int *valid,
|
||||||
|
int32_t *x, int32_t *y, int32_t *h)
|
||||||
|
{
|
||||||
|
int32_t wireh;
|
||||||
|
|
||||||
|
if(valid == NULL || x == NULL || y == NULL || h == NULL ||
|
||||||
|
ipcreqtype(req) != Ipccaret)
|
||||||
|
return -1;
|
||||||
|
wireh = get32(req + 12);
|
||||||
|
if(wireh < 0)
|
||||||
|
return -1;
|
||||||
|
*valid = req[3];
|
||||||
|
if(!*valid){
|
||||||
|
*x = 0;
|
||||||
|
*y = 0;
|
||||||
|
*h = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*x = get32(req + 4);
|
||||||
|
*y = get32(req + 8);
|
||||||
|
*h = wireh;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
ipcreqreset(const unsigned char req[Ipcreqsz])
|
ipcreqreset(const unsigned char req[Ipcreqsz])
|
||||||
{
|
{
|
||||||
|
|||||||
23
ipc.h
23
ipc.h
@@ -2,7 +2,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Request: [flags, modifiers, key byte 0, ..., key byte 3].
|
* Legacy request: [flags, modifiers, key byte 0, ..., key byte 3].
|
||||||
* Flags request preedit and distinguish lifecycle reset from physical Escape.
|
* Flags request preedit and distinguish lifecycle reset from physical Escape.
|
||||||
* The server identifies the connection as the engine owner; that identity is
|
* The server identifies the connection as the engine owner; that identity is
|
||||||
* not sent on the wire.
|
* not sent on the wire.
|
||||||
@@ -11,12 +11,20 @@
|
|||||||
* followed, when requested, by
|
* followed, when requested, by
|
||||||
* [preedit-length-low, preedit-length-high, preedit...].
|
* [preedit-length-low, preedit-length-high, preedit...].
|
||||||
* Lengths are little-endian byte counts; fields are at most Ipcfieldmax bytes.
|
* Lengths are little-endian byte counts; fields are at most Ipcfieldmax bytes.
|
||||||
|
*
|
||||||
|
* Capability control is six bytes: [0x80|want, version, 0, 0, 0, 0].
|
||||||
|
* Caret control is sixteen bytes: [0x80, version, 1, valid, x, y, h],
|
||||||
|
* with the signed coordinates and height encoded as little-endian 32-bit words.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
Ipcreqwant = 1<<0,
|
Ipcreqwant = 1<<0,
|
||||||
Ipcreqreset = 1<<1,
|
Ipcreqreset = 1<<1,
|
||||||
|
Ipcext = 1<<7,
|
||||||
|
Ipcversion = 1,
|
||||||
|
Ipcopcap = 0,
|
||||||
|
Ipcopcaret = 1,
|
||||||
|
|
||||||
Kspec = 0x110000,
|
Kspec = 0x110000,
|
||||||
Kback = Kspec|0x08,
|
Kback = Kspec|0x08,
|
||||||
@@ -35,12 +43,21 @@ enum
|
|||||||
Mmask = Mshift|Mctrl|Malt|Msuper,
|
Mmask = Mshift|Mctrl|Malt|Msuper,
|
||||||
|
|
||||||
Ipcreqsz = 6,
|
Ipcreqsz = 6,
|
||||||
|
Ipccaretsz = 16,
|
||||||
Ipclensz = 2,
|
Ipclensz = 2,
|
||||||
Ipcresphdrsz = 1 + Ipclensz,
|
Ipcresphdrsz = 1 + Ipclensz,
|
||||||
Ipcfieldmax = 256,
|
Ipcfieldmax = 256,
|
||||||
Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax,
|
Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Ipcunknown = -1,
|
||||||
|
Ipclegacy,
|
||||||
|
Ipccap,
|
||||||
|
Ipccaret,
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct Ipcresp Ipcresp;
|
typedef struct Ipcresp Ipcresp;
|
||||||
struct Ipcresp
|
struct Ipcresp
|
||||||
{
|
{
|
||||||
@@ -51,7 +68,11 @@ struct Ipcresp
|
|||||||
|
|
||||||
void ipcpackreq(unsigned char[Ipcreqsz], int, uint32_t, uint32_t);
|
void ipcpackreq(unsigned char[Ipcreqsz], int, uint32_t, uint32_t);
|
||||||
void ipcpackreset(unsigned char[Ipcreqsz], int);
|
void ipcpackreset(unsigned char[Ipcreqsz], int);
|
||||||
|
void ipcpackcap(unsigned char[Ipcreqsz], int);
|
||||||
|
int ipcpackcaret(unsigned char[Ipccaretsz], int, int32_t, int32_t, int32_t);
|
||||||
void ipcunpackreq(const unsigned char[Ipcreqsz], int*, uint32_t*, uint32_t*);
|
void ipcunpackreq(const unsigned char[Ipcreqsz], int*, uint32_t*, uint32_t*);
|
||||||
|
int ipcunpackcaret(const unsigned char[Ipccaretsz], int*, int32_t*, int32_t*, int32_t*);
|
||||||
|
int ipcreqtype(const unsigned char[Ipcreqsz]);
|
||||||
int ipcreqreset(const unsigned char[Ipcreqsz]);
|
int ipcreqreset(const unsigned char[Ipcreqsz]);
|
||||||
int ipcpackresp(unsigned char*, size_t, int, const char*, size_t, const char*, size_t, int);
|
int ipcpackresp(unsigned char*, size_t, int, const char*, size_t, const char*, size_t, int);
|
||||||
int ipcreadn(int, void*, size_t);
|
int ipcreadn(int, void*, size_t);
|
||||||
|
|||||||
71
srv.c
71
srv.c
@@ -8,20 +8,46 @@ static char adir[256];
|
|||||||
static Channel *clientc;
|
static Channel *clientc;
|
||||||
|
|
||||||
static int
|
static int
|
||||||
srvreadkey(int fd, Keyreq *kr)
|
srvreadreq(int fd, Keyreq *kr, int *want)
|
||||||
{
|
{
|
||||||
uchar req[Ipcreqsz];
|
uchar req[Ipccaretsz];
|
||||||
|
int type, valid;
|
||||||
|
int32_t x, y, h;
|
||||||
u32int ks, mod;
|
u32int ks, mod;
|
||||||
int want;
|
|
||||||
|
|
||||||
if(ipcreadn(fd, req, sizeof req) < 0)
|
if(ipcreadn(fd, req, Ipcreqsz) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
ipcunpackreq(req, &want, &mod, &ks);
|
type = ipcreqtype(req);
|
||||||
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
|
switch(type){
|
||||||
kr->ks = ks;
|
case Ipclegacy:
|
||||||
kr->mod = mod;
|
ipcunpackreq(req, want, &mod, &ks);
|
||||||
memset(&kr->caret, 0, sizeof kr->caret);
|
kr->op = ipcreqreset(req) ? Keyreset : Keypress;
|
||||||
return want;
|
kr->ks = ks;
|
||||||
|
kr->mod = mod;
|
||||||
|
break;
|
||||||
|
case Ipccap:
|
||||||
|
*want = (req[0] & Ipcreqwant) != 0;
|
||||||
|
kr->op = Keycap;
|
||||||
|
kr->ks = 0;
|
||||||
|
kr->mod = 0;
|
||||||
|
break;
|
||||||
|
case Ipccaret:
|
||||||
|
if(ipcreadn(fd, req + Ipcreqsz, Ipccaretsz - Ipcreqsz) < 0 ||
|
||||||
|
ipcunpackcaret(req, &valid, &x, &y, &h) < 0)
|
||||||
|
return -1;
|
||||||
|
*want = 0;
|
||||||
|
kr->op = Keycaret;
|
||||||
|
kr->ks = 0;
|
||||||
|
kr->mod = 0;
|
||||||
|
kr->caret.valid = valid;
|
||||||
|
kr->caret.x = x;
|
||||||
|
kr->caret.y = y;
|
||||||
|
kr->caret.h = h;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -31,9 +57,10 @@ clientthread(void *arg)
|
|||||||
int fd;
|
int fd;
|
||||||
Keyreq kr;
|
Keyreq kr;
|
||||||
Keyres res;
|
Keyres res;
|
||||||
|
Caret caret;
|
||||||
uchar out[Ipcmaxresp];
|
uchar out[Ipcmaxresp];
|
||||||
char commit[Maxutf], preedit[Maxutf];
|
char commit[Maxutf], preedit[Maxutf];
|
||||||
int n, ncommit, npreedit, want;
|
int cap, n, ncommit, npreedit, type, want;
|
||||||
uchar token;
|
uchar token;
|
||||||
|
|
||||||
fd = (int)(uintptr)arg;
|
fd = (int)(uintptr)arg;
|
||||||
@@ -41,18 +68,34 @@ clientthread(void *arg)
|
|||||||
reply = chancreate(sizeof(Keyres), 0);
|
reply = chancreate(sizeof(Keyres), 0);
|
||||||
kr.reply = reply;
|
kr.reply = reply;
|
||||||
kr.owner = &fd;
|
kr.owner = &fd;
|
||||||
kr.cap = Cclientpreedit;
|
cap = Cclientpreedit;
|
||||||
while((want = srvreadkey(fd, &kr)) >= 0){
|
memset(&caret, 0, sizeof caret);
|
||||||
|
kr.cap = cap;
|
||||||
|
kr.caret = caret;
|
||||||
|
while((type = srvreadreq(fd, &kr, &want)) >= 0){
|
||||||
|
if(type == Ipccap)
|
||||||
|
cap = want ? Cclientpreedit : 0;
|
||||||
|
else if(type == Ipccaret)
|
||||||
|
caret = kr.caret;
|
||||||
|
kr.cap = cap;
|
||||||
|
kr.caret = caret;
|
||||||
chansend(keyc, &kr);
|
chansend(keyc, &kr);
|
||||||
chanrecv(reply, &res);
|
chanrecv(reply, &res);
|
||||||
|
if(type == Ipccaret)
|
||||||
|
continue;
|
||||||
ncommit = stoutf(&res.commit, commit, sizeof commit);
|
ncommit = stoutf(&res.commit, commit, sizeof commit);
|
||||||
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
|
npreedit = stoutf(&res.preedit, preedit, sizeof preedit);
|
||||||
n = ipcpackresp(out, sizeof out, res.eaten,
|
n = ipcpackresp(out, sizeof out,
|
||||||
|
type == Ipccap ? 1 : res.eaten,
|
||||||
commit, ncommit, preedit, npreedit, want);
|
commit, ncommit, preedit, npreedit, want);
|
||||||
if(n < 0 || ipcsend(fd, out, n) < 0)
|
if(n < 0 || ipcsend(fd, out, n) < 0)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
kr.op = Keyrelease;
|
kr.op = Keyrelease;
|
||||||
|
kr.cap = cap;
|
||||||
|
kr.caret = caret;
|
||||||
|
kr.ks = 0;
|
||||||
|
kr.mod = 0;
|
||||||
chansend(keyc, &kr);
|
chansend(keyc, &kr);
|
||||||
chanrecv(reply, &res);
|
chanrecv(reply, &res);
|
||||||
chanfree(reply);
|
chanfree(reply);
|
||||||
|
|||||||
@@ -27,6 +27,90 @@ ipc_masks_modifiers(struct ct *t)
|
|||||||
CT_CHECK(t, ipcreqreset(buf));
|
CT_CHECK(t, ipcreqreset(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ipc_control_and_caret_frames(struct ct *t)
|
||||||
|
{
|
||||||
|
static const uchar capon[Ipcreqsz] = {
|
||||||
|
Ipcext|Cclientpreedit, Ipcversion, Ipcopcap, 0, 0, 0,
|
||||||
|
};
|
||||||
|
static const uchar capoff[Ipcreqsz] = {
|
||||||
|
Ipcext, Ipcversion, Ipcopcap, 0, 0, 0,
|
||||||
|
};
|
||||||
|
static const uchar caret[Ipccaretsz] = {
|
||||||
|
Ipcext, Ipcversion, Ipcopcaret, 1,
|
||||||
|
0xfe, 0xff, 0xff, 0xff,
|
||||||
|
0x04, 0x03, 0x02, 0x01,
|
||||||
|
0x06, 0x05, 0x00, 0x00,
|
||||||
|
};
|
||||||
|
static const uchar nocaret[Ipccaretsz] = {
|
||||||
|
Ipcext, Ipcversion, Ipcopcaret, 0,
|
||||||
|
};
|
||||||
|
uchar buf[Ipccaretsz], bad[Ipccaretsz];
|
||||||
|
u32int key, mod;
|
||||||
|
int valid, want;
|
||||||
|
int32_t x, y, h;
|
||||||
|
|
||||||
|
ipcpackcap(buf, Cclientpreedit);
|
||||||
|
CT_EQ_MEM(t, capon, buf, sizeof capon);
|
||||||
|
CT_EQ_INT(t, Ipccap, ipcreqtype(buf));
|
||||||
|
/* The probe remains a harmless key-zero request to an old daemon. */
|
||||||
|
ipcunpackreq(buf, &want, &mod, &key);
|
||||||
|
CT_EQ_INT(t, 1, want);
|
||||||
|
CT_EQ_UINT(t, 0, key);
|
||||||
|
ipcpackcap(buf, 0);
|
||||||
|
CT_EQ_MEM(t, capoff, buf, sizeof capoff);
|
||||||
|
ipcunpackreq(buf, &want, &mod, &key);
|
||||||
|
CT_EQ_INT(t, 0, want);
|
||||||
|
CT_EQ_UINT(t, 0, key);
|
||||||
|
|
||||||
|
CT_EQ_INT(t, 0, ipcpackcaret(buf, 1, -2, 0x01020304, 0x506));
|
||||||
|
CT_EQ_MEM(t, caret, buf, sizeof caret);
|
||||||
|
CT_EQ_INT(t, Ipccaret, ipcreqtype(buf));
|
||||||
|
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h))){
|
||||||
|
CT_EQ_INT(t, 1, valid);
|
||||||
|
CT_EQ_INT(t, -2, x);
|
||||||
|
CT_EQ_INT(t, 0x01020304, y);
|
||||||
|
CT_EQ_INT(t, 0x506, h);
|
||||||
|
}
|
||||||
|
CT_EQ_INT(t, 0, ipcpackcaret(buf, 1, INT32_MIN, INT32_MAX, 0));
|
||||||
|
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h))){
|
||||||
|
CT_EQ_INT(t, INT32_MIN, x);
|
||||||
|
CT_EQ_INT(t, INT32_MAX, y);
|
||||||
|
CT_EQ_INT(t, 0, h);
|
||||||
|
}
|
||||||
|
CT_EQ_INT(t, 0, ipcpackcaret(buf, 0, -2, 3, 4));
|
||||||
|
CT_EQ_MEM(t, nocaret, buf, sizeof nocaret);
|
||||||
|
if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h)))
|
||||||
|
CT_EQ_INT(t, 0, valid);
|
||||||
|
CT_EQ_INT(t, -1, ipcpackcaret(buf, 1, 0, 0, -1));
|
||||||
|
|
||||||
|
memcpy(bad, caret, sizeof bad);
|
||||||
|
bad[1]++;
|
||||||
|
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
||||||
|
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
||||||
|
memcpy(bad, caret, sizeof bad);
|
||||||
|
bad[2]++;
|
||||||
|
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
||||||
|
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
||||||
|
memcpy(bad, caret, sizeof bad);
|
||||||
|
bad[3] = 2;
|
||||||
|
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
||||||
|
memcpy(bad, caret, sizeof bad);
|
||||||
|
bad[0] |= Ipcreqwant;
|
||||||
|
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
||||||
|
memcpy(bad, caret, sizeof bad);
|
||||||
|
bad[15] = 0x80;
|
||||||
|
CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h));
|
||||||
|
memcpy(bad, capon, sizeof capon);
|
||||||
|
bad[3] = 1;
|
||||||
|
CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad));
|
||||||
|
|
||||||
|
ipcpackreq(buf, 1, 0, 'a');
|
||||||
|
CT_EQ_INT(t, Ipclegacy, ipcreqtype(buf));
|
||||||
|
ipcpackreset(buf, 1);
|
||||||
|
CT_EQ_INT(t, Ipclegacy, ipcreqtype(buf));
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ipc_runtime_path(struct ct *t)
|
ipc_runtime_path(struct ct *t)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -142,17 +142,23 @@ sendreset(struct ct *t, Testclient *client, int want)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Keyreq
|
static Keyreq
|
||||||
nextrequest(struct ct *t, Enginegate *g, int op)
|
nextrequestcap(struct ct *t, Enginegate *g, int op, int cap)
|
||||||
{
|
{
|
||||||
Keyreq req;
|
Keyreq req;
|
||||||
|
|
||||||
memset(&req, 0, sizeof req);
|
memset(&req, 0, sizeof req);
|
||||||
chanrecv(g->seen, &req);
|
chanrecv(g->seen, &req);
|
||||||
CT_EQ_INT(t, op, req.op);
|
CT_EQ_INT(t, op, req.op);
|
||||||
CT_EQ_INT(t, Cclientpreedit, req.cap);
|
CT_EQ_INT(t, cap, req.cap);
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Keyreq
|
||||||
|
nextrequest(struct ct *t, Enginegate *g, int op)
|
||||||
|
{
|
||||||
|
return nextrequestcap(t, g, op, Cclientpreedit);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
allowrequest(Enginegate *g)
|
allowrequest(Enginegate *g)
|
||||||
{
|
{
|
||||||
@@ -175,6 +181,31 @@ readreply(struct ct *t, Testclient *client, int want, char *preedit, int npreedi
|
|||||||
return resp.eaten;
|
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
|
static void
|
||||||
waitclient(struct ct *t, Testclient *client)
|
waitclient(struct ct *t, Testclient *client)
|
||||||
{
|
{
|
||||||
@@ -369,3 +400,236 @@ cleanup:
|
|||||||
while(channbrecv(drawc, &dc) > 0)
|
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)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ void dictionary_candidates(struct ct*);
|
|||||||
void dictionary_misses_clear_result(struct ct*);
|
void dictionary_misses_clear_result(struct ct*);
|
||||||
void dictionary_emoji_identity(struct ct*);
|
void dictionary_emoji_identity(struct ct*);
|
||||||
void ipc_masks_modifiers(struct ct*);
|
void ipc_masks_modifiers(struct ct*);
|
||||||
|
void ipc_control_and_caret_frames(struct ct*);
|
||||||
void ipc_runtime_path(struct ct*);
|
void ipc_runtime_path(struct ct*);
|
||||||
void ipc_response_pack_boundaries(struct ct*);
|
void ipc_response_pack_boundaries(struct ct*);
|
||||||
void ipc_response_empty_and_preedit(struct ct*);
|
void ipc_response_empty_and_preedit(struct ct*);
|
||||||
@@ -73,6 +74,8 @@ void ipc_response_max_and_drain(struct ct*);
|
|||||||
void ipc_response_fragmented_and_truncated(struct ct*);
|
void ipc_response_fragmented_and_truncated(struct ct*);
|
||||||
void ipc_broken_peer_send(struct ct*);
|
void ipc_broken_peer_send(struct ct*);
|
||||||
void server_connection_ownership(struct ct*);
|
void server_connection_ownership(struct ct*);
|
||||||
|
void server_extension_stream(struct ct*);
|
||||||
|
void server_rejects_unknown_extension(struct ct*);
|
||||||
void ibus_machine_id_fallback(struct ct*);
|
void ibus_machine_id_fallback(struct ct*);
|
||||||
void ibus_startup_requires_ownership(struct ct*);
|
void ibus_startup_requires_ownership(struct ct*);
|
||||||
void ibus_context_lifecycle(struct ct*);
|
void ibus_context_lifecycle(struct ct*);
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ static const struct ct_test tests[] = {
|
|||||||
{ "dict/misses-clear-result", dictionary_misses_clear_result },
|
{ "dict/misses-clear-result", dictionary_misses_clear_result },
|
||||||
{ "dict/emoji-identity", dictionary_emoji_identity },
|
{ "dict/emoji-identity", dictionary_emoji_identity },
|
||||||
{ "ipc/masks-modifiers", ipc_masks_modifiers },
|
{ "ipc/masks-modifiers", ipc_masks_modifiers },
|
||||||
|
{ "ipc/control-caret-frames", ipc_control_and_caret_frames },
|
||||||
{ "ipc/runtime-path", ipc_runtime_path },
|
{ "ipc/runtime-path", ipc_runtime_path },
|
||||||
{ "ipc/response-pack-boundaries", ipc_response_pack_boundaries },
|
{ "ipc/response-pack-boundaries", ipc_response_pack_boundaries },
|
||||||
{ "ipc/response-empty-preedit", ipc_response_empty_and_preedit },
|
{ "ipc/response-empty-preedit", ipc_response_empty_and_preedit },
|
||||||
@@ -127,6 +128,8 @@ static const struct ct_test tests[] = {
|
|||||||
{ "ipc/response-fragmented-truncated", ipc_response_fragmented_and_truncated },
|
{ "ipc/response-fragmented-truncated", ipc_response_fragmented_and_truncated },
|
||||||
{ "ipc/broken-peer-send", ipc_broken_peer_send },
|
{ "ipc/broken-peer-send", ipc_broken_peer_send },
|
||||||
{ "server/connection-ownership", server_connection_ownership },
|
{ "server/connection-ownership", server_connection_ownership },
|
||||||
|
{ "server/extension-stream", server_extension_stream },
|
||||||
|
{ "server/rejects-unknown-extension", server_rejects_unknown_extension },
|
||||||
{ "ibus/machine-id-fallback", ibus_machine_id_fallback },
|
{ "ibus/machine-id-fallback", ibus_machine_id_fallback },
|
||||||
{ "ibus/startup-requires-ownership", ibus_startup_requires_ownership },
|
{ "ibus/startup-requires-ownership", ibus_startup_requires_ownership },
|
||||||
{ "ibus/context-lifecycle", ibus_context_lifecycle },
|
{ "ibus/context-lifecycle", ibus_context_lifecycle },
|
||||||
|
|||||||
Reference in New Issue
Block a user