tests: cover frontend input-panel protocols
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@
|
|||||||
/tests/ibus_live_test
|
/tests/ibus_live_test
|
||||||
/tests/ibus_client_smoke
|
/tests/ibus_client_smoke
|
||||||
/tests/gtk_live_test
|
/tests/gtk_live_test
|
||||||
|
/tests/xim_live_test
|
||||||
/tests/ipc_live_test
|
/tests/ipc_live_test
|
||||||
/tests/daemon_collision_test
|
/tests/daemon_collision_test
|
||||||
/tests/daemon_failure_test
|
/tests/daemon_failure_test
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ RUN pacman -Syu --noconfirm --needed \
|
|||||||
gtk3 \
|
gtk3 \
|
||||||
ibus \
|
ibus \
|
||||||
harfbuzz \
|
harfbuzz \
|
||||||
|
libx11 \
|
||||||
libxcb \
|
libxcb \
|
||||||
libxkbcommon \
|
libxkbcommon \
|
||||||
make \
|
make \
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ IBUS_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
|
|||||||
IBUS_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
|
IBUS_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
|
||||||
IBUS_CLIENT_CFLAGS = $(shell pkg-config --cflags ibus-1.0)
|
IBUS_CLIENT_CFLAGS = $(shell pkg-config --cflags ibus-1.0)
|
||||||
IBUS_CLIENT_LIBS = $(shell pkg-config --libs ibus-1.0)
|
IBUS_CLIENT_LIBS = $(shell pkg-config --libs ibus-1.0)
|
||||||
|
X11_CFLAGS = $(shell pkg-config --cflags x11)
|
||||||
|
X11_LIBS = $(shell pkg-config --libs x11)
|
||||||
XIM_CFLAGS = $(shell pkg-config --cflags xcb-imdkit xkbcommon)
|
XIM_CFLAGS = $(shell pkg-config --cflags xcb-imdkit xkbcommon)
|
||||||
XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon)
|
XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon)
|
||||||
LIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS)
|
LIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS)
|
||||||
|
|
||||||
PROG = unit_test
|
PROG = unit_test
|
||||||
LIVE = ibus_live_test ibus_client_smoke gtk_live_test ipc_live_test \
|
LIVE = ibus_live_test ibus_client_smoke gtk_live_test xim_live_test \
|
||||||
daemon_collision_test daemon_failure_test daemon_restart_test
|
ipc_live_test daemon_collision_test daemon_failure_test daemon_restart_test
|
||||||
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
TESTSRC = unit_test.c test_util.c str_test.c hash_test.c trie_test.c \
|
||||||
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
||||||
popup_test.c font_test.c ibus_test.c server_test.c xim_adapter_test.c
|
popup_test.c font_test.c ibus_test.c server_test.c xim_adapter_test.c
|
||||||
@@ -35,6 +37,7 @@ check test: $(PROG) $(LIVE)
|
|||||||
./$(PROG) $(TESTARGS)
|
./$(PROG) $(TESTARGS)
|
||||||
./ibus_live_test ../strans ../map ./ibus_client_smoke
|
./ibus_live_test ../strans ../map ./ibus_client_smoke
|
||||||
./gtk_live_test ../gtk/im-strans.so
|
./gtk_live_test ../gtk/im-strans.so
|
||||||
|
./xim_live_test ../strans ../map
|
||||||
./ipc_live_test ../strans ../map
|
./ipc_live_test ../strans ../map
|
||||||
./daemon_collision_test ../strans ../map
|
./daemon_collision_test ../strans ../map
|
||||||
./daemon_failure_test ../strans
|
./daemon_failure_test ../strans
|
||||||
@@ -54,6 +57,10 @@ gtk_live_test: gtk_live_test.c ../ipc.c ../ipc.h
|
|||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(GTK_CFLAGS) -o $@ \
|
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(GTK_CFLAGS) -o $@ \
|
||||||
gtk_live_test.c ../ipc.c $(GTK_LIBS) -pthread
|
gtk_live_test.c ../ipc.c $(GTK_LIBS) -pthread
|
||||||
|
|
||||||
|
xim_live_test: xim_live_test.c ../ipc.c ../ipc.h
|
||||||
|
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(X11_CFLAGS) -o $@ \
|
||||||
|
xim_live_test.c ../ipc.c $(X11_LIBS)
|
||||||
|
|
||||||
ipc_live_test: ipc_live_test.c ../ipc.c ../ipc.h
|
ipc_live_test: ipc_live_test.c ../ipc.c ../ipc.h
|
||||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. -o $@ ipc_live_test.c ../ipc.c
|
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. -o $@ ipc_live_test.c ../ipc.c
|
||||||
|
|
||||||
|
|||||||
946
tests/xim_live_test.c
Normal file
946
tests/xim_live_test.c
Normal file
@@ -0,0 +1,946 @@
|
|||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
#include <X11/keysym.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <locale.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "ipc.h"
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Starttimeout = 8000,
|
||||||
|
Eventtimeout = 4000,
|
||||||
|
Stoptimeout = 3000,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct Child Child;
|
||||||
|
typedef struct Run Run;
|
||||||
|
typedef struct Prelog Prelog;
|
||||||
|
|
||||||
|
struct Child
|
||||||
|
{
|
||||||
|
pid_t pid;
|
||||||
|
pid_t pgid;
|
||||||
|
char log[320];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Run
|
||||||
|
{
|
||||||
|
Child xvfb;
|
||||||
|
Child daemon;
|
||||||
|
char root[256];
|
||||||
|
char runtime[320];
|
||||||
|
char config[320];
|
||||||
|
char ibus[352];
|
||||||
|
char bus[384];
|
||||||
|
char home[320];
|
||||||
|
char socket[384];
|
||||||
|
char display[32];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Prelog
|
||||||
|
{
|
||||||
|
int start;
|
||||||
|
int nonempty;
|
||||||
|
int empty;
|
||||||
|
int done;
|
||||||
|
int doneafterempty;
|
||||||
|
int badorder;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int xerror;
|
||||||
|
|
||||||
|
static int
|
||||||
|
fail(char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
fprintf(stderr, "xim_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 void
|
||||||
|
pausems(int n)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
ts.tv_sec = n / 1000;
|
||||||
|
ts.tv_nsec = (n % 1000) * 1000000L;
|
||||||
|
while(nanosleep(&ts, &ts) < 0 && errno == EINTR)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
makedir(char *path)
|
||||||
|
{
|
||||||
|
if(mkdir(path, 0700) == 0)
|
||||||
|
return 1;
|
||||||
|
return fail("mkdir %s: %s", path, strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
childlog(char *path)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0600);
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
spawnxvfb(Run *r, char *program)
|
||||||
|
{
|
||||||
|
char fdstr[16], line[32];
|
||||||
|
int displayfd[2], errfd, flags, n, status;
|
||||||
|
int64_t deadline;
|
||||||
|
pid_t pid;
|
||||||
|
struct pollfd pfd;
|
||||||
|
ssize_t nr;
|
||||||
|
|
||||||
|
if(pipe2(displayfd, O_CLOEXEC) < 0)
|
||||||
|
return fail("create Xvfb display pipe: %s", strerror(errno));
|
||||||
|
errfd = childlog(r->xvfb.log);
|
||||||
|
if(errfd < 0){
|
||||||
|
close(displayfd[0]);
|
||||||
|
close(displayfd[1]);
|
||||||
|
return fail("open Xvfb log: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
pid = fork();
|
||||||
|
if(pid < 0){
|
||||||
|
close(displayfd[0]);
|
||||||
|
close(displayfd[1]);
|
||||||
|
close(errfd);
|
||||||
|
return fail("fork Xvfb: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
if(pid == 0){
|
||||||
|
setpgid(0, 0);
|
||||||
|
close(displayfd[0]);
|
||||||
|
flags = fcntl(displayfd[1], F_GETFD);
|
||||||
|
if(flags < 0 || fcntl(displayfd[1], F_SETFD,
|
||||||
|
flags & ~FD_CLOEXEC) < 0)
|
||||||
|
_exit(126);
|
||||||
|
if(dup2(errfd, STDOUT_FILENO) < 0 ||
|
||||||
|
dup2(errfd, STDERR_FILENO) < 0)
|
||||||
|
_exit(126);
|
||||||
|
close(errfd);
|
||||||
|
snprintf(fdstr, sizeof fdstr, "%d", displayfd[1]);
|
||||||
|
execlp(program, program, "-displayfd", fdstr,
|
||||||
|
"-screen", "0", "800x600x24", "-nolisten", "tcp",
|
||||||
|
"-noreset", (char*)0);
|
||||||
|
_exit(127);
|
||||||
|
}
|
||||||
|
close(displayfd[1]);
|
||||||
|
close(errfd);
|
||||||
|
if((setpgid(pid, pid) < 0 && errno != EACCES) || getpgid(pid) != pid){
|
||||||
|
kill(pid, SIGKILL);
|
||||||
|
while(waitpid(pid, &status, 0) < 0 && errno == EINTR)
|
||||||
|
;
|
||||||
|
close(displayfd[0]);
|
||||||
|
return fail("isolate Xvfb process group: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
r->xvfb.pid = pid;
|
||||||
|
r->xvfb.pgid = pid;
|
||||||
|
pfd.fd = displayfd[0];
|
||||||
|
pfd.events = POLLIN|POLLHUP;
|
||||||
|
deadline = nowms() + Starttimeout;
|
||||||
|
n = 0;
|
||||||
|
while(n + 1 < (int)sizeof line){
|
||||||
|
pfd.revents = 0;
|
||||||
|
status = poll(&pfd, 1, leftms(deadline));
|
||||||
|
if(status < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
if(status <= 0)
|
||||||
|
break;
|
||||||
|
nr = read(displayfd[0], line + n, sizeof line - n - 1);
|
||||||
|
if(nr > 0){
|
||||||
|
n += nr;
|
||||||
|
line[n] = '\0';
|
||||||
|
if(strchr(line, '\n') != NULL)
|
||||||
|
break;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(nr < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
close(displayfd[0]);
|
||||||
|
if(n == 0 || strchr(line, '\n') == NULL)
|
||||||
|
return fail("Xvfb did not report a private display");
|
||||||
|
line[strcspn(line, "\r\n")] = '\0';
|
||||||
|
for(n = 0; line[n] != '\0'; n++)
|
||||||
|
if(line[n] < '0' || line[n] > '9')
|
||||||
|
return fail("invalid Xvfb display number %s", line);
|
||||||
|
if(snprintf(r->display, sizeof r->display, ":%s", line)
|
||||||
|
>= (int)sizeof r->display)
|
||||||
|
return fail("Xvfb display number is too long");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
startdaemon(Run *r, char *program, char *mapdir)
|
||||||
|
{
|
||||||
|
int errfd;
|
||||||
|
pid_t pid;
|
||||||
|
|
||||||
|
errfd = childlog(r->daemon.log);
|
||||||
|
if(errfd < 0)
|
||||||
|
return fail("open daemon log: %s", strerror(errno));
|
||||||
|
pid = fork();
|
||||||
|
if(pid < 0){
|
||||||
|
close(errfd);
|
||||||
|
return fail("fork daemon: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
if(pid == 0){
|
||||||
|
setpgid(0, 0);
|
||||||
|
if(dup2(errfd, STDOUT_FILENO) < 0 ||
|
||||||
|
dup2(errfd, STDERR_FILENO) < 0)
|
||||||
|
_exit(126);
|
||||||
|
close(errfd);
|
||||||
|
execl(program, program, mapdir, (char*)0);
|
||||||
|
_exit(127);
|
||||||
|
}
|
||||||
|
close(errfd);
|
||||||
|
if((setpgid(pid, pid) < 0 && errno != EACCES) || getpgid(pid) != pid){
|
||||||
|
kill(pid, SIGKILL);
|
||||||
|
while(waitpid(pid, NULL, 0) < 0 && errno == EINTR)
|
||||||
|
;
|
||||||
|
return fail("isolate daemon process group: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
r->daemon.pid = pid;
|
||||||
|
r->daemon.pgid = pid;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
waitsocket(Run *r)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
int status;
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
deadline = nowms() + Starttimeout;
|
||||||
|
while(leftms(deadline) > 0){
|
||||||
|
if(lstat(r->socket, &st) == 0 && S_ISSOCK(st.st_mode) &&
|
||||||
|
(st.st_mode & 0777) == 0600)
|
||||||
|
return 1;
|
||||||
|
if(waitpid(r->daemon.pid, &status, WNOHANG) == r->daemon.pid){
|
||||||
|
r->daemon.pid = -1;
|
||||||
|
return fail("daemon exited before creating its IPC socket");
|
||||||
|
}
|
||||||
|
pausems(20);
|
||||||
|
}
|
||||||
|
return fail("timed out waiting for daemon IPC socket");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
start(Run *r, char *daemon, char *mapdir, char *xvfb)
|
||||||
|
{
|
||||||
|
memset(r, 0, sizeof *r);
|
||||||
|
r->xvfb.pid = r->daemon.pid = -1;
|
||||||
|
r->xvfb.pgid = r->daemon.pgid = -1;
|
||||||
|
snprintf(r->root, sizeof r->root, "/tmp/strans-xim.XXXXXX");
|
||||||
|
if(mkdtemp(r->root) == NULL)
|
||||||
|
return fail("mkdtemp: %s", strerror(errno));
|
||||||
|
if(snprintf(r->runtime, sizeof r->runtime, "%s/runtime", r->root)
|
||||||
|
>= (int)sizeof r->runtime ||
|
||||||
|
snprintf(r->config, sizeof r->config, "%s/config", r->root)
|
||||||
|
>= (int)sizeof r->config ||
|
||||||
|
snprintf(r->ibus, sizeof r->ibus, "%s/ibus", r->config)
|
||||||
|
>= (int)sizeof r->ibus ||
|
||||||
|
snprintf(r->bus, sizeof r->bus, "%s/bus", r->ibus)
|
||||||
|
>= (int)sizeof r->bus ||
|
||||||
|
snprintf(r->home, sizeof r->home, "%s/home", r->root)
|
||||||
|
>= (int)sizeof r->home ||
|
||||||
|
snprintf(r->socket, sizeof r->socket, "%s/strans.sock", r->runtime)
|
||||||
|
>= (int)sizeof r->socket ||
|
||||||
|
snprintf(r->xvfb.log, sizeof r->xvfb.log, "%s/xvfb.log", r->root)
|
||||||
|
>= (int)sizeof r->xvfb.log ||
|
||||||
|
snprintf(r->daemon.log, sizeof r->daemon.log, "%s/daemon.log", r->root)
|
||||||
|
>= (int)sizeof r->daemon.log)
|
||||||
|
return fail("temporary path is too long");
|
||||||
|
if(!makedir(r->runtime) || !makedir(r->config) ||
|
||||||
|
!makedir(r->ibus) || !makedir(r->bus) || !makedir(r->home))
|
||||||
|
return 0;
|
||||||
|
if(!spawnxvfb(r, xvfb))
|
||||||
|
return 0;
|
||||||
|
if(setenv("DISPLAY", r->display, 1) < 0 ||
|
||||||
|
setenv("XMODIFIERS", "@im=strans", 1) < 0 ||
|
||||||
|
setenv("XDG_RUNTIME_DIR", r->runtime, 1) < 0 ||
|
||||||
|
setenv("XDG_CONFIG_HOME", r->config, 1) < 0 ||
|
||||||
|
setenv("HOME", r->home, 1) < 0 ||
|
||||||
|
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
||||||
|
unsetenv("IBUS_ADDRESS") < 0)
|
||||||
|
return fail("set private environment: %s", strerror(errno));
|
||||||
|
if(!startdaemon(r, daemon, mapdir))
|
||||||
|
return 0;
|
||||||
|
return waitsocket(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
showlog(char *name)
|
||||||
|
{
|
||||||
|
char buf[4096];
|
||||||
|
ssize_t n;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = open(name, O_RDONLY|O_CLOEXEC);
|
||||||
|
if(fd < 0)
|
||||||
|
return;
|
||||||
|
while((n = read(fd, buf, sizeof buf)) > 0)
|
||||||
|
fwrite(buf, 1, n, stderr);
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
stopchild(Child *c)
|
||||||
|
{
|
||||||
|
int i, status;
|
||||||
|
pid_t n;
|
||||||
|
|
||||||
|
if(c->pgid > 0 && kill(-c->pgid, SIGTERM) < 0 && errno != ESRCH)
|
||||||
|
fail("terminate process group %ld: %s", (long)c->pgid,
|
||||||
|
strerror(errno));
|
||||||
|
if(c->pid > 0)
|
||||||
|
for(i = 0; i < Stoptimeout / 20; i++){
|
||||||
|
n = waitpid(c->pid, &status, WNOHANG);
|
||||||
|
if(n == c->pid || (n < 0 && errno == ECHILD)){
|
||||||
|
c->pid = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(n < 0 && errno != EINTR)
|
||||||
|
break;
|
||||||
|
pausems(20);
|
||||||
|
}
|
||||||
|
if(c->pgid > 0)
|
||||||
|
kill(-c->pgid, SIGKILL);
|
||||||
|
if(c->pid > 0){
|
||||||
|
while(waitpid(c->pid, &status, 0) < 0 && errno == EINTR)
|
||||||
|
;
|
||||||
|
c->pid = -1;
|
||||||
|
}
|
||||||
|
c->pgid = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
cleardir(char *path)
|
||||||
|
{
|
||||||
|
char name[512];
|
||||||
|
struct dirent *de;
|
||||||
|
DIR *dir;
|
||||||
|
|
||||||
|
dir = opendir(path);
|
||||||
|
if(dir == NULL)
|
||||||
|
return;
|
||||||
|
while((de = readdir(dir)) != NULL){
|
||||||
|
if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
||||||
|
continue;
|
||||||
|
if(snprintf(name, sizeof name, "%s/%s", path, de->d_name)
|
||||||
|
< (int)sizeof name)
|
||||||
|
unlink(name);
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
cleanup(Run *r, int noisy)
|
||||||
|
{
|
||||||
|
stopchild(&r->daemon);
|
||||||
|
stopchild(&r->xvfb);
|
||||||
|
if(noisy){
|
||||||
|
fprintf(stderr, "xim_live_test: daemon log:\n");
|
||||||
|
showlog(r->daemon.log);
|
||||||
|
fprintf(stderr, "xim_live_test: Xvfb log:\n");
|
||||||
|
showlog(r->xvfb.log);
|
||||||
|
}
|
||||||
|
if(r->socket[0] != '\0')
|
||||||
|
unlink(r->socket);
|
||||||
|
cleardir(r->bus);
|
||||||
|
if(r->daemon.log[0] != '\0')
|
||||||
|
unlink(r->daemon.log);
|
||||||
|
if(r->xvfb.log[0] != '\0')
|
||||||
|
unlink(r->xvfb.log);
|
||||||
|
if(r->bus[0] != '\0') rmdir(r->bus);
|
||||||
|
if(r->ibus[0] != '\0') rmdir(r->ibus);
|
||||||
|
if(r->config[0] != '\0') rmdir(r->config);
|
||||||
|
if(r->runtime[0] != '\0') rmdir(r->runtime);
|
||||||
|
if(r->home[0] != '\0') rmdir(r->home);
|
||||||
|
if(r->root[0] != '\0') rmdir(r->root);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
xerr(Display *dpy, XErrorEvent *e)
|
||||||
|
{
|
||||||
|
char msg[128];
|
||||||
|
|
||||||
|
xerror++;
|
||||||
|
XGetErrorText(dpy, e->error_code, msg, sizeof msg);
|
||||||
|
fprintf(stderr, "xim_live_test: X error: %s (request %d.%d)\n",
|
||||||
|
msg, e->request_code, e->minor_code);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
pumpinput(Display *dpy, XIC ic, char *commit, size_t cap, int timeout)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
XEvent ev;
|
||||||
|
KeySym sym;
|
||||||
|
Status status;
|
||||||
|
char buf[Ipcfieldmax+1];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
XFlush(dpy);
|
||||||
|
if(XPending(dpy) == 0){
|
||||||
|
pfd.fd = ConnectionNumber(dpy);
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
pfd.revents = 0;
|
||||||
|
while(poll(&pfd, 1, timeout) < 0 && errno == EINTR)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
while(XPending(dpy) != 0){
|
||||||
|
XNextEvent(dpy, &ev);
|
||||||
|
if(XFilterEvent(&ev, None) || ic == NULL || ev.type != KeyPress)
|
||||||
|
continue;
|
||||||
|
n = Xutf8LookupString(ic, &ev.xkey, buf, sizeof buf - 1,
|
||||||
|
&sym, &status);
|
||||||
|
if(n < 0 || ((status == XLookupChars || status == XLookupBoth) &&
|
||||||
|
((size_t)n >= cap || commit == NULL)))
|
||||||
|
return 0;
|
||||||
|
if((status == XLookupChars || status == XLookupBoth) && n != 0){
|
||||||
|
memcpy(commit, buf, n);
|
||||||
|
commit[n] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pump(Display *dpy, int timeout)
|
||||||
|
{
|
||||||
|
pumpinput(dpy, NULL, NULL, 0, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
prestart(XIC ic, XPointer data, XPointer call)
|
||||||
|
{
|
||||||
|
Prelog *p;
|
||||||
|
|
||||||
|
(void)ic;
|
||||||
|
(void)call;
|
||||||
|
p = (Prelog*)data;
|
||||||
|
if(p->start != p->done)
|
||||||
|
p->badorder++;
|
||||||
|
p->start++;
|
||||||
|
return Ipcfieldmax;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
predraw(XIM im, XPointer data, XPointer call)
|
||||||
|
{
|
||||||
|
Prelog *p;
|
||||||
|
XIMPreeditDrawCallbackStruct *draw;
|
||||||
|
|
||||||
|
(void)im;
|
||||||
|
p = (Prelog*)data;
|
||||||
|
draw = (XIMPreeditDrawCallbackStruct*)call;
|
||||||
|
if(draw != NULL && draw->text != NULL && draw->text->length != 0){
|
||||||
|
if(p->start != p->done + 1)
|
||||||
|
p->badorder++;
|
||||||
|
p->nonempty++;
|
||||||
|
}else{
|
||||||
|
if(p->start != p->done + 1)
|
||||||
|
p->badorder++;
|
||||||
|
p->empty++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
predone(XIM im, XPointer data, XPointer call)
|
||||||
|
{
|
||||||
|
Prelog *p;
|
||||||
|
|
||||||
|
(void)im;
|
||||||
|
(void)call;
|
||||||
|
p = (Prelog*)data;
|
||||||
|
if(p->empty > p->done)
|
||||||
|
p->doneafterempty++;
|
||||||
|
else
|
||||||
|
p->badorder++;
|
||||||
|
p->done++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
precaret(XIM im, XPointer data, XPointer call)
|
||||||
|
{
|
||||||
|
(void)im;
|
||||||
|
(void)data;
|
||||||
|
(void)call;
|
||||||
|
}
|
||||||
|
|
||||||
|
static XIC
|
||||||
|
callbackic(XIM im, Window win, Prelog *log)
|
||||||
|
{
|
||||||
|
union {
|
||||||
|
int (*start)(XIC, XPointer, XPointer);
|
||||||
|
XIMProc xim;
|
||||||
|
} fn;
|
||||||
|
XIMCallback start, draw, done, caret;
|
||||||
|
XVaNestedList pre;
|
||||||
|
XIC ic;
|
||||||
|
|
||||||
|
start.client_data = (XPointer)log;
|
||||||
|
fn.start = prestart;
|
||||||
|
start.callback = fn.xim;
|
||||||
|
draw.client_data = (XPointer)log;
|
||||||
|
draw.callback = predraw;
|
||||||
|
done.client_data = (XPointer)log;
|
||||||
|
done.callback = predone;
|
||||||
|
caret.client_data = (XPointer)log;
|
||||||
|
caret.callback = precaret;
|
||||||
|
pre = XVaCreateNestedList(0,
|
||||||
|
XNPreeditStartCallback, &start,
|
||||||
|
XNPreeditDrawCallback, &draw,
|
||||||
|
XNPreeditDoneCallback, &done,
|
||||||
|
XNPreeditCaretCallback, &caret,
|
||||||
|
NULL);
|
||||||
|
if(pre == NULL)
|
||||||
|
return NULL;
|
||||||
|
ic = XCreateIC(im,
|
||||||
|
XNInputStyle, XIMPreeditCallbacks|XIMStatusNothing,
|
||||||
|
XNClientWindow, win,
|
||||||
|
XNFocusWindow, win,
|
||||||
|
XNPreeditAttributes, pre,
|
||||||
|
NULL);
|
||||||
|
XFree(pre);
|
||||||
|
return ic;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sendkey(Display *dpy, XIC ic, Window win, KeySym sym, unsigned int state,
|
||||||
|
char *commit, size_t cap)
|
||||||
|
{
|
||||||
|
XKeyPressedEvent key;
|
||||||
|
XEvent ev;
|
||||||
|
KeySym got;
|
||||||
|
Status status;
|
||||||
|
char buf[Ipcfieldmax+1];
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if(cap != 0)
|
||||||
|
commit[0] = '\0';
|
||||||
|
memset(&key, 0, sizeof key);
|
||||||
|
key.type = KeyPress;
|
||||||
|
key.display = dpy;
|
||||||
|
key.window = win;
|
||||||
|
key.root = DefaultRootWindow(dpy);
|
||||||
|
key.time = CurrentTime;
|
||||||
|
key.x = key.y = key.x_root = key.y_root = 1;
|
||||||
|
key.same_screen = True;
|
||||||
|
key.keycode = XKeysymToKeycode(dpy, sym);
|
||||||
|
key.state = state;
|
||||||
|
memset(&ev, 0, sizeof ev);
|
||||||
|
ev.xkey = key;
|
||||||
|
if(XFilterEvent(&ev, None)){
|
||||||
|
return pumpinput(dpy, ic, commit, cap, 20);
|
||||||
|
}
|
||||||
|
n = Xutf8LookupString(ic, &key, buf, sizeof buf - 1, &got, &status);
|
||||||
|
if(n < 0)
|
||||||
|
return fail("Xutf8LookupString failed for keysym %#lx",
|
||||||
|
(unsigned long)sym);
|
||||||
|
if((status == XLookupChars || status == XLookupBoth) && n != 0){
|
||||||
|
if((size_t)n >= cap)
|
||||||
|
return fail("commit buffer overflow");
|
||||||
|
memcpy(commit, buf, n);
|
||||||
|
commit[n] = '\0';
|
||||||
|
}
|
||||||
|
return pumpinput(dpy, ic, commit, cap, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
waitcommit(Display *dpy, XIC ic, char *commit, size_t cap)
|
||||||
|
{
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
deadline = nowms() + Eventtimeout;
|
||||||
|
while(leftms(deadline) > 0){
|
||||||
|
if(!pumpinput(dpy, ic, commit, cap, leftms(deadline)))
|
||||||
|
return fail("read committed XIM text");
|
||||||
|
if(commit[0] != '\0')
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rawtakeover(void)
|
||||||
|
{
|
||||||
|
unsigned char req[Ipcreqsz];
|
||||||
|
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
|
||||||
|
Ipcresp resp;
|
||||||
|
struct timeval tv;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = ipcconnect();
|
||||||
|
if(fd < 0)
|
||||||
|
return fail("connect raw IPC owner: %s", strerror(errno));
|
||||||
|
tv.tv_sec = Eventtimeout / 1000;
|
||||||
|
tv.tv_usec = (Eventtimeout % 1000) * 1000;
|
||||||
|
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
|
||||||
|
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
|
||||||
|
ipcpackreq(req, 0, 0, 'z');
|
||||||
|
if(ipcsend(fd, req, sizeof req) < 0 ||
|
||||||
|
ipcreadresp(fd, 0, commit, sizeof commit,
|
||||||
|
preedit, sizeof preedit, &resp) < 0){
|
||||||
|
close(fd);
|
||||||
|
return fail("raw IPC ownership transfer: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
waitclear(Display *dpy, Prelog *p, int empty, int done)
|
||||||
|
{
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
deadline = nowms() + Eventtimeout;
|
||||||
|
while(leftms(deadline) > 0){
|
||||||
|
pump(dpy, leftms(deadline));
|
||||||
|
if(p->empty >= empty && p->done >= done)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return fail("timed out waiting for empty DRAW and DONE");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
waitpreedit(Display *dpy, Prelog *p)
|
||||||
|
{
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
deadline = nowms() + Eventtimeout;
|
||||||
|
while(leftms(deadline) > 0){
|
||||||
|
pump(dpy, leftms(deadline));
|
||||||
|
if(p->empty != 0 || p->done != 0 || p->badorder != 0)
|
||||||
|
return -1;
|
||||||
|
if(p->start != 0 && p->nonempty != 0)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static XIM
|
||||||
|
openim(Display *dpy)
|
||||||
|
{
|
||||||
|
XIM im;
|
||||||
|
int64_t deadline;
|
||||||
|
|
||||||
|
deadline = nowms() + Starttimeout;
|
||||||
|
do{
|
||||||
|
im = XOpenIM(dpy, NULL, "strans", "Strans");
|
||||||
|
if(im != NULL)
|
||||||
|
return im;
|
||||||
|
pump(dpy, 20);
|
||||||
|
}while(leftms(deadline) > 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
testcallbacks(Display *dpy, XIM im, Window win)
|
||||||
|
{
|
||||||
|
char commit[Ipcfieldmax+1], *reset;
|
||||||
|
Prelog log;
|
||||||
|
XIC ic;
|
||||||
|
int fd, empty, done;
|
||||||
|
|
||||||
|
memset(&log, 0, sizeof log);
|
||||||
|
ic = callbackic(im, win, &log);
|
||||||
|
if(ic == NULL)
|
||||||
|
return fail("create PreeditCallbacks input context");
|
||||||
|
XSetICFocus(ic);
|
||||||
|
if(!sendkey(dpy, ic, win, XK_s, ControlMask, commit, sizeof commit) ||
|
||||||
|
!sendkey(dpy, ic, win, XK_r, 0, commit, sizeof commit)){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(waitpreedit(dpy, &log) != 1){
|
||||||
|
int start, draw, empty, done;
|
||||||
|
|
||||||
|
start = log.start;
|
||||||
|
draw = log.nonempty;
|
||||||
|
empty = log.empty;
|
||||||
|
done = log.done;
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return fail("invalid callback preedit: start=%d draw=%d empty=%d done=%d",
|
||||||
|
start, draw, empty, done);
|
||||||
|
}
|
||||||
|
pump(dpy, 50);
|
||||||
|
if(log.start != 1 || log.nonempty < 1 || log.done != 0 ||
|
||||||
|
log.empty != 0 || log.badorder != 0){
|
||||||
|
int start, draw, empty, done, ordered, bad;
|
||||||
|
|
||||||
|
start = log.start;
|
||||||
|
draw = log.nonempty;
|
||||||
|
empty = log.empty;
|
||||||
|
done = log.done;
|
||||||
|
ordered = log.doneafterempty;
|
||||||
|
bad = log.badorder;
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return fail("bad initial callback sequence: start=%d draw=%d empty=%d done=%d ordered=%d bad=%d",
|
||||||
|
start, draw, empty, done, ordered, bad);
|
||||||
|
}
|
||||||
|
empty = log.empty;
|
||||||
|
done = log.done;
|
||||||
|
fd = rawtakeover();
|
||||||
|
if(fd < 0){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(!waitclear(dpy, &log, empty + 1, done + 1)){
|
||||||
|
close(fd);
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* Leave the live IC time to expose a delayed duplicate transfer clear. */
|
||||||
|
pump(dpy, 100);
|
||||||
|
if(log.empty != empty + 1 || log.done != done + 1 ||
|
||||||
|
log.doneafterempty != 1 || log.badorder != 0){
|
||||||
|
int gotempty, gotdone, ordered;
|
||||||
|
|
||||||
|
gotempty = log.empty - empty;
|
||||||
|
gotdone = log.done - done;
|
||||||
|
ordered = log.doneafterempty;
|
||||||
|
close(fd);
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return fail("owner loss callback counts: empty=%d done=%d ordered=%d",
|
||||||
|
gotempty, gotdone, ordered);
|
||||||
|
}
|
||||||
|
reset = Xutf8ResetIC(ic);
|
||||||
|
if(reset != NULL)
|
||||||
|
XFree(reset);
|
||||||
|
XUnsetICFocus(ic);
|
||||||
|
XDestroyIC(ic);
|
||||||
|
pump(dpy, 100);
|
||||||
|
close(fd);
|
||||||
|
pump(dpy, 100);
|
||||||
|
if(log.empty != empty + 1 || log.done != done + 1)
|
||||||
|
return fail("late lifecycle produced duplicate clear callbacks");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
testnothing(Display *dpy, XIM im, Window win)
|
||||||
|
{
|
||||||
|
char commit[Ipcfieldmax+1];
|
||||||
|
XIC ic;
|
||||||
|
|
||||||
|
ic = XCreateIC(im,
|
||||||
|
XNInputStyle, XIMPreeditNothing|XIMStatusNothing,
|
||||||
|
XNClientWindow, win,
|
||||||
|
XNFocusWindow, win,
|
||||||
|
NULL);
|
||||||
|
if(ic == NULL)
|
||||||
|
return fail("create PreeditNothing input context");
|
||||||
|
XSetICFocus(ic);
|
||||||
|
if(!sendkey(dpy, ic, win, XK_s, ControlMask, commit, sizeof commit)){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
pump(dpy, 100);
|
||||||
|
if(!sendkey(dpy, ic, win, XK_r, 0, commit, sizeof commit) ||
|
||||||
|
!sendkey(dpy, ic, win, XK_k, 0, commit, sizeof commit) ||
|
||||||
|
!sendkey(dpy, ic, win, XK_Return, 0, commit, sizeof commit)){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(commit[0] == '\0' &&
|
||||||
|
!waitcommit(dpy, ic, commit, sizeof commit)){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return fail("PreeditNothing composition produced no commit");
|
||||||
|
}
|
||||||
|
if(strcmp(commit, "\352\260\200") != 0){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return fail("PreeditNothing commit did not contain 가");
|
||||||
|
}
|
||||||
|
XUnsetICFocus(ic);
|
||||||
|
XDestroyIC(ic);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
testposition(Display *dpy, XIM im, Window client, Window focus)
|
||||||
|
{
|
||||||
|
char commit[Ipcfieldmax+1];
|
||||||
|
char **missing, *def;
|
||||||
|
int nmissing;
|
||||||
|
XPoint spot;
|
||||||
|
XVaNestedList pre;
|
||||||
|
XFontSet fontset;
|
||||||
|
XIC ic;
|
||||||
|
|
||||||
|
missing = NULL;
|
||||||
|
nmissing = 0;
|
||||||
|
def = NULL;
|
||||||
|
fontset = XCreateFontSet(dpy, "fixed", &missing, &nmissing, &def);
|
||||||
|
if(missing != NULL)
|
||||||
|
XFreeStringList(missing);
|
||||||
|
if(fontset == NULL)
|
||||||
|
return fail("create XIM position font set");
|
||||||
|
spot.x = 7;
|
||||||
|
spot.y = 11;
|
||||||
|
pre = XVaCreateNestedList(0, XNSpotLocation, &spot,
|
||||||
|
XNFontSet, fontset, NULL);
|
||||||
|
if(pre == NULL){
|
||||||
|
XFreeFontSet(dpy, fontset);
|
||||||
|
return fail("create XNSpotLocation list");
|
||||||
|
}
|
||||||
|
ic = XCreateIC(im,
|
||||||
|
XNInputStyle, XIMPreeditPosition|XIMStatusNothing,
|
||||||
|
XNClientWindow, client,
|
||||||
|
XNFocusWindow, focus,
|
||||||
|
XNPreeditAttributes, pre,
|
||||||
|
NULL);
|
||||||
|
XFree(pre);
|
||||||
|
if(ic == NULL){
|
||||||
|
XFreeFontSet(dpy, fontset);
|
||||||
|
return fail("create nested PreeditPosition input context");
|
||||||
|
}
|
||||||
|
XSetICFocus(ic);
|
||||||
|
if(!sendkey(dpy, ic, focus, XK_s, ControlMask, commit, sizeof commit) ||
|
||||||
|
!sendkey(dpy, ic, focus, XK_r, 0, commit, sizeof commit)){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
XFreeFontSet(dpy, fontset);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
spot.x = 13;
|
||||||
|
spot.y = 17;
|
||||||
|
pre = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
|
||||||
|
if(pre == NULL || XSetICValues(ic, XNPreeditAttributes, pre, NULL) != NULL){
|
||||||
|
if(pre != NULL)
|
||||||
|
XFree(pre);
|
||||||
|
XDestroyIC(ic);
|
||||||
|
XFreeFontSet(dpy, fontset);
|
||||||
|
return fail("update nested XNSpotLocation");
|
||||||
|
}
|
||||||
|
XFree(pre);
|
||||||
|
if(!sendkey(dpy, ic, focus, XK_Escape, 0, commit, sizeof commit)){
|
||||||
|
XDestroyIC(ic);
|
||||||
|
XFreeFontSet(dpy, fontset);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
XUnsetICFocus(ic);
|
||||||
|
XDestroyIC(ic);
|
||||||
|
XFreeFontSet(dpy, fontset);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
Display *dpy;
|
||||||
|
Window client, focus, root;
|
||||||
|
XIM im;
|
||||||
|
Run run;
|
||||||
|
int ok;
|
||||||
|
char *xvfb;
|
||||||
|
|
||||||
|
if(argc != 3 && argc != 4){
|
||||||
|
fprintf(stderr, "usage: xim_live_test daemon mapdir [Xvfb]\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
memset(&run, 0, sizeof run);
|
||||||
|
run.xvfb.pid = run.daemon.pid = -1;
|
||||||
|
run.xvfb.pgid = run.daemon.pgid = -1;
|
||||||
|
xvfb = argc == 4 ? argv[3] : "Xvfb";
|
||||||
|
ok = start(&run, argv[1], argv[2], xvfb);
|
||||||
|
if(!ok){
|
||||||
|
cleanup(&run, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if(setlocale(LC_CTYPE, "C.UTF-8") == NULL || !XSupportsLocale() ||
|
||||||
|
XSetLocaleModifiers("@im=strans") == NULL){
|
||||||
|
fail("initialize UTF-8 X locale for strans");
|
||||||
|
cleanup(&run, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
XSetErrorHandler(xerr);
|
||||||
|
dpy = XOpenDisplay(run.display);
|
||||||
|
if(dpy == NULL){
|
||||||
|
fail("open private display %s", run.display);
|
||||||
|
cleanup(&run, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
im = openim(dpy);
|
||||||
|
if(im == NULL){
|
||||||
|
fail("open strans XIM on private display %s", run.display);
|
||||||
|
XCloseDisplay(dpy);
|
||||||
|
cleanup(&run, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
root = DefaultRootWindow(dpy);
|
||||||
|
client = XCreateSimpleWindow(dpy, root, 41, 53, 240, 100, 0, 0, 0);
|
||||||
|
focus = XCreateSimpleWindow(dpy, client, 17, 19, 160, 50, 0, 0, 0);
|
||||||
|
XSelectInput(dpy, client, KeyPressMask|StructureNotifyMask);
|
||||||
|
XSelectInput(dpy, focus, KeyPressMask|StructureNotifyMask);
|
||||||
|
XMapWindow(dpy, client);
|
||||||
|
XMapWindow(dpy, focus);
|
||||||
|
XSync(dpy, False);
|
||||||
|
ok = testcallbacks(dpy, im, client) &&
|
||||||
|
testnothing(dpy, im, client) &&
|
||||||
|
testposition(dpy, im, client, focus);
|
||||||
|
XDestroyWindow(dpy, focus);
|
||||||
|
XDestroyWindow(dpy, client);
|
||||||
|
XCloseIM(im);
|
||||||
|
XSync(dpy, False);
|
||||||
|
XCloseDisplay(dpy);
|
||||||
|
if(xerror != 0){
|
||||||
|
fail("observed %d X protocol errors", xerror);
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
cleanup(&run, !ok);
|
||||||
|
if(!ok)
|
||||||
|
return 1;
|
||||||
|
printf("xim_live_test: ok\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user