Clicking elsewhere, changing focus, or any client reset dropped the composition in the GTK module and IBus (only XIM ResetIC handed it back), so typing 안녕 and clicking Send lost 녕. The engine now returns the pending text — a moved-to candidate first, as Enter would — on Keyreset and Keyrelease; the GTK module asks for it before closing on focus-out and commits it, IBus commits it on FocusOut, Reset and a switch to a password field, and XIM commits it on focus loss and hands it back on ResetIC without a separate capability probe.
346 lines
7.3 KiB
C
346 lines
7.3 KiB
C
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include "ipc.h"
|
|
#include "live.h"
|
|
|
|
enum
|
|
{
|
|
Maxclients = 64,
|
|
Cappreedit = 1,
|
|
};
|
|
|
|
typedef struct Response Response;
|
|
|
|
struct Response
|
|
{
|
|
int eaten;
|
|
char commit[Ipcfieldmax+1];
|
|
char preedit[Ipcfieldmax+1];
|
|
};
|
|
|
|
static size_t
|
|
getlen(unsigned char p[Ipclensz])
|
|
{
|
|
return p[0] | (p[1] << 8);
|
|
}
|
|
|
|
static int
|
|
readresponseuntil(int fd, int want, Response *res, int64_t deadline,
|
|
int quiet)
|
|
{
|
|
unsigned char hdr[Ipcresphdrsz], len[Ipclensz];
|
|
size_t n;
|
|
int rv;
|
|
|
|
memset(res, 0, sizeof *res);
|
|
rv = readuntil(fd, hdr, sizeof hdr, deadline);
|
|
if(rv <= 0){
|
|
if(!quiet)
|
|
fail("read response header: %s",
|
|
rv == 0 ? "peer closed" : strerror(errno));
|
|
return rv;
|
|
}
|
|
res->eaten = hdr[0] != 0;
|
|
n = getlen(hdr + 1);
|
|
if(n > Ipcfieldmax){
|
|
fail("invalid commit length %zu", n);
|
|
return -1;
|
|
}
|
|
if(n != 0){
|
|
rv = readuntil(fd, res->commit, n, deadline);
|
|
if(rv <= 0){
|
|
if(!quiet)
|
|
fail("read commit: %s",
|
|
rv == 0 ? "peer closed" : strerror(errno));
|
|
return rv;
|
|
}
|
|
}
|
|
res->commit[n] = '\0';
|
|
if(!want)
|
|
return 1;
|
|
rv = readuntil(fd, len, sizeof len, deadline);
|
|
if(rv <= 0){
|
|
if(!quiet)
|
|
fail("read preedit length: %s",
|
|
rv == 0 ? "peer closed" : strerror(errno));
|
|
return rv;
|
|
}
|
|
n = getlen(len);
|
|
if(n > Ipcfieldmax){
|
|
fail("invalid preedit length %zu", n);
|
|
return -1;
|
|
}
|
|
if(n != 0){
|
|
rv = readuntil(fd, res->preedit, n, deadline);
|
|
if(rv <= 0){
|
|
if(!quiet)
|
|
fail("read preedit: %s",
|
|
rv == 0 ? "peer closed" : strerror(errno));
|
|
return rv;
|
|
}
|
|
}
|
|
res->preedit[n] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
sendkey(int fd, int want, uint32_t mod, uint32_t key)
|
|
{
|
|
unsigned char req[Ipcreqsz];
|
|
|
|
ipcpackreq(req, want, mod, key);
|
|
return ipcsend(fd, req, sizeof req) == 0;
|
|
}
|
|
|
|
static int
|
|
sendreset(int fd, int want)
|
|
{
|
|
unsigned char req[Ipcreqsz];
|
|
|
|
ipcpackreset(req, want);
|
|
return ipcsend(fd, req, sizeof req) == 0;
|
|
}
|
|
|
|
static int
|
|
sendcap(int fd, int cap)
|
|
{
|
|
unsigned char req[Ipcreqsz];
|
|
|
|
ipcpackcap(req, cap);
|
|
return ipcsend(fd, req, sizeof req) == 0;
|
|
}
|
|
|
|
static int
|
|
expectresponse(int fd, int want, int eaten, char *commit, char *preedit,
|
|
char *where)
|
|
{
|
|
Response res;
|
|
|
|
if(readresponseuntil(fd, want, &res, nowms() + Calltimeout, 0) != 1)
|
|
return 0;
|
|
if(res.eaten != eaten || strcmp(res.commit, commit) != 0 ||
|
|
(want && strcmp(res.preedit, preedit) != 0))
|
|
return fail("%s: eaten=%d commit=%s preedit=%s",
|
|
where, res.eaten, res.commit, res.preedit);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
requestreset(int fd, int want, int eaten, char *commit, char *preedit,
|
|
char *where)
|
|
{
|
|
if(!sendreset(fd, want))
|
|
return fail("send %s: %s", where, strerror(errno));
|
|
return expectresponse(fd, want, eaten, commit, preedit, where);
|
|
}
|
|
|
|
static int
|
|
requestkey(int fd, int want, uint32_t mod, uint32_t key, int eaten,
|
|
char *commit, char *preedit, char *where)
|
|
{
|
|
if(!sendkey(fd, want, mod, key))
|
|
return fail("send %s: %s", where, strerror(errno));
|
|
return expectresponse(fd, want, eaten, commit, preedit, where);
|
|
}
|
|
|
|
static int
|
|
tryclient(char *path, int64_t deadline, int *client)
|
|
{
|
|
Response res;
|
|
int fd, rv;
|
|
|
|
fd = connectsocket(path, deadline);
|
|
if(fd < 0)
|
|
return errno == ETIMEDOUT ? -1 : 0;
|
|
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
rv = readresponseuntil(fd, 1, &res, deadline, 1);
|
|
if(rv != 1){
|
|
close(fd);
|
|
return rv < 0 && errno == ETIMEDOUT ? -1 : 0;
|
|
}
|
|
if(res.eaten || res.commit[0] != '\0' || res.preedit[0] != '\0'){
|
|
close(fd);
|
|
fail("replacement client returned invalid modifier response");
|
|
return -1;
|
|
}
|
|
*client = fd;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
waitslot(char *path, int *client, char *where)
|
|
{
|
|
int rv;
|
|
int64_t deadline;
|
|
|
|
deadline = nowms() + Calltimeout;
|
|
for(;;){
|
|
rv = tryclient(path, deadline, client);
|
|
if(rv > 0)
|
|
return 1;
|
|
if(rv < 0 || leftms(deadline) == 0)
|
|
return fail("%s: timed out waiting for a worker slot", where);
|
|
}
|
|
}
|
|
|
|
static int
|
|
overflowrejected(char *path)
|
|
{
|
|
struct pollfd pfd;
|
|
unsigned char byte;
|
|
ssize_t n;
|
|
int fd, timeout;
|
|
int64_t deadline;
|
|
|
|
deadline = nowms() + Calltimeout;
|
|
fd = connectsocket(path, deadline);
|
|
if(fd < 0)
|
|
return fail("overflow connect: %s", strerror(errno));
|
|
if(!sendkey(fd, 1, 0, Kmodfirst)){
|
|
close(fd);
|
|
return 1;
|
|
}
|
|
for(;;){
|
|
timeout = leftms(deadline);
|
|
if(timeout == 0){
|
|
close(fd);
|
|
return fail("overflow client was not rejected");
|
|
}
|
|
pfd.fd = fd;
|
|
pfd.events = POLLIN;
|
|
pfd.revents = 0;
|
|
n = poll(&pfd, 1, timeout);
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
if(n <= 0){
|
|
close(fd);
|
|
return fail("poll overflow client: %s",
|
|
n == 0 ? "timed out" : strerror(errno));
|
|
}
|
|
n = recv(fd, &byte, 1, 0);
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
if(n == 0 || (n < 0 && (errno == ECONNRESET || errno == EPIPE))){
|
|
close(fd);
|
|
return 1;
|
|
}
|
|
if(n < 0){
|
|
close(fd);
|
|
return fail("read overflow rejection: %s", strerror(errno));
|
|
}
|
|
close(fd);
|
|
return fail("overflow client received a response");
|
|
}
|
|
}
|
|
|
|
static int
|
|
runsmoke(char *path)
|
|
{
|
|
int client, ok;
|
|
|
|
client = connectsocket(path, nowms() + Calltimeout);
|
|
if(client < 0)
|
|
return fail("connect client: %s", strerror(errno));
|
|
ok = sendcap(client, Cappreedit) &&
|
|
expectresponse(client, 1, 1, "", "", "negotiate preedit") &&
|
|
requestkey(client, 1, Mctrl, 'n', 1, "", "",
|
|
"select Japanese") &&
|
|
requestkey(client, 1, 0, 'k', 1, "", "k", "preedit") &&
|
|
requestreset(client, 1, 1, "k", "", "reset");
|
|
close(client);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
runcapacity(char *path)
|
|
{
|
|
int client[Maxclients];
|
|
int i, ok;
|
|
|
|
for(i = 0; i < Maxclients; i++)
|
|
client[i] = -1;
|
|
ok = 0;
|
|
client[0] = connectsocket(path, nowms() + Calltimeout);
|
|
if(client[0] < 0){
|
|
fail("connect first client: %s", strerror(errno));
|
|
goto out;
|
|
}
|
|
if(!requestkey(client[0], 1, Mctrl, 'n', 1, "", "",
|
|
"select Japanese"))
|
|
goto out;
|
|
for(i = 1; i < Maxclients; i++){
|
|
client[i] = connectsocket(path, nowms() + Calltimeout);
|
|
if(client[i] < 0){
|
|
fail("connect capacity client %d: %s", i, strerror(errno));
|
|
goto out;
|
|
}
|
|
if(!requestkey(client[i], 1, 0, Kmodfirst, 0, "", "",
|
|
"capacity modifier"))
|
|
goto out;
|
|
}
|
|
if(!overflowrejected(path))
|
|
goto out;
|
|
close(client[Maxclients-1]);
|
|
client[Maxclients-1] = -1;
|
|
if(!waitslot(path, &client[Maxclients-1], "inactive slot recovery"))
|
|
goto out;
|
|
close(client[0]);
|
|
client[0] = -1;
|
|
if(!waitslot(path, &client[0], "active slot recovery") ||
|
|
!requestkey(client[0], 1, 0, 'a', 1, "", "あ",
|
|
"post-disconnect composition"))
|
|
goto out;
|
|
ok = 1;
|
|
out:
|
|
for(i = 0; i < Maxclients; i++)
|
|
if(client[i] >= 0)
|
|
close(client[i]);
|
|
return ok;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
Daemon daemon;
|
|
Live live;
|
|
int capacity, ok;
|
|
|
|
testname = "ipc_live_test";
|
|
capacity = argc == 4 && strcmp(argv[1], "--capacity") == 0;
|
|
if((!capacity && argc != 3) || (capacity && argc != 4)){
|
|
fprintf(stderr,
|
|
"usage: ipc_live_test [--capacity] strans mapdir\n");
|
|
return 2;
|
|
}
|
|
daemoninit(&daemon, "daemon");
|
|
ok = livesetup(&live, "ipc") &&
|
|
startdaemon(&live, &daemon, argv[1+capacity], argv[2+capacity]) &&
|
|
waitready(&live, &daemon, NULL, 0);
|
|
if(ok)
|
|
ok = capacity ? runcapacity(live.socket) : runsmoke(live.socket);
|
|
if(!ok)
|
|
showerrors(&daemon);
|
|
if(!stopdaemon(&daemon))
|
|
ok = 0;
|
|
if(!closeerrors(&daemon))
|
|
ok = 0;
|
|
if(!liveclean(&live))
|
|
ok = 0;
|
|
if(!ok)
|
|
return 1;
|
|
printf("ipc %s: ok\n", capacity ? "connection capacity" : "live smoke");
|
|
return 0;
|
|
}
|