1406 lines
39 KiB
C
1406 lines
39 KiB
C
#define _GNU_SOURCE
|
|
#include <dbus/dbus.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/inotify.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
enum
|
|
{
|
|
Maxconnections = 16,
|
|
Calltimeout = 4000,
|
|
Starttimeout = 8000,
|
|
Stoptimeout = 3000,
|
|
Relmask = 1<<30,
|
|
Ctrlmask = 1<<2,
|
|
Cappreedit = 1<<0,
|
|
Purposefree = 0,
|
|
Purposepassword = 8,
|
|
Purposepin = 9,
|
|
Hintprivate = 1<<11,
|
|
Hinthidden = 1<<12,
|
|
};
|
|
|
|
typedef struct Daemon Daemon;
|
|
typedef struct Siglog Siglog;
|
|
|
|
struct Daemon
|
|
{
|
|
pid_t pid;
|
|
int errfd;
|
|
char root[256];
|
|
char runtime[320];
|
|
char config[320];
|
|
char ibus[384];
|
|
char bus[448];
|
|
char home[320];
|
|
char socket[384];
|
|
char addrfile[512];
|
|
char address[512];
|
|
char err[4096];
|
|
size_t nerr;
|
|
};
|
|
|
|
struct Siglog
|
|
{
|
|
int invalid;
|
|
int npreedit;
|
|
int ncommit;
|
|
char preeditpath[96];
|
|
char commitpath[96];
|
|
char preedit[256];
|
|
char commit[256];
|
|
dbus_uint32_t cursor;
|
|
dbus_bool_t visible;
|
|
};
|
|
|
|
static int
|
|
fail(char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
fprintf(stderr, "ibus_live_test: ");
|
|
va_start(ap, fmt);
|
|
vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
fputc('\n', stderr);
|
|
return 0;
|
|
}
|
|
|
|
static int64_t
|
|
nowms(void)
|
|
{
|
|
struct timespec ts;
|
|
|
|
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
|
|
return 0;
|
|
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
}
|
|
|
|
static int
|
|
leftms(int64_t deadline)
|
|
{
|
|
int64_t n;
|
|
|
|
n = deadline - nowms();
|
|
if(n <= 0)
|
|
return 0;
|
|
if(n > 0x7fffffff)
|
|
return 0x7fffffff;
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
makedir(char *path)
|
|
{
|
|
if(mkdir(path, 0700) == 0)
|
|
return 1;
|
|
return fail("mkdir %s: %s", path, strerror(errno));
|
|
}
|
|
|
|
static void
|
|
readerrors(Daemon *d)
|
|
{
|
|
ssize_t n;
|
|
|
|
if(d->errfd < 0)
|
|
return;
|
|
while(d->nerr + 1 < sizeof d->err){
|
|
n = read(d->errfd, d->err + d->nerr,
|
|
sizeof d->err - d->nerr - 1);
|
|
if(n > 0){
|
|
d->nerr += n;
|
|
continue;
|
|
}
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
break;
|
|
}
|
|
d->err[d->nerr] = '\0';
|
|
}
|
|
|
|
static void
|
|
showerrors(Daemon *d)
|
|
{
|
|
readerrors(d);
|
|
if(d->nerr != 0)
|
|
fprintf(stderr, "ibus_live_test: daemon stderr:\n%s", d->err);
|
|
}
|
|
|
|
static int
|
|
readaddress(Daemon *d)
|
|
{
|
|
FILE *fp;
|
|
char line[768], *p;
|
|
long pid;
|
|
int haveaddr, havepid;
|
|
|
|
fp = fopen(d->addrfile, "r");
|
|
if(fp == NULL)
|
|
return fail("open %s: %s", d->addrfile, strerror(errno));
|
|
haveaddr = havepid = 0;
|
|
pid = -1;
|
|
while(fgets(line, sizeof line, fp) != NULL){
|
|
p = strchr(line, '\n');
|
|
if(p != NULL)
|
|
*p = '\0';
|
|
if(strncmp(line, "IBUS_ADDRESS=", 13) == 0){
|
|
if(snprintf(d->address, sizeof d->address, "%s", line + 13)
|
|
>= (int)sizeof d->address)
|
|
break;
|
|
haveaddr = d->address[0] != '\0';
|
|
}else if(strncmp(line, "IBUS_DAEMON_PID=", 16) == 0){
|
|
errno = 0;
|
|
pid = strtol(line + 16, &p, 10);
|
|
havepid = errno == 0 && *p == '\0';
|
|
}
|
|
}
|
|
if(fclose(fp) != 0)
|
|
return fail("close %s: %s", d->addrfile, strerror(errno));
|
|
if(!haveaddr || !havepid)
|
|
return fail("invalid IBus address file %s", d->addrfile);
|
|
if(pid != d->pid)
|
|
return fail("address file PID %ld, expected %ld", pid, (long)d->pid);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
waitaddress(Daemon *d, int notifyfd)
|
|
{
|
|
struct pollfd pfd[2];
|
|
char buf[4096];
|
|
struct inotify_event *ev;
|
|
ssize_t n;
|
|
int i, timeout, status;
|
|
int64_t deadline;
|
|
|
|
deadline = nowms() + Starttimeout;
|
|
for(;;){
|
|
if(waitpid(d->pid, &status, WNOHANG) == d->pid){
|
|
d->pid = -1;
|
|
readerrors(d);
|
|
return fail("daemon exited before publishing IBus address");
|
|
}
|
|
timeout = leftms(deadline);
|
|
if(timeout == 0)
|
|
return fail("timed out waiting for IBus address file");
|
|
pfd[0].fd = notifyfd;
|
|
pfd[0].events = POLLIN;
|
|
pfd[0].revents = 0;
|
|
pfd[1].fd = d->errfd;
|
|
pfd[1].events = POLLIN;
|
|
pfd[1].revents = 0;
|
|
i = poll(pfd, 2, timeout);
|
|
if(i < 0 && errno == EINTR)
|
|
continue;
|
|
if(i < 0)
|
|
return fail("poll for IBus address: %s", strerror(errno));
|
|
if(i == 0)
|
|
continue;
|
|
if(pfd[1].revents != 0)
|
|
readerrors(d);
|
|
if(!(pfd[0].revents & POLLIN))
|
|
continue;
|
|
n = read(notifyfd, buf, sizeof buf);
|
|
if(n < 0 && (errno == EINTR || errno == EAGAIN))
|
|
continue;
|
|
if(n < 0)
|
|
return fail("read IBus directory watch: %s", strerror(errno));
|
|
for(i = 0; i < n; i += sizeof *ev + ev->len){
|
|
ev = (struct inotify_event*)(buf + i);
|
|
if(!(ev->mask & IN_MOVED_TO) || ev->len == 0)
|
|
continue;
|
|
if(snprintf(d->addrfile, sizeof d->addrfile, "%s/%s",
|
|
d->bus, ev->name) >= (int)sizeof d->addrfile)
|
|
return fail("IBus address path is too long");
|
|
return readaddress(d);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
startdaemon(Daemon *d, char *program, char *mapdir)
|
|
{
|
|
int fd, notifyfd, watch, errpipe[2];
|
|
long maxfd;
|
|
pid_t pid;
|
|
|
|
memset(d, 0, sizeof *d);
|
|
d->pid = -1;
|
|
d->errfd = -1;
|
|
snprintf(d->root, sizeof d->root, "/tmp/strans-ibus.XXXXXX");
|
|
if(mkdtemp(d->root) == NULL)
|
|
return fail("mkdtemp: %s", strerror(errno));
|
|
if(snprintf(d->runtime, sizeof d->runtime, "%s/runtime", d->root)
|
|
>= (int)sizeof d->runtime ||
|
|
snprintf(d->config, sizeof d->config, "%s/config", d->root)
|
|
>= (int)sizeof d->config ||
|
|
snprintf(d->ibus, sizeof d->ibus, "%s/ibus", d->config)
|
|
>= (int)sizeof d->ibus ||
|
|
snprintf(d->bus, sizeof d->bus, "%s/bus", d->ibus)
|
|
>= (int)sizeof d->bus ||
|
|
snprintf(d->home, sizeof d->home, "%s/home", d->root)
|
|
>= (int)sizeof d->home ||
|
|
snprintf(d->socket, sizeof d->socket, "%s/strans.sock", d->runtime)
|
|
>= (int)sizeof d->socket)
|
|
return fail("temporary path is too long");
|
|
if(!makedir(d->runtime) || !makedir(d->config) || !makedir(d->ibus) ||
|
|
!makedir(d->bus) || !makedir(d->home))
|
|
return 0;
|
|
notifyfd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
|
|
if(notifyfd < 0)
|
|
return fail("inotify_init1: %s", strerror(errno));
|
|
watch = inotify_add_watch(notifyfd, d->bus, IN_MOVED_TO);
|
|
if(watch < 0){
|
|
close(notifyfd);
|
|
return fail("inotify_add_watch: %s", strerror(errno));
|
|
}
|
|
if(pipe2(errpipe, O_CLOEXEC|O_NONBLOCK) < 0){
|
|
close(notifyfd);
|
|
return fail("pipe2: %s", strerror(errno));
|
|
}
|
|
pid = fork();
|
|
if(pid < 0){
|
|
close(errpipe[0]);
|
|
close(errpipe[1]);
|
|
close(notifyfd);
|
|
return fail("fork: %s", strerror(errno));
|
|
}
|
|
if(pid == 0){
|
|
close(errpipe[0]);
|
|
close(notifyfd);
|
|
if(dup2(errpipe[1], STDOUT_FILENO) < 0 ||
|
|
dup2(errpipe[1], STDERR_FILENO) < 0)
|
|
_exit(126);
|
|
close(errpipe[1]);
|
|
if(close_range(3, UINT_MAX, 0) < 0){
|
|
maxfd = sysconf(_SC_OPEN_MAX);
|
|
if(maxfd < 0)
|
|
maxfd = 1024;
|
|
for(fd = 3; fd < maxfd; fd++)
|
|
close(fd);
|
|
}
|
|
if(setenv("XDG_RUNTIME_DIR", d->runtime, 1) < 0 ||
|
|
setenv("XDG_CONFIG_HOME", d->config, 1) < 0 ||
|
|
setenv("HOME", d->home, 1) < 0 || unsetenv("DISPLAY") < 0 ||
|
|
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
|
unsetenv("IBUS_ADDRESS") < 0){
|
|
dprintf(STDERR_FILENO, "set daemon environment: %s\n",
|
|
strerror(errno));
|
|
_exit(126);
|
|
}
|
|
execl(program, program, mapdir, (char*)0);
|
|
dprintf(STDERR_FILENO, "exec %s: %s\n", program, strerror(errno));
|
|
_exit(127);
|
|
}
|
|
close(errpipe[1]);
|
|
d->pid = pid;
|
|
d->errfd = errpipe[0];
|
|
if(!waitaddress(d, notifyfd)){
|
|
inotify_rm_watch(notifyfd, watch);
|
|
close(notifyfd);
|
|
return 0;
|
|
}
|
|
inotify_rm_watch(notifyfd, watch);
|
|
close(notifyfd);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
clearbus(Daemon *d)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *de;
|
|
char path[576];
|
|
int ok;
|
|
|
|
if(d->bus[0] == '\0')
|
|
return 1;
|
|
dir = opendir(d->bus);
|
|
if(dir == NULL)
|
|
return errno == ENOENT || fail("open cleanup directory %s: %s",
|
|
d->bus, strerror(errno));
|
|
ok = 1;
|
|
errno = 0;
|
|
while((de = readdir(dir)) != NULL){
|
|
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
|
continue;
|
|
if(snprintf(path, sizeof path, "%s/%s", d->bus, de->d_name)
|
|
>= (int)sizeof path){
|
|
fail("cleanup path is too long: %s", de->d_name);
|
|
ok = 0;
|
|
continue;
|
|
}
|
|
if(unlink(path) < 0 && errno != ENOENT){
|
|
fail("remove IBus address file %s: %s", de->d_name,
|
|
strerror(errno));
|
|
ok = 0;
|
|
}
|
|
errno = 0;
|
|
}
|
|
if(errno != 0){
|
|
fail("read cleanup directory %s: %s", d->bus, strerror(errno));
|
|
ok = 0;
|
|
}
|
|
closedir(dir);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
rmdirknown(char *path)
|
|
{
|
|
if(path[0] == '\0' || rmdir(path) == 0 || errno == ENOENT)
|
|
return 1;
|
|
return fail("rmdir %s: %s", path, strerror(errno));
|
|
}
|
|
|
|
static int
|
|
killdaemon(Daemon *d, int *status)
|
|
{
|
|
int n, ok;
|
|
|
|
ok = 1;
|
|
if(kill(d->pid, SIGKILL) < 0 && errno != ESRCH){
|
|
fail("kill -9 %ld: %s", (long)d->pid, strerror(errno));
|
|
ok = 0;
|
|
}
|
|
do
|
|
n = waitpid(d->pid, status, 0);
|
|
while(n < 0 && errno == EINTR);
|
|
if(n != d->pid){
|
|
fail("reap %ld after SIGKILL: %s", (long)d->pid,
|
|
n < 0 ? strerror(errno) : "wrong child");
|
|
ok = 0;
|
|
}else
|
|
d->pid = -1;
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
stopdaemon(Daemon *d)
|
|
{
|
|
struct pollfd pfd;
|
|
int ok, status, n, reaped;
|
|
int64_t deadline;
|
|
|
|
ok = 1;
|
|
if(d->pid > 0){
|
|
do
|
|
n = waitpid(d->pid, &status, WNOHANG);
|
|
while(n < 0 && errno == EINTR);
|
|
if(n == d->pid){
|
|
fail("daemon exited before test termination with wait status %#x",
|
|
status);
|
|
d->pid = -1;
|
|
ok = 0;
|
|
}else if(n < 0){
|
|
fail("check daemon %ld before termination: %s",
|
|
(long)d->pid, strerror(errno));
|
|
if(errno == ECHILD)
|
|
d->pid = -1;
|
|
ok = 0;
|
|
}
|
|
}
|
|
if(d->pid > 0){
|
|
if(kill(d->pid, SIGTERM) < 0){
|
|
fail("kill %ld: %s", (long)d->pid, strerror(errno));
|
|
ok = 0;
|
|
}
|
|
reaped = 0;
|
|
deadline = nowms() + Stoptimeout;
|
|
for(;;){
|
|
n = waitpid(d->pid, &status, WNOHANG);
|
|
if(n == d->pid){
|
|
reaped = 1;
|
|
d->pid = -1;
|
|
break;
|
|
}
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
if(n < 0){
|
|
fail("waitpid %ld: %s", (long)d->pid, strerror(errno));
|
|
ok = 0;
|
|
if(errno == ECHILD)
|
|
d->pid = -1;
|
|
break;
|
|
}
|
|
n = leftms(deadline);
|
|
if(n == 0){
|
|
fail("daemon %ld did not stop after SIGTERM", (long)d->pid);
|
|
ok = 0;
|
|
if(!killdaemon(d, &status))
|
|
ok = 0;
|
|
break;
|
|
}
|
|
pfd.fd = d->errfd;
|
|
pfd.events = POLLIN|POLLHUP;
|
|
pfd.revents = 0;
|
|
if(poll(&pfd, 1, n) < 0 && errno != EINTR){
|
|
fail("poll daemon %ld: %s", (long)d->pid, strerror(errno));
|
|
ok = 0;
|
|
if(!killdaemon(d, &status))
|
|
ok = 0;
|
|
break;
|
|
}
|
|
readerrors(d);
|
|
}
|
|
/* plan9port turns a caught termination note into exit status 1. */
|
|
if(reaped &&
|
|
!((WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) ||
|
|
(WIFEXITED(status) && WEXITSTATUS(status) == 1))){
|
|
fail("daemon exited with unexpected wait status %#x", status);
|
|
ok = 0;
|
|
}
|
|
}
|
|
readerrors(d);
|
|
if(d->errfd >= 0){
|
|
close(d->errfd);
|
|
d->errfd = -1;
|
|
}
|
|
if(d->socket[0] != '\0' && unlink(d->socket) < 0 && errno != ENOENT){
|
|
fail("remove IPC socket %s: %s", d->socket, strerror(errno));
|
|
ok = 0;
|
|
}
|
|
if(!clearbus(d)) ok = 0;
|
|
if(!rmdirknown(d->bus)) ok = 0;
|
|
if(!rmdirknown(d->ibus)) ok = 0;
|
|
if(!rmdirknown(d->config)) ok = 0;
|
|
if(!rmdirknown(d->runtime)) ok = 0;
|
|
if(!rmdirknown(d->home)) ok = 0;
|
|
if(!rmdirknown(d->root)) ok = 0;
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
emptyarray(DBusMessageIter *it, int element)
|
|
{
|
|
DBusMessageIter sub;
|
|
|
|
if(dbus_message_iter_get_arg_type(it) != DBUS_TYPE_ARRAY ||
|
|
dbus_message_iter_get_element_type(it) != element)
|
|
return 0;
|
|
dbus_message_iter_recurse(it, &sub);
|
|
return dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_INVALID;
|
|
}
|
|
|
|
static int
|
|
ibusattrs(DBusMessageIter *it)
|
|
{
|
|
DBusMessageIter v, st;
|
|
const char *name;
|
|
|
|
if(dbus_message_iter_get_arg_type(it) != DBUS_TYPE_VARIANT)
|
|
return 0;
|
|
dbus_message_iter_recurse(it, &v);
|
|
if(dbus_message_iter_get_arg_type(&v) != DBUS_TYPE_STRUCT)
|
|
return 0;
|
|
dbus_message_iter_recurse(&v, &st);
|
|
if(dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_STRING)
|
|
return 0;
|
|
dbus_message_iter_get_basic(&st, &name);
|
|
if(strcmp(name, "IBusAttrList") != 0 || !dbus_message_iter_next(&st) ||
|
|
!emptyarray(&st, DBUS_TYPE_DICT_ENTRY) ||
|
|
!dbus_message_iter_next(&st) || !emptyarray(&st, DBUS_TYPE_VARIANT) ||
|
|
dbus_message_iter_next(&st))
|
|
return 0;
|
|
return !dbus_message_iter_next(&v);
|
|
}
|
|
|
|
static int
|
|
ibustext(DBusMessageIter *it, char *text, size_t ntext)
|
|
{
|
|
DBusMessageIter v, st;
|
|
const char *name, *s;
|
|
|
|
if(dbus_message_iter_get_arg_type(it) != DBUS_TYPE_VARIANT)
|
|
return 0;
|
|
dbus_message_iter_recurse(it, &v);
|
|
if(dbus_message_iter_get_arg_type(&v) != DBUS_TYPE_STRUCT)
|
|
return 0;
|
|
dbus_message_iter_recurse(&v, &st);
|
|
if(dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_STRING)
|
|
return 0;
|
|
dbus_message_iter_get_basic(&st, &name);
|
|
if(strcmp(name, "IBusText") != 0 || !dbus_message_iter_next(&st) ||
|
|
!emptyarray(&st, DBUS_TYPE_DICT_ENTRY) || !dbus_message_iter_next(&st) ||
|
|
dbus_message_iter_get_arg_type(&st) != DBUS_TYPE_STRING)
|
|
return 0;
|
|
dbus_message_iter_get_basic(&st, &s);
|
|
if(snprintf(text, ntext, "%s", s) >= (int)ntext ||
|
|
!dbus_message_iter_next(&st) || !ibusattrs(&st) ||
|
|
dbus_message_iter_next(&st))
|
|
return 0;
|
|
return !dbus_message_iter_next(&v);
|
|
}
|
|
|
|
static DBusHandlerResult
|
|
onsignal(DBusConnection *conn, DBusMessage *m, void *arg)
|
|
{
|
|
Siglog *log;
|
|
DBusMessageIter it;
|
|
const char *path;
|
|
|
|
(void)conn;
|
|
log = arg;
|
|
if(dbus_message_get_type(m) != DBUS_MESSAGE_TYPE_SIGNAL ||
|
|
!dbus_message_has_interface(m, "org.freedesktop.IBus.InputContext"))
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
if(dbus_message_has_member(m, "UpdatePreeditText")){
|
|
log->npreedit++;
|
|
path = dbus_message_get_path(m);
|
|
if(path == NULL || snprintf(log->preeditpath,
|
|
sizeof log->preeditpath, "%s", path) >=
|
|
(int)sizeof log->preeditpath ||
|
|
!dbus_message_has_signature(m, "vub") ||
|
|
!dbus_message_iter_init(m, &it) ||
|
|
!ibustext(&it, log->preedit, sizeof log->preedit) ||
|
|
!dbus_message_iter_next(&it) ||
|
|
dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_UINT32){
|
|
log->invalid = 1;
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
dbus_message_iter_get_basic(&it, &log->cursor);
|
|
if(!dbus_message_iter_next(&it) ||
|
|
dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_BOOLEAN){
|
|
log->invalid = 1;
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
dbus_message_iter_get_basic(&it, &log->visible);
|
|
if(dbus_message_iter_next(&it))
|
|
log->invalid = 1;
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
if(dbus_message_has_member(m, "CommitText")){
|
|
log->ncommit++;
|
|
path = dbus_message_get_path(m);
|
|
if(path == NULL || snprintf(log->commitpath,
|
|
sizeof log->commitpath, "%s", path) >=
|
|
(int)sizeof log->commitpath ||
|
|
!dbus_message_has_signature(m, "v") ||
|
|
!dbus_message_iter_init(m, &it) ||
|
|
!ibustext(&it, log->commit, sizeof log->commit) ||
|
|
dbus_message_iter_next(&it))
|
|
log->invalid = 1;
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
|
|
static void
|
|
resetsig(Siglog *log)
|
|
{
|
|
memset(log, 0, sizeof *log);
|
|
}
|
|
|
|
static DBusConnection*
|
|
openbus(char *address, Siglog *log)
|
|
{
|
|
DBusConnection *conn;
|
|
DBusError err;
|
|
|
|
dbus_error_init(&err);
|
|
conn = dbus_connection_open_private(address, &err);
|
|
if(conn == NULL){
|
|
fail("connect to private IBus address: %s", err.message);
|
|
dbus_error_free(&err);
|
|
return NULL;
|
|
}
|
|
dbus_connection_set_exit_on_disconnect(conn, FALSE);
|
|
if(!dbus_connection_add_filter(conn, onsignal, log, NULL)){
|
|
fail("add D-Bus signal filter: out of memory");
|
|
dbus_connection_close(conn);
|
|
dbus_connection_unref(conn);
|
|
return NULL;
|
|
}
|
|
return conn;
|
|
}
|
|
|
|
static void
|
|
closebus(DBusConnection **conn, Siglog *log)
|
|
{
|
|
if(*conn == NULL)
|
|
return;
|
|
dbus_connection_remove_filter(*conn, onsignal, log);
|
|
dbus_connection_close(*conn);
|
|
dbus_connection_unref(*conn);
|
|
*conn = NULL;
|
|
}
|
|
|
|
static int
|
|
overflowrejected(char *address)
|
|
{
|
|
DBusConnection *conn;
|
|
DBusError err;
|
|
struct pollfd pfd;
|
|
int fd, n, ok, timeout;
|
|
int64_t deadline;
|
|
|
|
dbus_error_init(&err);
|
|
conn = dbus_connection_open_private(address, &err);
|
|
if(conn == NULL){
|
|
if(!dbus_error_is_set(&err)){
|
|
dbus_error_free(&err);
|
|
return fail("overflow connection failed without a D-Bus error");
|
|
}
|
|
dbus_error_free(&err);
|
|
return 1;
|
|
}
|
|
dbus_error_free(&err);
|
|
dbus_connection_set_exit_on_disconnect(conn, FALSE);
|
|
if(!dbus_connection_get_unix_fd(conn, &fd)){
|
|
dbus_connection_close(conn);
|
|
dbus_connection_unref(conn);
|
|
return fail("overflow connection has no Unix descriptor");
|
|
}
|
|
ok = 0;
|
|
deadline = nowms() + Calltimeout;
|
|
for(;;){
|
|
if(!dbus_connection_get_is_connected(conn)){
|
|
ok = 1;
|
|
break;
|
|
}
|
|
timeout = leftms(deadline);
|
|
if(timeout == 0){
|
|
fail("overflow IBus connection was not rejected");
|
|
break;
|
|
}
|
|
pfd.fd = fd;
|
|
pfd.events = POLLIN;
|
|
pfd.revents = 0;
|
|
n = poll(&pfd, 1, timeout);
|
|
if(n < 0 && errno == EINTR)
|
|
continue;
|
|
if(n < 0){
|
|
fail("poll overflow IBus connection: %s", strerror(errno));
|
|
break;
|
|
}
|
|
if(n == 0)
|
|
continue;
|
|
if(pfd.revents & POLLNVAL){
|
|
fail("overflow IBus descriptor became invalid");
|
|
break;
|
|
}
|
|
dbus_connection_read_write_dispatch(conn, 0);
|
|
}
|
|
dbus_connection_close(conn);
|
|
dbus_connection_unref(conn);
|
|
return ok;
|
|
}
|
|
|
|
static DBusMessage*
|
|
method(char *path, char *iface, char *member)
|
|
{
|
|
DBusMessage *m;
|
|
|
|
m = dbus_message_new_method_call("org.freedesktop.IBus", path,
|
|
iface, member);
|
|
if(m == NULL)
|
|
fail("allocate %s call: out of memory", member);
|
|
return m;
|
|
}
|
|
|
|
static DBusMessage*
|
|
sendcall(DBusConnection *conn, DBusMessage *m)
|
|
{
|
|
DBusMessage *reply;
|
|
DBusPendingCall *pending;
|
|
char member[64], path[128];
|
|
const char *s;
|
|
int timeout;
|
|
int64_t deadline;
|
|
|
|
if(m == NULL)
|
|
return NULL;
|
|
s = dbus_message_get_member(m);
|
|
snprintf(member, sizeof member, "%s", s != NULL ? s : "method");
|
|
s = dbus_message_get_path(m);
|
|
snprintf(path, sizeof path, "%s", s != NULL ? s : "path");
|
|
pending = NULL;
|
|
if(!dbus_connection_send_with_reply(conn, m, &pending, Calltimeout) ||
|
|
pending == NULL){
|
|
dbus_message_unref(m);
|
|
fail("queue %s on %s: out of memory", member, path);
|
|
return NULL;
|
|
}
|
|
dbus_message_unref(m);
|
|
deadline = nowms() + Calltimeout;
|
|
while(!dbus_pending_call_get_completed(pending)){
|
|
timeout = leftms(deadline);
|
|
if(timeout == 0){
|
|
fail("timed out waiting for %s on %s", member, path);
|
|
goto fail;
|
|
}
|
|
if(!dbus_connection_read_write_dispatch(conn, timeout)){
|
|
fail("connection closed waiting for %s on %s", member, path);
|
|
goto fail;
|
|
}
|
|
}
|
|
reply = dbus_pending_call_steal_reply(pending);
|
|
dbus_pending_call_unref(pending);
|
|
if(reply == NULL){
|
|
fail("%s on %s completed without a reply", member, path);
|
|
return NULL;
|
|
}
|
|
while(dbus_connection_get_dispatch_status(conn) == DBUS_DISPATCH_DATA_REMAINS)
|
|
dbus_connection_dispatch(conn);
|
|
return reply;
|
|
fail:
|
|
dbus_pending_call_cancel(pending);
|
|
dbus_pending_call_unref(pending);
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
hello(DBusConnection *conn, char *name, size_t nname)
|
|
{
|
|
DBusMessage *m, *reply;
|
|
DBusError err;
|
|
const char *s;
|
|
|
|
m = dbus_message_new_method_call("org.freedesktop.DBus",
|
|
"/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello");
|
|
reply = sendcall(conn, m);
|
|
if(reply == NULL)
|
|
return 0;
|
|
dbus_error_init(&err);
|
|
if(!dbus_message_has_signature(reply, "s") ||
|
|
!dbus_message_get_args(reply, &err, DBUS_TYPE_STRING, &s,
|
|
DBUS_TYPE_INVALID)){
|
|
dbus_message_unref(reply);
|
|
dbus_error_free(&err);
|
|
return fail("Hello returned an invalid reply");
|
|
}
|
|
if(snprintf(name, nname, "%s", s) >= (int)nname){
|
|
dbus_message_unref(reply);
|
|
return fail("Hello name is too long");
|
|
}
|
|
dbus_message_unref(reply);
|
|
return name[0] == ':' || fail("Hello returned invalid name %s", name);
|
|
}
|
|
|
|
static int
|
|
createcontext(DBusConnection *conn, char *path, size_t npath)
|
|
{
|
|
DBusMessage *m, *reply;
|
|
DBusError err;
|
|
const char *client, *p;
|
|
|
|
client = "ibus-live-test";
|
|
m = method("/org/freedesktop/IBus", "org.freedesktop.IBus",
|
|
"CreateInputContext");
|
|
if(m == NULL || !dbus_message_append_args(m, DBUS_TYPE_STRING, &client,
|
|
DBUS_TYPE_INVALID)){
|
|
if(m != NULL) dbus_message_unref(m);
|
|
return fail("build CreateInputContext call");
|
|
}
|
|
reply = sendcall(conn, m);
|
|
if(reply == NULL)
|
|
return 0;
|
|
dbus_error_init(&err);
|
|
if(!dbus_message_has_signature(reply, "o") ||
|
|
!dbus_message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, &p,
|
|
DBUS_TYPE_INVALID)){
|
|
dbus_message_unref(reply);
|
|
dbus_error_free(&err);
|
|
return fail("CreateInputContext returned an invalid reply");
|
|
}
|
|
if(snprintf(path, npath, "%s", p) >= (int)npath){
|
|
dbus_message_unref(reply);
|
|
return fail("input context path is too long");
|
|
}
|
|
dbus_message_unref(reply);
|
|
return path[0] == '/' || fail("invalid input context path %s", path);
|
|
}
|
|
|
|
static int
|
|
emptycall(DBusConnection *conn, Siglog *log, char *path, char *member)
|
|
{
|
|
DBusMessage *reply;
|
|
|
|
resetsig(log);
|
|
reply = sendcall(conn, method(path,
|
|
"org.freedesktop.IBus.InputContext", member));
|
|
if(reply == NULL)
|
|
return 0;
|
|
if(!dbus_message_has_signature(reply, "")){
|
|
dbus_message_unref(reply);
|
|
return fail("%s returned a nonempty reply", member);
|
|
}
|
|
dbus_message_unref(reply);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
capcall(DBusConnection *conn, Siglog *log, char *path, dbus_uint32_t cap)
|
|
{
|
|
DBusMessage *m, *reply;
|
|
|
|
resetsig(log);
|
|
m = method(path, "org.freedesktop.IBus.InputContext", "SetCapabilities");
|
|
if(m == NULL || !dbus_message_append_args(m, DBUS_TYPE_UINT32, &cap,
|
|
DBUS_TYPE_INVALID)){
|
|
if(m != NULL) dbus_message_unref(m);
|
|
return fail("build SetCapabilities call");
|
|
}
|
|
reply = sendcall(conn, m);
|
|
if(reply == NULL)
|
|
return 0;
|
|
if(!dbus_message_has_signature(reply, "")){
|
|
dbus_message_unref(reply);
|
|
return fail("SetCapabilities returned a nonempty reply");
|
|
}
|
|
dbus_message_unref(reply);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
contentcall(DBusConnection *conn, Siglog *log, char *path,
|
|
dbus_uint32_t purpose, dbus_uint32_t hints)
|
|
{
|
|
DBusMessage *m, *reply;
|
|
DBusMessageIter it, v, st;
|
|
const char *iface, *property;
|
|
|
|
resetsig(log);
|
|
iface = "org.freedesktop.IBus.InputContext";
|
|
property = "ContentType";
|
|
m = method(path, "org.freedesktop.DBus.Properties", "Set");
|
|
if(m == NULL)
|
|
return 0;
|
|
dbus_message_iter_init_append(m, &it);
|
|
if(!dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &iface) ||
|
|
!dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &property) ||
|
|
!dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, "(uu)", &v) ||
|
|
!dbus_message_iter_open_container(&v, DBUS_TYPE_STRUCT, NULL, &st) ||
|
|
!dbus_message_iter_append_basic(&st, DBUS_TYPE_UINT32, &purpose) ||
|
|
!dbus_message_iter_append_basic(&st, DBUS_TYPE_UINT32, &hints) ||
|
|
!dbus_message_iter_close_container(&v, &st) ||
|
|
!dbus_message_iter_close_container(&it, &v)){
|
|
dbus_message_unref(m);
|
|
return fail("build Properties.Set ContentType call");
|
|
}
|
|
reply = sendcall(conn, m);
|
|
if(reply == NULL)
|
|
return 0;
|
|
if(!dbus_message_has_signature(reply, "")){
|
|
dbus_message_unref(reply);
|
|
return fail("Properties.Set ContentType returned a nonempty reply");
|
|
}
|
|
dbus_message_unref(reply);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
keycall(DBusConnection *conn, Siglog *log, char *path, dbus_uint32_t sym,
|
|
dbus_uint32_t state, int *eaten)
|
|
{
|
|
DBusMessage *m, *reply;
|
|
DBusError err;
|
|
dbus_uint32_t code;
|
|
dbus_bool_t b;
|
|
|
|
resetsig(log);
|
|
code = 0;
|
|
m = method(path, "org.freedesktop.IBus.InputContext", "ProcessKeyEvent");
|
|
if(m == NULL || !dbus_message_append_args(m,
|
|
DBUS_TYPE_UINT32, &sym, DBUS_TYPE_UINT32, &code,
|
|
DBUS_TYPE_UINT32, &state, DBUS_TYPE_INVALID)){
|
|
if(m != NULL) dbus_message_unref(m);
|
|
return fail("build ProcessKeyEvent call");
|
|
}
|
|
reply = sendcall(conn, m);
|
|
if(reply == NULL)
|
|
return 0;
|
|
dbus_error_init(&err);
|
|
if(!dbus_message_has_signature(reply, "b") ||
|
|
!dbus_message_get_args(reply, &err, DBUS_TYPE_BOOLEAN, &b,
|
|
DBUS_TYPE_INVALID)){
|
|
dbus_message_unref(reply);
|
|
dbus_error_free(&err);
|
|
return fail("ProcessKeyEvent returned an invalid reply");
|
|
}
|
|
*eaten = b != FALSE;
|
|
dbus_message_unref(reply);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
cursorcall(DBusConnection *conn, Siglog *log, char *path,
|
|
int x, int y, int w, int h)
|
|
{
|
|
DBusMessage *m, *reply;
|
|
dbus_int32_t dx, dy, dw, dh;
|
|
|
|
resetsig(log);
|
|
dx = x;
|
|
dy = y;
|
|
dw = w;
|
|
dh = h;
|
|
m = method(path, "org.freedesktop.IBus.InputContext",
|
|
"SetCursorLocation");
|
|
if(m == NULL || !dbus_message_append_args(m,
|
|
DBUS_TYPE_INT32, &dx, DBUS_TYPE_INT32, &dy,
|
|
DBUS_TYPE_INT32, &dw, DBUS_TYPE_INT32, &dh,
|
|
DBUS_TYPE_INVALID)){
|
|
if(m != NULL) dbus_message_unref(m);
|
|
return fail("build SetCursorLocation call");
|
|
}
|
|
reply = sendcall(conn, m);
|
|
if(reply == NULL)
|
|
return 0;
|
|
if(!dbus_message_has_signature(reply, "")){
|
|
dbus_message_unref(reply);
|
|
return fail("SetCursorLocation returned a nonempty reply");
|
|
}
|
|
dbus_message_unref(reply);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
unknowncall(DBusConnection *conn, char *path, char *member, char *error)
|
|
{
|
|
DBusMessage *m, *reply;
|
|
DBusError err;
|
|
|
|
m = method(path, "org.freedesktop.IBus.InputContext", member);
|
|
if(m == NULL)
|
|
return 0;
|
|
dbus_error_init(&err);
|
|
reply = dbus_connection_send_with_reply_and_block(conn, m,
|
|
Calltimeout, &err);
|
|
dbus_message_unref(m);
|
|
if(reply != NULL){
|
|
dbus_message_unref(reply);
|
|
dbus_error_free(&err);
|
|
return fail("%s on %s unexpectedly succeeded", member, path);
|
|
}
|
|
if(!dbus_error_has_name(&err, error)){
|
|
fail("%s on %s returned %s, expected %s", member, path,
|
|
err.name != NULL ? err.name : "no error", error);
|
|
dbus_error_free(&err);
|
|
return 0;
|
|
}
|
|
dbus_error_free(&err);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
limitscall(DBusConnection *conn)
|
|
{
|
|
DBusMessage *m, *reply;
|
|
DBusError err;
|
|
const char *client;
|
|
|
|
client = "ibus-live-test-overflow";
|
|
m = method("/org/freedesktop/IBus", "org.freedesktop.IBus",
|
|
"CreateInputContext");
|
|
if(m == NULL || !dbus_message_append_args(m, DBUS_TYPE_STRING, &client,
|
|
DBUS_TYPE_INVALID)){
|
|
if(m != NULL) dbus_message_unref(m);
|
|
return fail("build overflow CreateInputContext call");
|
|
}
|
|
dbus_error_init(&err);
|
|
reply = dbus_connection_send_with_reply_and_block(conn, m,
|
|
Calltimeout, &err);
|
|
dbus_message_unref(m);
|
|
if(reply != NULL){
|
|
dbus_message_unref(reply);
|
|
dbus_error_free(&err);
|
|
return fail("65th input context unexpectedly succeeded");
|
|
}
|
|
if(!dbus_error_has_name(&err, DBUS_ERROR_LIMITS_EXCEEDED)){
|
|
fail("65th input context returned %s, expected %s",
|
|
err.name != NULL ? err.name : "no error",
|
|
DBUS_ERROR_LIMITS_EXCEEDED);
|
|
dbus_error_free(&err);
|
|
return 0;
|
|
}
|
|
dbus_error_free(&err);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
nosignal(Siglog *log, char *where)
|
|
{
|
|
if(log->invalid || log->npreedit != 0 || log->ncommit != 0)
|
|
return fail("%s emitted invalid or unexpected signals", where);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
preedit(Siglog *log, char *path, char *text, unsigned int cursor, int visible,
|
|
char *where)
|
|
{
|
|
if(log->invalid || log->npreedit != 1 || log->ncommit != 0 ||
|
|
strcmp(log->preeditpath, path) != 0 ||
|
|
strcmp(log->preedit, text) != 0 ||
|
|
log->cursor != cursor || (log->visible != FALSE) != (visible != 0))
|
|
return fail("%s preedit: count=%d commit=%d path=%s text=%s cursor=%u visible=%d",
|
|
where, log->npreedit, log->ncommit, log->preeditpath, log->preedit,
|
|
(unsigned)log->cursor, log->visible != FALSE);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
committed(Siglog *log, char *path, char *text, char *where)
|
|
{
|
|
if(log->invalid || log->npreedit != 1 || log->ncommit != 1 ||
|
|
strcmp(log->preeditpath, path) != 0 ||
|
|
strcmp(log->commitpath, path) != 0 ||
|
|
strcmp(log->commit, text) != 0 ||
|
|
strcmp(log->preedit, "") != 0 || log->cursor != 0 ||
|
|
log->visible != FALSE)
|
|
return fail("%s signals: preedit=%d commit=%d text=%s/%s",
|
|
where, log->npreedit, log->ncommit, log->commit, log->preedit);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
expectkey(DBusConnection *conn, Siglog *log, char *path,
|
|
dbus_uint32_t sym, dbus_uint32_t state, int eaten, char *text, char *where)
|
|
{
|
|
int got;
|
|
|
|
if(!keycall(conn, log, path, sym, state, &got))
|
|
return 0;
|
|
if(got != eaten)
|
|
return fail("%s eaten=%d, expected %d", where, got, eaten);
|
|
if(text == NULL)
|
|
return nosignal(log, where);
|
|
return preedit(log, path, text, (unsigned)strlen(text) == 0 ? 0 : 1,
|
|
text[0] != '\0', where);
|
|
}
|
|
|
|
static int
|
|
runconnectioncapacity(Daemon *d)
|
|
{
|
|
DBusConnection *conn[Maxconnections];
|
|
Siglog logs[Maxconnections];
|
|
char names[Maxconnections][32];
|
|
char paths[Maxconnections][96];
|
|
int i, ok;
|
|
|
|
for(i = 0; i < Maxconnections; i++)
|
|
conn[i] = NULL;
|
|
memset(logs, 0, sizeof logs);
|
|
ok = 0;
|
|
for(i = 0; i < Maxconnections; i++){
|
|
conn[i] = openbus(d->address, &logs[i]);
|
|
if(conn[i] == NULL || !hello(conn[i], names[i], sizeof names[i]) ||
|
|
!createcontext(conn[i], paths[i], sizeof paths[i]) ||
|
|
!emptycall(conn[i], &logs[i], paths[i], "FocusIn") ||
|
|
!nosignal(&logs[i], "connection capacity FocusIn") ||
|
|
!expectkey(conn[i], &logs[i], paths[i], 0xffe1, 0, 0, NULL,
|
|
"connection capacity key"))
|
|
goto out;
|
|
}
|
|
if(!overflowrejected(d->address))
|
|
goto out;
|
|
for(i = 0; i < Maxconnections; i++)
|
|
if(!expectkey(conn[i], &logs[i], paths[i], 0xffe1, 0, 0, NULL,
|
|
"accepted connection after rejection"))
|
|
goto out;
|
|
closebus(&conn[Maxconnections-1], &logs[Maxconnections-1]);
|
|
if(!expectkey(conn[0], &logs[0], paths[0], 0xffe1, 0, 0, NULL,
|
|
"connection close barrier"))
|
|
goto out;
|
|
conn[Maxconnections-1] = openbus(d->address,
|
|
&logs[Maxconnections-1]);
|
|
if(conn[Maxconnections-1] == NULL ||
|
|
!hello(conn[Maxconnections-1], names[Maxconnections-1],
|
|
sizeof names[Maxconnections-1]) ||
|
|
!createcontext(conn[Maxconnections-1], paths[Maxconnections-1],
|
|
sizeof paths[Maxconnections-1]) ||
|
|
!emptycall(conn[Maxconnections-1], &logs[Maxconnections-1],
|
|
paths[Maxconnections-1], "FocusIn") ||
|
|
!nosignal(&logs[Maxconnections-1], "recovered connection FocusIn") ||
|
|
!capcall(conn[Maxconnections-1], &logs[Maxconnections-1],
|
|
paths[Maxconnections-1], Cappreedit) ||
|
|
!nosignal(&logs[Maxconnections-1], "recovered connection capability") ||
|
|
!expectkey(conn[Maxconnections-1], &logs[Maxconnections-1],
|
|
paths[Maxconnections-1], 'n', Ctrlmask, 1, "",
|
|
"recovered connection select Japanese") ||
|
|
!expectkey(conn[Maxconnections-1], &logs[Maxconnections-1],
|
|
paths[Maxconnections-1], 'k', 0, 1, "k",
|
|
"recovered connection key"))
|
|
goto out;
|
|
for(i = 0; i < Maxconnections-1; i++)
|
|
if(!expectkey(conn[i], &logs[i], paths[i], 0xffe1, 0, 0, NULL,
|
|
"accepted connection after recovery"))
|
|
goto out;
|
|
ok = 1;
|
|
out:
|
|
for(i = 0; i < Maxconnections; i++)
|
|
closebus(&conn[i], &logs[i]);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
runlifecycle(Daemon *d)
|
|
{
|
|
DBusConnection *c1, *c2, *c3;
|
|
Siglog s1, s2, s3;
|
|
char n1[32], n2[32], n3[32];
|
|
char a[96], a2[96], b[96], active[96], path[96], first[96];
|
|
int eaten, i, ok;
|
|
|
|
c1 = c2 = c3 = NULL;
|
|
memset(&s1, 0, sizeof s1);
|
|
memset(&s2, 0, sizeof s2);
|
|
memset(&s3, 0, sizeof s3);
|
|
ok = 0;
|
|
c1 = openbus(d->address, &s1);
|
|
c2 = openbus(d->address, &s2);
|
|
if(c1 == NULL || c2 == NULL)
|
|
goto out;
|
|
if(!hello(c1, n1, sizeof n1) || !hello(c2, n2, sizeof n2) ||
|
|
strcmp(n1, n2) == 0)
|
|
goto out;
|
|
if(!createcontext(c1, a, sizeof a) || !createcontext(c2, b, sizeof b))
|
|
goto out;
|
|
if(!unknowncall(c1, "/org/freedesktop/IBus/InputContext_missing",
|
|
"FocusIn", DBUS_ERROR_UNKNOWN_OBJECT))
|
|
goto out;
|
|
if(!expectkey(c1, &s1, a, 'x', 0, 0, NULL, "unfocused key"))
|
|
goto out;
|
|
if(!cursorcall(c1, &s1, a, -10, -20, 0, 14) ||
|
|
!nosignal(&s1, "pre-focus cursor") ||
|
|
!emptycall(c1, &s1, a, "FocusIn") || !nosignal(&s1, "FocusIn") ||
|
|
!capcall(c1, &s1, a, Cappreedit) ||
|
|
!nosignal(&s1, "A capability") ||
|
|
!expectkey(c1, &s1, a, 'x', Relmask, 0, NULL, "physical release"))
|
|
goto out;
|
|
if(!expectkey(c1, &s1, a, 'n', Ctrlmask, 1, "", "select Japanese") ||
|
|
!expectkey(c1, &s1, a, 'k', 0, 1, "k", "A key k") ||
|
|
!expectkey(c1, &s1, a, 'a', 0, 1, "か", "A key a"))
|
|
goto out;
|
|
if(!emptycall(c2, &s2, b, "FocusIn") || !nosignal(&s2, "B FocusIn") ||
|
|
!capcall(c2, &s2, b, Cappreedit) ||
|
|
!nosignal(&s2, "inactive B capability") ||
|
|
!expectkey(c2, &s2, b, 'n', 0, 1, "ん", "B takeover") ||
|
|
!expectkey(c1, &s1, a, 0xffe1, 0, 0, "", "stale A state") ||
|
|
!expectkey(c2, &s2, b, 0xffe1, 0, 0, "ん", "active B state"))
|
|
goto out;
|
|
if(!emptycall(c1, &s1, a, "Reset") ||
|
|
!preedit(&s1, a, "", 0, 0, "stale Reset reply") ||
|
|
!expectkey(c2, &s2, b, 0xffe1, 0, 0, "ん", "B after stale Reset") ||
|
|
!emptycall(c1, &s1, a, "FocusOut") ||
|
|
!preedit(&s1, a, "", 0, 0, "stale FocusOut reply") ||
|
|
!expectkey(c2, &s2, b, 0xffe1, 0, 0, "ん", "B after stale FocusOut"))
|
|
goto out;
|
|
if(!emptycall(c1, &s1, a, "FocusIn") || !nosignal(&s1, "stale FocusIn") ||
|
|
!cursorcall(c1, &s1, a, 90, 100, 0, 20) ||
|
|
!nosignal(&s1, "stale cursor") ||
|
|
!expectkey(c2, &s2, b, 0xffe1, 0, 0, "ん", "B after stale cursor") ||
|
|
!emptycall(c1, &s1, a, "Destroy") || !nosignal(&s1, "stale Destroy") ||
|
|
!expectkey(c2, &s2, b, 0xffe1, 0, 0, "ん", "B after stale Destroy") ||
|
|
!unknowncall(c1, a, "FocusIn", DBUS_ERROR_UNKNOWN_OBJECT))
|
|
goto out;
|
|
if(!createcontext(c1, a2, sizeof a2) ||
|
|
!emptycall(c1, &s1, a2, "FocusIn") || !nosignal(&s1, "A2 FocusIn") ||
|
|
!capcall(c1, &s1, a2, Cappreedit) ||
|
|
!nosignal(&s1, "A2 capability") ||
|
|
!cursorcall(c1, &s1, a2, 120, 130, 0, 18) ||
|
|
!nosignal(&s1, "A2 stale cursor"))
|
|
goto out;
|
|
closebus(&c1, &s1);
|
|
if(!expectkey(c2, &s2, b, 0xffe1, 0, 0, "ん", "B during stale close") ||
|
|
!expectkey(c2, &s2, b, 0xffe1, 0, 0, "ん", "B after stale close"))
|
|
goto out;
|
|
if(!emptycall(c2, &s2, b, "Reset") ||
|
|
!preedit(&s2, b, "", 0, 0, "active Reset") ||
|
|
!expectkey(c2, &s2, b, 'k', 0, 1, "k", "reset owner key k") ||
|
|
!expectkey(c2, &s2, b, 'a', 0, 1, "か", "reset owner key a"))
|
|
goto out;
|
|
if(!keycall(c2, &s2, b, '0', 0, &eaten) || !eaten ||
|
|
!committed(&s2, b, "か", "active commit"))
|
|
goto out;
|
|
if(!expectkey(c2, &s2, b, 'k', 0, 1, "k", "pre-FocusOut key") ||
|
|
!cursorcall(c2, &s2, b, 50, 60, 0, 12) ||
|
|
!nosignal(&s2, "active cursor") ||
|
|
!emptycall(c2, &s2, b, "FocusOut") ||
|
|
!preedit(&s2, b, "", 0, 0, "active FocusOut") ||
|
|
!emptycall(c2, &s2, b, "FocusOut") ||
|
|
!preedit(&s2, b, "", 0, 0, "repeated FocusOut") ||
|
|
!emptycall(c2, &s2, b, "FocusIn") || !nosignal(&s2, "B refocus") ||
|
|
!expectkey(c2, &s2, b, 0xffe1, 0, 0, "", "B state after FocusOut") ||
|
|
!expectkey(c2, &s2, b, 'k', 0, 1, "k", "B after refocus") ||
|
|
!emptycall(c2, &s2, b, "Destroy") || !nosignal(&s2, "active Destroy") ||
|
|
!unknowncall(c2, b, "Reset", DBUS_ERROR_UNKNOWN_OBJECT))
|
|
goto out;
|
|
if(!createcontext(c2, active, sizeof active))
|
|
goto out;
|
|
for(i = 1; i < 64; i++)
|
|
if(!createcontext(c2, path, sizeof path))
|
|
goto out;
|
|
if(!emptycall(c2, &s2, active, "FocusIn") ||
|
|
!nosignal(&s2, "capacity owner FocusIn") ||
|
|
!capcall(c2, &s2, active, Cappreedit) ||
|
|
!nosignal(&s2, "capacity owner capability") ||
|
|
!expectkey(c2, &s2, active, 'k', 0, 1, "k", "capacity owner key"))
|
|
goto out;
|
|
closebus(&c2, &s2);
|
|
c3 = openbus(d->address, &s3);
|
|
if(c3 == NULL || !hello(c3, n3, sizeof n3))
|
|
goto out;
|
|
for(i = 0; i < 64; i++){
|
|
if(!createcontext(c3, path, sizeof path))
|
|
goto out;
|
|
if(i == 0)
|
|
snprintf(first, sizeof first, "%s", path);
|
|
}
|
|
if(!limitscall(c3) ||
|
|
!emptycall(c3, &s3, first, "FocusIn") ||
|
|
!nosignal(&s3, "reused context FocusIn") ||
|
|
!capcall(c3, &s3, first, Cappreedit) ||
|
|
!nosignal(&s3, "reused context capability") ||
|
|
!expectkey(c3, &s3, first, 'k', 0, 1, "k", "reused context key"))
|
|
goto out;
|
|
ok = 1;
|
|
out:
|
|
closebus(&c1, &s1);
|
|
closebus(&c2, &s2);
|
|
closebus(&c3, &s3);
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
runpolicy(Daemon *d)
|
|
{
|
|
DBusConnection *c1, *c2;
|
|
Siglog s1, s2;
|
|
char n1[32], n2[32], a[96], b[96], typed[16];
|
|
char *query;
|
|
int eaten, i, ok;
|
|
|
|
c1 = c2 = NULL;
|
|
memset(&s1, 0, sizeof s1);
|
|
memset(&s2, 0, sizeof s2);
|
|
ok = 0;
|
|
c1 = openbus(d->address, &s1);
|
|
c2 = openbus(d->address, &s2);
|
|
if(c1 == NULL || c2 == NULL ||
|
|
!hello(c1, n1, sizeof n1) || !hello(c2, n2, sizeof n2) ||
|
|
!createcontext(c1, a, sizeof a) || !createcontext(c2, b, sizeof b) ||
|
|
!emptycall(c1, &s1, a, "FocusIn") || !nosignal(&s1, "policy FocusIn"))
|
|
goto out;
|
|
|
|
/* Capability starts at zero; only advertised bit zero affects routing. */
|
|
if(!expectkey(c1, &s1, a, 'n', Ctrlmask, 1, NULL,
|
|
"zero-capability language") ||
|
|
!expectkey(c1, &s1, a, 'k', 0, 1, NULL, "zero-capability key") ||
|
|
!capcall(c1, &s1, a, 1<<1) || !nosignal(&s1, "non-preedit cap") ||
|
|
!capcall(c1, &s1, a, Cappreedit|(1<<1)) ||
|
|
!preedit(&s1, a, "k", 1, 1, "enable inline") ||
|
|
!capcall(c1, &s1, a, Cappreedit|(1<<1)) ||
|
|
!nosignal(&s1, "repeat inline capability") ||
|
|
!capcall(c1, &s1, a, 1<<1) ||
|
|
!preedit(&s1, a, "", 0, 0, "disable inline") ||
|
|
!capcall(c1, &s1, a, 1<<1) ||
|
|
!nosignal(&s1, "repeat popup capability") ||
|
|
!expectkey(c1, &s1, a, 'a', 0, 1, NULL, "popup-preedit key") ||
|
|
!capcall(c1, &s1, a, Cappreedit|(1<<1)) ||
|
|
!preedit(&s1, a, "か", 1, 1, "restore inline"))
|
|
goto out;
|
|
|
|
/* A focused but inactive context cannot acquire ownership through Keycap. */
|
|
if(!emptycall(c2, &s2, b, "FocusIn") || !nosignal(&s2, "inactive FocusIn") ||
|
|
!capcall(c2, &s2, b, Cappreedit) ||
|
|
!nosignal(&s2, "inactive capability") ||
|
|
!expectkey(c1, &s1, a, 0xffe1, 0, 0, "か", "owner after inactive cap"))
|
|
goto out;
|
|
|
|
/* Toggling inline display leaves the engine-owned candidate list intact. */
|
|
if(!emptycall(c1, &s1, a, "Reset") ||
|
|
!preedit(&s1, a, "", 0, 0, "candidate reset") ||
|
|
!expectkey(c1, &s1, a, 'e', Ctrlmask, 1, "", "begin emoji search"))
|
|
goto out;
|
|
query = "smile";
|
|
typed[0] = '\0';
|
|
for(i = 0; query[i] != '\0'; i++){
|
|
typed[i] = query[i];
|
|
typed[i+1] = '\0';
|
|
if(!keycall(c1, &s1, a, query[i], 0, &eaten) || !eaten ||
|
|
!preedit(&s1, a, typed, i+1, 1, "emoji query"))
|
|
goto out;
|
|
}
|
|
if(!capcall(c1, &s1, a, 1<<1) ||
|
|
!preedit(&s1, a, "", 0, 0, "candidate popup policy") ||
|
|
!capcall(c1, &s1, a, Cappreedit|(1<<1)) ||
|
|
!preedit(&s1, a, "smile", 5, 1, "candidate inline policy") ||
|
|
!keycall(c1, &s1, a, '1', 0, &eaten) || !eaten ||
|
|
!committed(&s1, a, "😀", "candidate after capability toggle"))
|
|
goto out;
|
|
|
|
/* PRIVATE preserves composition; hidden purposes and hints reset it. */
|
|
if(!expectkey(c1, &s1, a, 'n', Ctrlmask, 1, "", "private select Japanese") ||
|
|
!expectkey(c1, &s1, a, 'n', 0, 1, "ん", "private preedit") ||
|
|
!contentcall(c1, &s1, a, Purposefree, Hintprivate) ||
|
|
!nosignal(&s1, "PRIVATE property") ||
|
|
!expectkey(c1, &s1, a, 'a', 0, 1, "な", "PRIVATE key") ||
|
|
!contentcall(c1, &s1, a, Purposepassword, 0) ||
|
|
!preedit(&s1, a, "", 0, 0, "PASSWORD transition") ||
|
|
!expectkey(c1, &s1, a, 'x', 0, 0, NULL, "PASSWORD key") ||
|
|
!contentcall(c1, &s1, a, Purposefree, 0) ||
|
|
!nosignal(&s1, "leave PASSWORD") ||
|
|
!expectkey(c1, &s1, a, 'k', 0, 1, "k", "after PASSWORD") ||
|
|
!contentcall(c1, &s1, a, Purposepin, 0) ||
|
|
!preedit(&s1, a, "", 0, 0, "PIN transition") ||
|
|
!expectkey(c1, &s1, a, 'x', 0, 0, NULL, "PIN key") ||
|
|
!contentcall(c1, &s1, a, Purposefree, Hintprivate) ||
|
|
!nosignal(&s1, "leave PIN with PRIVATE") ||
|
|
!expectkey(c1, &s1, a, 'n', 0, 1, "ん", "PRIVATE resumes") ||
|
|
!contentcall(c1, &s1, a, Purposefree, Hinthidden) ||
|
|
!preedit(&s1, a, "", 0, 0, "HIDDEN_TEXT transition") ||
|
|
!expectkey(c1, &s1, a, 'x', 0, 0, NULL, "HIDDEN_TEXT key") ||
|
|
!contentcall(c1, &s1, a, Purposefree, 0) ||
|
|
!nosignal(&s1, "free-form property") ||
|
|
!expectkey(c1, &s1, a, 'n', 0, 1, "ん", "free-form resumes"))
|
|
goto out;
|
|
ok = 1;
|
|
out:
|
|
closebus(&c1, &s1);
|
|
closebus(&c2, &s2);
|
|
return ok;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
Daemon daemon;
|
|
int ok;
|
|
|
|
if(argc != 3){
|
|
fprintf(stderr, "usage: ibus_live_test strans mapdir\n");
|
|
return 2;
|
|
}
|
|
ok = startdaemon(&daemon, argv[1], argv[2]);
|
|
if(ok)
|
|
ok = runconnectioncapacity(&daemon);
|
|
if(ok)
|
|
ok = runlifecycle(&daemon);
|
|
if(ok)
|
|
ok = runpolicy(&daemon);
|
|
if(!ok)
|
|
showerrors(&daemon);
|
|
if(!stopdaemon(&daemon))
|
|
ok = 0;
|
|
if(!ok)
|
|
return 1;
|
|
printf("ibus live lifecycle, policy, and connection capacity: ok\n");
|
|
return 0;
|
|
}
|