The popup window is created once at startup and only mapped afterwards, and MapWindow does not restack, so every application window (or WM frame) opened after the daemon sat above it: candidates and preedit were invisible in those windows until the daemon restarted. Configure the window above its siblings on every show. The XIM live test now checks the popup stacks above a client window created after the daemon.
823 lines
18 KiB
C
823 lines
18 KiB
C
#define _GNU_SOURCE
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/keysym.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <locale.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include "ipc.h"
|
|
#include "live.h"
|
|
|
|
enum
|
|
{
|
|
Eventtimeout = 4000,
|
|
};
|
|
|
|
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
|
|
{
|
|
Live l;
|
|
Child xvfb;
|
|
Child daemon;
|
|
char display[32];
|
|
};
|
|
|
|
struct Prelog
|
|
{
|
|
int start;
|
|
int nonempty;
|
|
int empty;
|
|
int done;
|
|
int doneafterempty;
|
|
int badorder;
|
|
};
|
|
|
|
static int xerror;
|
|
|
|
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
|
|
spawndaemon(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->l.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;
|
|
if(!livesetup(&r->l, "xim"))
|
|
return 0;
|
|
if(snprintf(r->xvfb.log, sizeof r->xvfb.log, "%s/xvfb.log", r->l.root)
|
|
>= (int)sizeof r->xvfb.log ||
|
|
snprintf(r->daemon.log, sizeof r->daemon.log, "%s/daemon.log", r->l.root)
|
|
>= (int)sizeof r->daemon.log)
|
|
return fail("temporary path is too long");
|
|
if(!spawnxvfb(r, xvfb))
|
|
return 0;
|
|
if(setenv("DISPLAY", r->display, 1) < 0 ||
|
|
setenv("XMODIFIERS", "@im=strans", 1) < 0 ||
|
|
setenv("XDG_RUNTIME_DIR", r->l.runtime, 1) < 0 ||
|
|
setenv("XDG_CONFIG_HOME", r->l.config, 1) < 0 ||
|
|
setenv("HOME", r->l.home, 1) < 0 ||
|
|
unsetenv("DBUS_SESSION_BUS_ADDRESS") < 0 ||
|
|
unsetenv("IBUS_ADDRESS") < 0)
|
|
return fail("set private environment: %s", strerror(errno));
|
|
if(!spawndaemon(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
|
|
cleanup(Run *r, int noisy)
|
|
{
|
|
stopchild(&r->daemon);
|
|
stopchild(&r->xvfb);
|
|
if(noisy){
|
|
fprintf(stderr, "%s: daemon log:\n", testname);
|
|
showlog(r->daemon.log);
|
|
fprintf(stderr, "%s: Xvfb log:\n", testname);
|
|
showlog(r->xvfb.log);
|
|
}
|
|
if(r->daemon.log[0] != '\0')
|
|
unlink(r->daemon.log);
|
|
if(r->xvfb.log[0] != '\0')
|
|
unlink(r->xvfb.log);
|
|
liveclean(&r->l);
|
|
}
|
|
|
|
static int
|
|
xerr(Display *dpy, XErrorEvent *e)
|
|
{
|
|
char msg[128];
|
|
|
|
xerror++;
|
|
XGetErrorText(dpy, e->error_code, msg, sizeof msg);
|
|
fail("X error: %s (request %d.%d)", 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
|
|
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;
|
|
|
|
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);
|
|
}
|
|
reset = Xutf8ResetIC(ic);
|
|
if(reset == NULL || strcmp(reset, "ㄱ") != 0){
|
|
if(reset != NULL)
|
|
XFree(reset);
|
|
XDestroyIC(ic);
|
|
return fail("ResetIC did not return pending preedit");
|
|
}
|
|
XFree(reset);
|
|
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("input context did not compose after ResetIC");
|
|
}
|
|
if(strcmp(commit, "가") != 0){
|
|
XDestroyIC(ic);
|
|
return fail("post-reset composition did not contain 가");
|
|
}
|
|
XUnsetICFocus(ic);
|
|
XDestroyIC(ic);
|
|
pump(dpy, 100);
|
|
if(log.badorder != 0)
|
|
return fail("bad callback order after ResetIC");
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* The popup is the one viewable override-redirect window on the private
|
|
* display; created before the client window, it must still stack above it.
|
|
*/
|
|
static int
|
|
popupabove(Display *dpy, Window client)
|
|
{
|
|
XWindowAttributes wa;
|
|
Window root, parent, *kids;
|
|
unsigned i, n;
|
|
int64_t deadline;
|
|
int above;
|
|
|
|
deadline = nowms() + Eventtimeout;
|
|
for(;;){
|
|
above = 0;
|
|
root = DefaultRootWindow(dpy);
|
|
if(!XQueryTree(dpy, root, &root, &parent, &kids, &n))
|
|
return fail("query the window tree");
|
|
for(i = 0; i < n; i++){
|
|
if(kids[i] == client)
|
|
above = 1;
|
|
else if(XGetWindowAttributes(dpy, kids[i], &wa) &&
|
|
wa.override_redirect && wa.map_state == IsViewable)
|
|
break;
|
|
}
|
|
XFree(kids);
|
|
if(i < n)
|
|
return above || fail("popup is stacked below the client");
|
|
if(leftms(deadline) == 0)
|
|
return fail("popup did not appear");
|
|
pump(dpy, 20);
|
|
}
|
|
}
|
|
|
|
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) ||
|
|
!popupabove(dpy, win) ||
|
|
!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;
|
|
|
|
testname = "xim_live_test";
|
|
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;
|
|
}
|