gtk: honor preedit policy and cursor placement
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
||||
/tests/*.o
|
||||
/tests/unit_test
|
||||
/tests/ibus_live_test
|
||||
/tests/gtk_live_test
|
||||
/tests/ipc_live_test
|
||||
/tests/daemon_collision_test
|
||||
/tests/daemon_failure_test
|
||||
|
||||
@@ -18,6 +18,7 @@ RUN pacman -Syu --noconfirm --needed \
|
||||
ttf-dejavu \
|
||||
ttf-jigmo \
|
||||
which \
|
||||
xorg-server-xvfb \
|
||||
xcb-imdkit \
|
||||
dbus \
|
||||
&& pacman -Scc --noconfirm
|
||||
|
||||
336
gtk/main.c
336
gtk/main.c
@@ -1,8 +1,12 @@
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <gtk/gtk.h>
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
#include "ipc.h"
|
||||
|
||||
typedef struct Im Im;
|
||||
@@ -10,8 +14,20 @@ struct Im
|
||||
{
|
||||
GtkIMContext parent;
|
||||
int fd;
|
||||
int usepreedit;
|
||||
int effective;
|
||||
int ext;
|
||||
int private;
|
||||
char pre[Ipcfieldmax+1];
|
||||
int prelen;
|
||||
GdkWindow *win;
|
||||
GdkRectangle cursor;
|
||||
int cursorvalid;
|
||||
int caretsent;
|
||||
int sentvalid;
|
||||
int32_t sentx;
|
||||
int32_t senty;
|
||||
int32_t senth;
|
||||
};
|
||||
|
||||
typedef struct ImClass ImClass;
|
||||
@@ -23,48 +39,163 @@ struct ImClass
|
||||
static GType imtype;
|
||||
|
||||
static void
|
||||
srvconnect(Im *im)
|
||||
setpreedit(Im *im, const char *s, int n)
|
||||
{
|
||||
if(im->fd >= 0)
|
||||
int was;
|
||||
|
||||
if(n < 0 || n > Ipcfieldmax)
|
||||
n = 0;
|
||||
if(n == im->prelen && memcmp(im->pre, s, n) == 0)
|
||||
return;
|
||||
im->fd = ipcconnect();
|
||||
was = im->prelen;
|
||||
if(n > 0)
|
||||
memcpy(im->pre, s, n);
|
||||
im->pre[n] = '\0';
|
||||
im->prelen = n;
|
||||
if(was == 0 && n > 0)
|
||||
g_signal_emit_by_name(im, "preedit-start");
|
||||
g_signal_emit_by_name(im, "preedit-changed");
|
||||
if(was > 0 && n == 0)
|
||||
g_signal_emit_by_name(im, "preedit-end");
|
||||
}
|
||||
|
||||
static void
|
||||
srvclose(Im *im)
|
||||
{
|
||||
int was;
|
||||
|
||||
if(im->fd >= 0)
|
||||
close(im->fd);
|
||||
im->fd = -1;
|
||||
was = im->prelen;
|
||||
im->pre[0] = '\0';
|
||||
im->prelen = 0;
|
||||
if(was > 0){
|
||||
g_signal_emit_by_name(im, "preedit-changed");
|
||||
g_signal_emit_by_name(im, "preedit-end");
|
||||
}
|
||||
im->ext = 0;
|
||||
im->effective = 1;
|
||||
im->caretsent = 0;
|
||||
setpreedit(im, "", 0);
|
||||
}
|
||||
|
||||
static int
|
||||
readresp(Im *im, char *buf, int bufsz)
|
||||
readresp(Im *im, int want, int update, char *commit, int ncommit,
|
||||
Ipcresp *resp)
|
||||
{
|
||||
Ipcresp resp;
|
||||
int was;
|
||||
char pre[Ipcfieldmax+1];
|
||||
|
||||
was = im->prelen;
|
||||
if(ipcreadresp(im->fd, 1, buf, bufsz, im->pre,
|
||||
sizeof(im->pre), &resp) < 0)
|
||||
if(ipcreadresp(im->fd, want, commit, ncommit, pre, sizeof pre,
|
||||
resp) < 0)
|
||||
return -1;
|
||||
im->prelen = resp.npreedit;
|
||||
if(was == 0 && im->prelen > 0)
|
||||
g_signal_emit_by_name(im, "preedit-start");
|
||||
if(was != 0 || im->prelen != 0)
|
||||
g_signal_emit_by_name(im, "preedit-changed");
|
||||
if(was > 0 && im->prelen == 0)
|
||||
g_signal_emit_by_name(im, "preedit-end");
|
||||
return resp.eaten;
|
||||
if(update)
|
||||
setpreedit(im, pre, resp->npreedit);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
dropwindow(Im *im)
|
||||
{
|
||||
if(im->win != NULL)
|
||||
g_object_unref(im->win);
|
||||
im->win = NULL;
|
||||
im->cursorvalid = 0;
|
||||
}
|
||||
|
||||
static int
|
||||
caretget(Im *im, int *valid, int32_t *x, int32_t *y, int32_t *h)
|
||||
{
|
||||
gint rx, ry, scale;
|
||||
int64_t sx, sy, sh;
|
||||
|
||||
*valid = 0;
|
||||
*x = *y = *h = 0;
|
||||
if(im->win != NULL && gdk_window_is_destroyed(im->win))
|
||||
dropwindow(im);
|
||||
if(!im->cursorvalid || im->win == NULL){
|
||||
im->cursorvalid = 0;
|
||||
return 0;
|
||||
}
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
if(!GDK_IS_X11_WINDOW(im->win)){
|
||||
im->cursorvalid = 0;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
im->cursorvalid = 0;
|
||||
return 0;
|
||||
#endif
|
||||
if(im->cursor.height < 0){
|
||||
im->cursorvalid = 0;
|
||||
return 0;
|
||||
}
|
||||
scale = gdk_window_get_scale_factor(im->win);
|
||||
if(scale <= 0){
|
||||
im->cursorvalid = 0;
|
||||
return 0;
|
||||
}
|
||||
/* GDK translates the client-relative point before device scaling. */
|
||||
gdk_window_get_root_coords(im->win, 0, 0, &rx, &ry);
|
||||
sx = ((int64_t)rx + im->cursor.x) * scale;
|
||||
sy = ((int64_t)ry + im->cursor.y) * scale;
|
||||
sh = (int64_t)im->cursor.height * scale;
|
||||
if(sx < INT32_MIN || sx > INT32_MAX ||
|
||||
sy < INT32_MIN || sy > INT32_MAX || sh > INT32_MAX){
|
||||
im->cursorvalid = 0;
|
||||
return 0;
|
||||
}
|
||||
*valid = 1;
|
||||
*x = sx;
|
||||
*y = sy;
|
||||
*h = sh;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
sendcaret(Im *im)
|
||||
{
|
||||
unsigned char buf[Ipccaretsz];
|
||||
int valid;
|
||||
int32_t x, y, h;
|
||||
|
||||
if(im->fd < 0 || !im->ext)
|
||||
return 0;
|
||||
caretget(im, &valid, &x, &y, &h);
|
||||
if(im->caretsent && valid == im->sentvalid &&
|
||||
(!valid || (x == im->sentx && y == im->senty && h == im->senth)))
|
||||
return 0;
|
||||
if(ipcpackcaret(buf, valid, x, y, h) < 0 ||
|
||||
ipcsend(im->fd, buf, sizeof buf) < 0)
|
||||
return -1;
|
||||
im->caretsent = 1;
|
||||
im->sentvalid = valid;
|
||||
im->sentx = x;
|
||||
im->senty = y;
|
||||
im->senth = h;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
srvconnect(Im *im)
|
||||
{
|
||||
unsigned char buf[Ipcreqsz];
|
||||
char commit[Ipcfieldmax+1];
|
||||
Ipcresp resp;
|
||||
|
||||
if(im->fd >= 0)
|
||||
return 0;
|
||||
im->fd = ipcconnect();
|
||||
if(im->fd < 0)
|
||||
return -1;
|
||||
/* Marker zero is an old daemon's harmless key-zero response. */
|
||||
ipcpackcap(buf, im->usepreedit);
|
||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||
readresp(im, im->usepreedit, im->usepreedit, commit,
|
||||
sizeof commit, &resp) < 0){
|
||||
srvclose(im);
|
||||
return -1;
|
||||
}
|
||||
im->ext = resp.eaten != 0;
|
||||
im->effective = im->ext ? im->usepreedit : 1;
|
||||
if(!im->effective)
|
||||
setpreedit(im, "", 0);
|
||||
if(im->ext && sendcaret(im) < 0){
|
||||
srvclose(im);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
@@ -101,16 +232,21 @@ static void
|
||||
sendreset(Im *im)
|
||||
{
|
||||
unsigned char buf[Ipcreqsz];
|
||||
char resp[Ipcfieldmax+1];
|
||||
char commit[Ipcfieldmax+1];
|
||||
Ipcresp resp;
|
||||
|
||||
if(im->fd < 0){
|
||||
setpreedit(im, "", 0);
|
||||
return;
|
||||
}
|
||||
ipcpackreset(buf, im->effective);
|
||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||
readresp(im, im->effective, 0, commit,
|
||||
sizeof commit, &resp) < 0){
|
||||
srvclose(im);
|
||||
return;
|
||||
}
|
||||
ipcpackreset(buf, 1);
|
||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||
readresp(im, resp, sizeof(resp)) < 0)
|
||||
srvclose(im);
|
||||
setpreedit(im, "", 0);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@@ -134,10 +270,9 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
||||
{
|
||||
Im *im;
|
||||
unsigned char buf[Ipcreqsz];
|
||||
char resp[Ipcfieldmax+1];
|
||||
char commit[Ipcfieldmax+1];
|
||||
uint32_t key, mod;
|
||||
int r;
|
||||
GtkInputPurpose purpose;
|
||||
Ipcresp resp;
|
||||
|
||||
im = (Im*)ctx;
|
||||
if(ev->type != GDK_KEY_PRESS)
|
||||
@@ -146,27 +281,25 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
||||
if(key == 0)
|
||||
return FALSE;
|
||||
mod = mget(ev->state);
|
||||
g_object_get(ctx, "input-purpose", &purpose, NULL);
|
||||
if(purpose == GTK_INPUT_PURPOSE_PASSWORD || purpose == GTK_INPUT_PURPOSE_PIN)
|
||||
if(im->private)
|
||||
return plaincommit(ctx, key, mod);
|
||||
srvconnect(im);
|
||||
if(im->fd < 0){
|
||||
if(srvconnect(im) < 0)
|
||||
return plaincommit(ctx, key, mod);
|
||||
/* A retained GDK window may have moved since the last cursor report. */
|
||||
if(sendcaret(im) < 0){
|
||||
srvclose(im);
|
||||
return plaincommit(ctx, key, mod);
|
||||
}
|
||||
ipcpackreq(buf, 1, mod, key);
|
||||
if(ipcsend(im->fd, buf, sizeof buf) < 0){
|
||||
ipcpackreq(buf, im->effective, mod, key);
|
||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||
readresp(im, im->effective, im->effective, commit,
|
||||
sizeof commit, &resp) < 0){
|
||||
srvclose(im);
|
||||
return plaincommit(ctx, key, mod);
|
||||
}
|
||||
r = readresp(im, resp, sizeof(resp));
|
||||
if(r < 0){
|
||||
srvclose(im);
|
||||
return plaincommit(ctx, key, mod);
|
||||
}
|
||||
if(resp[0] != '\0')
|
||||
g_signal_emit_by_name(ctx, "commit", resp);
|
||||
if(r != 0)
|
||||
if(commit[0] != '\0')
|
||||
g_signal_emit_by_name(ctx, "commit", commit);
|
||||
if(resp.eaten)
|
||||
return TRUE;
|
||||
return plaincommit(ctx, key, mod);
|
||||
}
|
||||
@@ -211,23 +344,121 @@ focusout(GtkIMContext *ctx)
|
||||
srvclose(im);
|
||||
}
|
||||
|
||||
static void
|
||||
setusepreedit(GtkIMContext *ctx, gboolean use)
|
||||
{
|
||||
Im *im;
|
||||
unsigned char buf[Ipcreqsz];
|
||||
char commit[Ipcfieldmax+1];
|
||||
Ipcresp resp;
|
||||
|
||||
im = (Im*)ctx;
|
||||
use = use != FALSE;
|
||||
if(im->usepreedit == use)
|
||||
return;
|
||||
im->usepreedit = use;
|
||||
if(im->fd < 0 || !im->ext){
|
||||
im->effective = im->ext ? use : 1;
|
||||
return;
|
||||
}
|
||||
ipcpackcap(buf, use);
|
||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||
readresp(im, use, use, commit, sizeof commit, &resp) < 0 ||
|
||||
!resp.eaten){
|
||||
srvclose(im);
|
||||
return;
|
||||
}
|
||||
im->effective = use;
|
||||
if(!use)
|
||||
setpreedit(im, "", 0);
|
||||
}
|
||||
|
||||
static void
|
||||
setclientwindow(GtkIMContext *ctx, GdkWindow *win)
|
||||
{
|
||||
Im *im;
|
||||
|
||||
im = (Im*)ctx;
|
||||
if(win != NULL && gdk_window_is_destroyed(win))
|
||||
win = NULL;
|
||||
if(im->win == win)
|
||||
return;
|
||||
if(win != NULL)
|
||||
g_object_ref(win);
|
||||
dropwindow(im);
|
||||
im->win = win;
|
||||
if(sendcaret(im) < 0)
|
||||
srvclose(im);
|
||||
}
|
||||
|
||||
static void
|
||||
setcursorlocation(GtkIMContext *ctx, GdkRectangle *area)
|
||||
{
|
||||
Im *im;
|
||||
|
||||
im = (Im*)ctx;
|
||||
if(im->win != NULL && gdk_window_is_destroyed(im->win))
|
||||
dropwindow(im);
|
||||
if(area == NULL || im->win == NULL)
|
||||
im->cursorvalid = 0;
|
||||
else{
|
||||
im->cursor = *area;
|
||||
im->cursorvalid = 1;
|
||||
}
|
||||
if(sendcaret(im) < 0)
|
||||
srvclose(im);
|
||||
}
|
||||
|
||||
static int
|
||||
isprivate(GtkInputPurpose purpose)
|
||||
{
|
||||
return purpose == GTK_INPUT_PURPOSE_PASSWORD ||
|
||||
purpose == GTK_INPUT_PURPOSE_PIN;
|
||||
}
|
||||
|
||||
static void
|
||||
purposechanged(GObject *obj, GParamSpec *pspec, gpointer data)
|
||||
{
|
||||
Im *im;
|
||||
GtkInputPurpose purpose;
|
||||
int private;
|
||||
|
||||
(void)pspec;
|
||||
(void)data;
|
||||
im = (Im*)obj;
|
||||
g_object_get(obj, "input-purpose", &purpose, NULL);
|
||||
private = isprivate(purpose);
|
||||
if(private == im->private)
|
||||
return;
|
||||
im->private = private;
|
||||
if(private)
|
||||
sendreset(im);
|
||||
}
|
||||
|
||||
static void
|
||||
finalize(GObject *obj)
|
||||
{
|
||||
Im *im;
|
||||
|
||||
im = (Im*)obj;
|
||||
if(im->fd >= 0)
|
||||
close(im->fd);
|
||||
srvclose(im);
|
||||
dropwindow(im);
|
||||
G_OBJECT_CLASS(g_type_class_peek_parent(G_OBJECT_GET_CLASS(obj)))->finalize(obj);
|
||||
}
|
||||
|
||||
static void
|
||||
init(Im *im)
|
||||
{
|
||||
GtkInputPurpose purpose;
|
||||
|
||||
im->fd = -1;
|
||||
im->usepreedit = 1;
|
||||
im->effective = 1;
|
||||
im->pre[0] = '\0';
|
||||
im->prelen = 0;
|
||||
g_object_get(im, "input-purpose", &purpose, NULL);
|
||||
im->private = isprivate(purpose);
|
||||
g_signal_connect(im, "notify::input-purpose",
|
||||
G_CALLBACK(purposechanged), NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -242,6 +473,9 @@ classinit(ImClass *klass)
|
||||
ic->get_preedit_string = getpreedit;
|
||||
ic->reset = reset;
|
||||
ic->focus_out = focusout;
|
||||
ic->set_use_preedit = setusepreedit;
|
||||
ic->set_client_window = setclientwindow;
|
||||
ic->set_cursor_location = setcursorlocation;
|
||||
oc->finalize = finalize;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ TEXT_CFLAGS = $(shell pkg-config --cflags pangocairo cairo)
|
||||
TEXT_LIBS = $(shell pkg-config --libs pangocairo cairo)
|
||||
DBUS_CFLAGS = $(shell pkg-config --cflags dbus-1)
|
||||
DBUS_LIBS = $(shell pkg-config --libs dbus-1)
|
||||
GTK_CFLAGS = $(shell pkg-config --cflags gtk+-3.0)
|
||||
GTK_LIBS = $(shell pkg-config --libs gtk+-3.0)
|
||||
IBUS_CFLAGS = $(shell pkg-config --cflags dbus-1 xkbcommon)
|
||||
IBUS_LIBS = $(shell pkg-config --libs dbus-1 xkbcommon)
|
||||
XIM_CFLAGS = $(shell pkg-config --cflags xcb-imdkit xkbcommon)
|
||||
@@ -13,7 +15,7 @@ XIM_LIBS = $(shell pkg-config --libs xcb-imdkit xkbcommon)
|
||||
LIBS = -lthread -lbio $(TEXT_LIBS) $(IBUS_LIBS) $(XIM_LIBS)
|
||||
|
||||
PROG = unit_test
|
||||
LIVE = ibus_live_test ipc_live_test daemon_collision_test daemon_failure_test \
|
||||
LIVE = ibus_live_test gtk_live_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 \
|
||||
ko_test.c vi_test.c engine_test.c dict_test.c ipc_test.c \
|
||||
@@ -30,6 +32,7 @@ all: $(PROG) $(LIVE)
|
||||
check test: $(PROG) $(LIVE)
|
||||
./$(PROG) $(TESTARGS)
|
||||
./ibus_live_test ../strans ../map
|
||||
./gtk_live_test ../gtk/im-strans.so
|
||||
./ipc_live_test ../strans ../map
|
||||
./daemon_collision_test ../strans ../map
|
||||
./daemon_failure_test ../strans
|
||||
@@ -41,6 +44,10 @@ $(PROG): $(OBJS)
|
||||
ibus_live_test: ibus_live_test.c
|
||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g $(DBUS_CFLAGS) -o $@ $< $(DBUS_LIBS)
|
||||
|
||||
gtk_live_test: gtk_live_test.c ../ipc.c ../ipc.h
|
||||
$(HOSTCC) -std=c99 -Wall -Wextra -O2 -g -I.. $(GTK_CFLAGS) -o $@ \
|
||||
gtk_live_test.c ../ipc.c $(GTK_LIBS) -pthread
|
||||
|
||||
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
|
||||
|
||||
|
||||
928
tests/gtk_live_test.c
Normal file
928
tests/gtk_live_test.c
Normal file
@@ -0,0 +1,928 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.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/un.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "ipc.h"
|
||||
|
||||
enum
|
||||
{
|
||||
Maxevent = 256,
|
||||
Timeout = 4000,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
Ecap,
|
||||
Ecaret,
|
||||
Ekey,
|
||||
Ereset,
|
||||
};
|
||||
|
||||
typedef struct Event Event;
|
||||
typedef struct Server Server;
|
||||
typedef struct Siglog Siglog;
|
||||
|
||||
struct Event
|
||||
{
|
||||
int type;
|
||||
int want;
|
||||
int valid;
|
||||
uint32_t key;
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
int32_t h;
|
||||
};
|
||||
|
||||
struct Server
|
||||
{
|
||||
pthread_t thread;
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t changed;
|
||||
int listenfd;
|
||||
int wake[2];
|
||||
int stop;
|
||||
int old;
|
||||
int nclose;
|
||||
char pre[Ipcfieldmax+1];
|
||||
Event ev[Maxevent];
|
||||
int nev;
|
||||
};
|
||||
|
||||
struct Siglog
|
||||
{
|
||||
char event[128];
|
||||
int n;
|
||||
char pre[Ipcfieldmax+1];
|
||||
char commit[Ipcfieldmax+1];
|
||||
};
|
||||
|
||||
static int
|
||||
fail(char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
fprintf(stderr, "gtk_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
|
||||
record(Server *s, Event *e)
|
||||
{
|
||||
pthread_mutex_lock(&s->lock);
|
||||
if(s->nev < Maxevent)
|
||||
s->ev[s->nev++] = *e;
|
||||
pthread_cond_broadcast(&s->changed);
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
closed(Server *s)
|
||||
{
|
||||
pthread_mutex_lock(&s->lock);
|
||||
s->nclose++;
|
||||
pthread_cond_broadcast(&s->changed);
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
}
|
||||
|
||||
static int
|
||||
sendresponse(Server *s, int fd, int eaten, int want)
|
||||
{
|
||||
unsigned char buf[Ipcmaxresp];
|
||||
char pre[Ipcfieldmax+1];
|
||||
int n;
|
||||
|
||||
pthread_mutex_lock(&s->lock);
|
||||
memcpy(pre, s->pre, sizeof pre);
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
n = ipcpackresp(buf, sizeof buf, eaten, "", 0, pre, strlen(pre), want);
|
||||
return n >= 0 && ipcsend(fd, buf, n) == 0;
|
||||
}
|
||||
|
||||
static int
|
||||
request(Server *s, int fd)
|
||||
{
|
||||
unsigned char buf[Ipccaretsz];
|
||||
uint32_t mod, key;
|
||||
Event e;
|
||||
int old, type, want;
|
||||
|
||||
if(ipcreadn(fd, buf, Ipcreqsz) < 0)
|
||||
return 0;
|
||||
type = ipcreqtype(buf);
|
||||
memset(&e, 0, sizeof e);
|
||||
switch(type){
|
||||
case Ipccap:
|
||||
e.type = Ecap;
|
||||
ipcunpackreq(buf, &e.want, &mod, &key);
|
||||
record(s, &e);
|
||||
pthread_mutex_lock(&s->lock);
|
||||
old = s->old;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return sendresponse(s, fd, !old, e.want);
|
||||
case Ipccaret:
|
||||
if(ipcreadn(fd, buf + Ipcreqsz, Ipccaretsz - Ipcreqsz) < 0 ||
|
||||
ipcunpackcaret(buf, &e.valid, &e.x, &e.y, &e.h) < 0)
|
||||
return 0;
|
||||
e.type = Ecaret;
|
||||
record(s, &e);
|
||||
return 1;
|
||||
case Ipclegacy:
|
||||
ipcunpackreq(buf, &want, &mod, &key);
|
||||
e.want = want;
|
||||
e.key = key;
|
||||
e.type = ipcreqreset(buf) ? Ereset : Ekey;
|
||||
record(s, &e);
|
||||
return sendresponse(s, fd, e.type == Ekey, want);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void*
|
||||
serverthread(void *v)
|
||||
{
|
||||
Server *s;
|
||||
struct pollfd pfd[3];
|
||||
int fd, n;
|
||||
char c;
|
||||
|
||||
s = v;
|
||||
fd = -1;
|
||||
for(;;){
|
||||
pfd[0].fd = s->wake[0];
|
||||
pfd[0].events = POLLIN;
|
||||
pfd[1].fd = s->listenfd;
|
||||
pfd[1].events = fd < 0 ? POLLIN : 0;
|
||||
pfd[2].fd = fd;
|
||||
pfd[2].events = fd >= 0 ? POLLIN : 0;
|
||||
n = poll(pfd, 3, -1);
|
||||
if(n < 0 && errno == EINTR)
|
||||
continue;
|
||||
if(n < 0 || (pfd[0].revents & POLLIN))
|
||||
break;
|
||||
if(pfd[1].revents & POLLIN){
|
||||
fd = accept(s->listenfd, NULL, NULL);
|
||||
continue;
|
||||
}
|
||||
if(fd >= 0 && pfd[2].revents != 0){
|
||||
if((pfd[2].revents & POLLIN) && request(s, fd))
|
||||
continue;
|
||||
close(fd);
|
||||
fd = -1;
|
||||
closed(s);
|
||||
}
|
||||
}
|
||||
if(fd >= 0){
|
||||
close(fd);
|
||||
closed(s);
|
||||
}
|
||||
while(read(s->wake[0], &c, 1) < 0 && errno == EINTR)
|
||||
;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
startserver(Server *s, char *path)
|
||||
{
|
||||
struct sockaddr_un addr;
|
||||
int cond;
|
||||
|
||||
memset(s, 0, sizeof *s);
|
||||
s->listenfd = -1;
|
||||
s->wake[0] = s->wake[1] = -1;
|
||||
if(pthread_mutex_init(&s->lock, NULL) != 0)
|
||||
return fail("initialize server lock");
|
||||
cond = pthread_cond_init(&s->changed, NULL) == 0;
|
||||
if(!cond){
|
||||
fail("initialize server condition");
|
||||
goto bad;
|
||||
}
|
||||
if(pipe(s->wake) < 0){
|
||||
fail("pipe: %s", strerror(errno));
|
||||
goto bad;
|
||||
}
|
||||
s->listenfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if(s->listenfd < 0){
|
||||
fail("socket: %s", strerror(errno));
|
||||
goto bad;
|
||||
}
|
||||
memset(&addr, 0, sizeof addr);
|
||||
addr.sun_family = AF_UNIX;
|
||||
if(strlen(path) >= sizeof addr.sun_path){
|
||||
fail("socket path is too long");
|
||||
goto bad;
|
||||
}
|
||||
strcpy(addr.sun_path, path);
|
||||
if(bind(s->listenfd, (struct sockaddr*)&addr, sizeof addr) < 0 ||
|
||||
listen(s->listenfd, 4) < 0){
|
||||
fail("listen %s: %s", path, strerror(errno));
|
||||
goto bad;
|
||||
}
|
||||
if(pthread_create(&s->thread, NULL, serverthread, s) != 0){
|
||||
fail("create server thread");
|
||||
goto bad;
|
||||
}
|
||||
return 1;
|
||||
bad:
|
||||
if(s->listenfd >= 0)
|
||||
close(s->listenfd);
|
||||
if(s->wake[0] >= 0)
|
||||
close(s->wake[0]);
|
||||
if(s->wake[1] >= 0)
|
||||
close(s->wake[1]);
|
||||
if(cond)
|
||||
pthread_cond_destroy(&s->changed);
|
||||
pthread_mutex_destroy(&s->lock);
|
||||
s->listenfd = s->wake[0] = s->wake[1] = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
stopserver(Server *s)
|
||||
{
|
||||
char c;
|
||||
|
||||
if(s->wake[1] >= 0){
|
||||
c = 0;
|
||||
while(write(s->wake[1], &c, 1) < 0 && errno == EINTR)
|
||||
;
|
||||
pthread_join(s->thread, NULL);
|
||||
}
|
||||
if(s->listenfd >= 0)
|
||||
close(s->listenfd);
|
||||
if(s->wake[0] >= 0)
|
||||
close(s->wake[0]);
|
||||
if(s->wake[1] >= 0)
|
||||
close(s->wake[1]);
|
||||
pthread_cond_destroy(&s->changed);
|
||||
pthread_mutex_destroy(&s->lock);
|
||||
s->listenfd = s->wake[0] = s->wake[1] = -1;
|
||||
}
|
||||
|
||||
static void
|
||||
setpre(Server *s, char *pre)
|
||||
{
|
||||
pthread_mutex_lock(&s->lock);
|
||||
snprintf(s->pre, sizeof s->pre, "%s", pre);
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
setold(Server *s, int old)
|
||||
{
|
||||
pthread_mutex_lock(&s->lock);
|
||||
s->old = old;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
}
|
||||
|
||||
static int
|
||||
eventcount(Server *s)
|
||||
{
|
||||
int n;
|
||||
|
||||
pthread_mutex_lock(&s->lock);
|
||||
n = s->nev;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return n;
|
||||
}
|
||||
|
||||
static int
|
||||
closecount(Server *s)
|
||||
{
|
||||
int n;
|
||||
|
||||
pthread_mutex_lock(&s->lock);
|
||||
n = s->nclose;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return n;
|
||||
}
|
||||
|
||||
static int
|
||||
waitcount(Server *s, int n)
|
||||
{
|
||||
struct timespec ts;
|
||||
int r;
|
||||
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += Timeout / 1000;
|
||||
ts.tv_nsec += (Timeout % 1000) * 1000000;
|
||||
if(ts.tv_nsec >= 1000000000){
|
||||
ts.tv_sec++;
|
||||
ts.tv_nsec -= 1000000000;
|
||||
}
|
||||
pthread_mutex_lock(&s->lock);
|
||||
while(s->nev < n){
|
||||
r = pthread_cond_timedwait(&s->changed, &s->lock, &ts);
|
||||
if(r == ETIMEDOUT)
|
||||
break;
|
||||
}
|
||||
r = s->nev >= n;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int
|
||||
waitclose(Server *s, int n)
|
||||
{
|
||||
struct timespec ts;
|
||||
int r;
|
||||
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += Timeout / 1000;
|
||||
pthread_mutex_lock(&s->lock);
|
||||
while(s->nclose < n){
|
||||
r = pthread_cond_timedwait(&s->changed, &s->lock, &ts);
|
||||
if(r == ETIMEDOUT)
|
||||
break;
|
||||
}
|
||||
r = s->nclose >= n;
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
static Event
|
||||
getevent(Server *s, int n)
|
||||
{
|
||||
Event e;
|
||||
|
||||
memset(&e, 0, sizeof e);
|
||||
pthread_mutex_lock(&s->lock);
|
||||
if(n >= 0 && n < s->nev)
|
||||
e = s->ev[n];
|
||||
pthread_mutex_unlock(&s->lock);
|
||||
return e;
|
||||
}
|
||||
|
||||
static int
|
||||
runquery(char *module, char *cache)
|
||||
{
|
||||
int fd, status;
|
||||
pid_t pid;
|
||||
|
||||
fd = open(cache, O_WRONLY|O_CREAT|O_TRUNC, 0600);
|
||||
if(fd < 0)
|
||||
return fail("open module cache: %s", strerror(errno));
|
||||
pid = fork();
|
||||
if(pid < 0){
|
||||
close(fd);
|
||||
return fail("fork gtk-query-immodules: %s", strerror(errno));
|
||||
}
|
||||
if(pid == 0){
|
||||
dup2(fd, STDOUT_FILENO);
|
||||
close(fd);
|
||||
execlp("gtk-query-immodules-3.0", "gtk-query-immodules-3.0",
|
||||
module, (char*)NULL);
|
||||
_exit(127);
|
||||
}
|
||||
close(fd);
|
||||
if(waitpid(pid, &status, 0) != pid || !WIFEXITED(status) ||
|
||||
WEXITSTATUS(status) != 0)
|
||||
return fail("gtk-query-immodules-3.0 failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
startxvfb(pid_t *pidp, char *display, size_t ndisplay)
|
||||
{
|
||||
struct pollfd pfd;
|
||||
char fdarg[32], line[32];
|
||||
int p[2], n, status;
|
||||
pid_t pid;
|
||||
|
||||
if(pipe(p) < 0)
|
||||
return fail("Xvfb display pipe: %s", strerror(errno));
|
||||
pid = fork();
|
||||
if(pid < 0){
|
||||
close(p[0]);
|
||||
close(p[1]);
|
||||
return fail("fork Xvfb: %s", strerror(errno));
|
||||
}
|
||||
if(pid == 0){
|
||||
close(p[0]);
|
||||
snprintf(fdarg, sizeof fdarg, "%d", p[1]);
|
||||
execlp("Xvfb", "Xvfb", "-displayfd", fdarg, "-screen", "0",
|
||||
"1024x768x24", "-nolisten", "tcp", "-noreset", (char*)NULL);
|
||||
_exit(127);
|
||||
}
|
||||
close(p[1]);
|
||||
pfd.fd = p[0];
|
||||
pfd.events = POLLIN;
|
||||
if(poll(&pfd, 1, Timeout) <= 0){
|
||||
kill(pid, SIGTERM);
|
||||
waitpid(pid, NULL, 0);
|
||||
close(p[0]);
|
||||
return fail("Xvfb did not publish a display");
|
||||
}
|
||||
n = read(p[0], line, sizeof line - 1);
|
||||
close(p[0]);
|
||||
if(n <= 0){
|
||||
waitpid(pid, &status, 0);
|
||||
return fail("read Xvfb display number");
|
||||
}
|
||||
line[n] = '\0';
|
||||
line[strcspn(line, "\r\n")] = '\0';
|
||||
if(snprintf(display, ndisplay, ":%s", line) >= (int)ndisplay){
|
||||
kill(pid, SIGTERM);
|
||||
waitpid(pid, NULL, 0);
|
||||
return fail("Xvfb display number is too long");
|
||||
}
|
||||
*pidp = pid;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
stopxvfb(pid_t *pidp)
|
||||
{
|
||||
int status;
|
||||
int64_t deadline;
|
||||
|
||||
if(*pidp <= 0)
|
||||
return;
|
||||
kill(*pidp, SIGTERM);
|
||||
deadline = nowms() + Timeout;
|
||||
while(waitpid(*pidp, &status, WNOHANG) == 0 && leftms(deadline) > 0)
|
||||
g_usleep(10000);
|
||||
if(waitpid(*pidp, &status, WNOHANG) == 0){
|
||||
kill(*pidp, SIGKILL);
|
||||
waitpid(*pidp, &status, 0);
|
||||
}
|
||||
*pidp = -1;
|
||||
}
|
||||
|
||||
static void
|
||||
pump(void)
|
||||
{
|
||||
while(gtk_events_pending())
|
||||
gtk_main_iteration();
|
||||
}
|
||||
|
||||
static void
|
||||
logsig(Siglog *l, char c)
|
||||
{
|
||||
if(l->n + 1 < (int)sizeof l->event){
|
||||
l->event[l->n++] = c;
|
||||
l->event[l->n] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
prestart(GtkIMContext *ctx, Siglog *l)
|
||||
{
|
||||
(void)ctx;
|
||||
logsig(l, 'S');
|
||||
}
|
||||
|
||||
static void
|
||||
prechange(GtkIMContext *ctx, Siglog *l)
|
||||
{
|
||||
gchar *s;
|
||||
|
||||
gtk_im_context_get_preedit_string(ctx, &s, NULL, NULL);
|
||||
snprintf(l->pre, sizeof l->pre, "%s", s);
|
||||
g_free(s);
|
||||
logsig(l, 'C');
|
||||
}
|
||||
|
||||
static void
|
||||
preend(GtkIMContext *ctx, Siglog *l)
|
||||
{
|
||||
(void)ctx;
|
||||
logsig(l, 'E');
|
||||
}
|
||||
|
||||
static void
|
||||
commit(GtkIMContext *ctx, gchar *s, Siglog *l)
|
||||
{
|
||||
(void)ctx;
|
||||
snprintf(l->commit, sizeof l->commit, "%s", s);
|
||||
logsig(l, 'K');
|
||||
}
|
||||
|
||||
static void
|
||||
weakgone(void *v, GObject *obj)
|
||||
{
|
||||
int *gone;
|
||||
|
||||
(void)obj;
|
||||
gone = v;
|
||||
*gone = 1;
|
||||
}
|
||||
|
||||
static void
|
||||
clearlog(Siglog *l)
|
||||
{
|
||||
memset(l, 0, sizeof *l);
|
||||
}
|
||||
|
||||
static GtkIMContext*
|
||||
newcontext(Siglog *l)
|
||||
{
|
||||
GtkIMContext *ctx;
|
||||
|
||||
ctx = gtk_im_multicontext_new();
|
||||
gtk_im_multicontext_set_context_id(GTK_IM_MULTICONTEXT(ctx), "strans");
|
||||
g_signal_connect(ctx, "preedit-start", G_CALLBACK(prestart), l);
|
||||
g_signal_connect(ctx, "preedit-changed", G_CALLBACK(prechange), l);
|
||||
g_signal_connect(ctx, "preedit-end", G_CALLBACK(preend), l);
|
||||
g_signal_connect(ctx, "commit", G_CALLBACK(commit), l);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static int
|
||||
key(GtkIMContext *ctx, GdkWindow *win, guint keyval)
|
||||
{
|
||||
GdkEvent *ev;
|
||||
int r;
|
||||
|
||||
ev = gdk_event_new(GDK_KEY_PRESS);
|
||||
ev->key.window = g_object_ref(win);
|
||||
ev->key.send_event = TRUE;
|
||||
ev->key.time = GDK_CURRENT_TIME;
|
||||
ev->key.keyval = keyval;
|
||||
r = gtk_im_context_filter_keypress(ctx, &ev->key);
|
||||
gdk_event_free(ev);
|
||||
return r;
|
||||
}
|
||||
|
||||
static GtkWidget*
|
||||
newwindow(int x, int y)
|
||||
{
|
||||
GtkWidget *w;
|
||||
|
||||
w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_default_size(GTK_WINDOW(w), 240, 120);
|
||||
gtk_window_move(GTK_WINDOW(w), x, y);
|
||||
gtk_widget_show(w);
|
||||
pump();
|
||||
return w;
|
||||
}
|
||||
|
||||
#define Check(c, ...) do{ if(!(c)){ fail(__VA_ARGS__); goto out; } }while(0)
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
Server srv;
|
||||
Siglog log;
|
||||
GtkIMContext *ctx, *oldctx, *badctx;
|
||||
GtkWidget *top, *other;
|
||||
GdkWindow *win, *win2;
|
||||
GdkRectangle rect;
|
||||
Event e;
|
||||
char root[] = "/tmp/strans-gtk.XXXXXX";
|
||||
char runtime[320] = "", socket[384] = "", cache[384] = "";
|
||||
char display[32];
|
||||
char *module;
|
||||
pid_t xvfb;
|
||||
int first, i, ox, oy, scale, closes, gone, ok, rootmade, serverup;
|
||||
|
||||
ctx = oldctx = badctx = NULL;
|
||||
top = other = NULL;
|
||||
xvfb = -1;
|
||||
rootmade = 0;
|
||||
serverup = 0;
|
||||
ok = 0;
|
||||
if(argc != 2)
|
||||
return fail("usage: gtk_live_test /path/to/im-strans.so");
|
||||
module = realpath(argv[1], NULL);
|
||||
if(module == NULL)
|
||||
return fail("realpath %s: %s", argv[1], strerror(errno));
|
||||
Check(mkdtemp(root) != NULL, "mkdtemp: %s", strerror(errno));
|
||||
rootmade = 1;
|
||||
Check(snprintf(runtime, sizeof runtime, "%s/runtime", root) <
|
||||
(int)sizeof runtime, "runtime path is too long");
|
||||
Check(snprintf(socket, sizeof socket, "%s/strans.sock", runtime) <
|
||||
(int)sizeof socket, "socket path is too long");
|
||||
Check(snprintf(cache, sizeof cache, "%s/immodules.cache", root) <
|
||||
(int)sizeof cache, "cache path is too long");
|
||||
Check(mkdir(runtime, 0700) == 0, "mkdir runtime: %s", strerror(errno));
|
||||
Check(startxvfb(&xvfb, display, sizeof display), "start private Xvfb");
|
||||
Check(setenv("DISPLAY", display, 1) == 0 &&
|
||||
setenv("GDK_BACKEND", "x11", 1) == 0 &&
|
||||
setenv("GDK_SCALE", "2", 1) == 0 &&
|
||||
setenv("XDG_RUNTIME_DIR", runtime, 1) == 0,
|
||||
"set private environment: %s", strerror(errno));
|
||||
Check(runquery(module, cache), "generate GTK module cache");
|
||||
Check(setenv("GTK_IM_MODULE_FILE", cache, 1) == 0 &&
|
||||
setenv("GTK_IM_MODULE", "strans", 1) == 0,
|
||||
"set GTK module environment: %s", strerror(errno));
|
||||
Check(startserver(&srv, socket), "start fake daemon");
|
||||
serverup = 1;
|
||||
gtk_init(&argc, &argv);
|
||||
|
||||
clearlog(&log);
|
||||
ctx = newcontext(&log);
|
||||
top = newwindow(73, 41);
|
||||
win = gtk_widget_get_window(top);
|
||||
gtk_im_context_set_client_window(ctx, win);
|
||||
rect.x = 11;
|
||||
rect.y = 17;
|
||||
rect.width = 3;
|
||||
rect.height = 19;
|
||||
gtk_im_context_set_cursor_location(ctx, &rect);
|
||||
Check(eventcount(&srv) == 0, "cursor reporting opened the socket");
|
||||
setpre(&srv, "pre");
|
||||
Check(key(ctx, win, GDK_KEY_a), "initial key was not eaten");
|
||||
Check(waitcount(&srv, 3), "initial protocol frames timed out");
|
||||
e = getevent(&srv, 0);
|
||||
Check(e.type == Ecap && e.want == 1, "default preedit capability missing");
|
||||
e = getevent(&srv, 1);
|
||||
Check(e.type == Ecaret && e.valid, "cached caret was not negotiated");
|
||||
gdk_window_get_origin(win, &ox, &oy);
|
||||
Check(ox != 0 && oy != 0, "test window did not get a nonzero root origin");
|
||||
scale = gdk_window_get_scale_factor(win);
|
||||
Check(scale == 2, "private Xvfb did not honor GDK_SCALE=2");
|
||||
Check(e.x == (ox + rect.x) * scale &&
|
||||
e.y == (oy + rect.y) * scale && e.h == rect.height * scale,
|
||||
"caret conversion got %d,%d,%d want %d,%d,%d",
|
||||
e.x, e.y, e.h, (ox + rect.x) * scale,
|
||||
(oy + rect.y) * scale, rect.height * scale);
|
||||
e = getevent(&srv, 2);
|
||||
Check(e.type == Ekey && e.want == 1, "initial key framing changed");
|
||||
Check(strcmp(log.event, "SC") == 0 && strcmp(log.pre, "pre") == 0,
|
||||
"initial preedit signals were %s (%s)", log.event, log.pre);
|
||||
|
||||
clearlog(&log);
|
||||
first = eventcount(&srv);
|
||||
gtk_im_context_set_use_preedit(ctx, FALSE);
|
||||
Check(waitcount(&srv, first + 1), "disable capability timed out");
|
||||
e = getevent(&srv, first);
|
||||
Check(e.type == Ecap && e.want == 0, "disable capability frame missing");
|
||||
Check(strcmp(log.event, "CE") == 0 && log.pre[0] == '\0',
|
||||
"disable signal order was %s (%s)", log.event, log.pre);
|
||||
clearlog(&log);
|
||||
gtk_im_context_set_use_preedit(ctx, FALSE);
|
||||
Check(log.n == 0, "repeated disable emitted %s", log.event);
|
||||
first = eventcount(&srv);
|
||||
gtk_im_context_set_use_preedit(ctx, TRUE);
|
||||
Check(waitcount(&srv, first + 1), "enable capability timed out");
|
||||
e = getevent(&srv, first);
|
||||
Check(e.type == Ecap && e.want == 1, "enable capability frame missing");
|
||||
Check(strcmp(log.event, "SC") == 0 && strcmp(log.pre, "pre") == 0,
|
||||
"enable signal order was %s (%s)", log.event, log.pre);
|
||||
|
||||
/* The retained relative rectangle follows a moved window before a key. */
|
||||
gtk_window_move(GTK_WINDOW(top), 137, 89);
|
||||
pump();
|
||||
first = eventcount(&srv);
|
||||
setpre(&srv, "pre2");
|
||||
Check(key(ctx, win, GDK_KEY_b), "key after move was not eaten");
|
||||
Check(waitcount(&srv, first + 2), "moved caret/key timed out");
|
||||
e = getevent(&srv, first);
|
||||
Check(e.type == Ecaret && e.valid, "window move did not refresh caret");
|
||||
gdk_window_get_origin(win, &ox, &oy);
|
||||
Check(e.x == (ox + rect.x) * scale && e.y == (oy + rect.y) * scale,
|
||||
"moved caret root conversion is wrong");
|
||||
Check(getevent(&srv, first + 1).type == Ekey,
|
||||
"caret did not precede moved-window key");
|
||||
|
||||
other = newwindow(301, 157);
|
||||
win2 = gtk_widget_get_window(other);
|
||||
first = eventcount(&srv);
|
||||
gtk_im_context_set_client_window(ctx, win2);
|
||||
Check(waitcount(&srv, first + 1), "window replacement invalidation timed out");
|
||||
e = getevent(&srv, first);
|
||||
Check(e.type == Ecaret && !e.valid, "window replacement retained old caret");
|
||||
rect.x = 7;
|
||||
rect.y = 9;
|
||||
rect.height = 13;
|
||||
first = eventcount(&srv);
|
||||
gtk_im_context_set_cursor_location(ctx, &rect);
|
||||
Check(waitcount(&srv, first + 1), "replacement caret timed out");
|
||||
e = getevent(&srv, first);
|
||||
gdk_window_get_origin(win2, &ox, &oy);
|
||||
scale = gdk_window_get_scale_factor(win2);
|
||||
Check(e.type == Ecaret && e.valid && e.x == (ox + rect.x) * scale &&
|
||||
e.y == (oy + rect.y) * scale && e.h == rect.height * scale,
|
||||
"replacement window caret is wrong");
|
||||
first = eventcount(&srv);
|
||||
gtk_im_context_set_client_window(ctx, NULL);
|
||||
Check(waitcount(&srv, first + 1), "NULL window invalidation timed out");
|
||||
e = getevent(&srv, first);
|
||||
Check(e.type == Ecaret && !e.valid, "NULL window did not invalidate caret");
|
||||
gtk_im_context_set_client_window(ctx, win2);
|
||||
rect.x = 1;
|
||||
rect.y = 1;
|
||||
rect.height = 1;
|
||||
first = eventcount(&srv);
|
||||
gtk_im_context_set_cursor_location(ctx, &rect);
|
||||
Check(waitcount(&srv, first + 1), "pre-overflow valid caret timed out");
|
||||
Check(getevent(&srv, first).type == Ecaret &&
|
||||
getevent(&srv, first).valid, "pre-overflow caret is not valid");
|
||||
rect.x = G_MAXINT;
|
||||
rect.y = 0;
|
||||
rect.height = 1;
|
||||
first = eventcount(&srv);
|
||||
gtk_im_context_set_cursor_location(ctx, &rect);
|
||||
Check(waitcount(&srv, first + 1), "overflow invalidation timed out");
|
||||
e = getevent(&srv, first);
|
||||
Check(e.type == Ecaret && !e.valid, "overflow caret was accepted");
|
||||
rect.x = 4;
|
||||
rect.y = 5;
|
||||
rect.height = -1;
|
||||
first = eventcount(&srv);
|
||||
gtk_im_context_set_cursor_location(ctx, &rect);
|
||||
if(eventcount(&srv) > first){
|
||||
e = getevent(&srv, first);
|
||||
Check(e.type == Ecaret && !e.valid, "negative caret height was accepted");
|
||||
}
|
||||
|
||||
/* A destroyed retained window invalidates placement on the next key. */
|
||||
rect.x = 4;
|
||||
rect.y = 5;
|
||||
rect.height = 10;
|
||||
first = eventcount(&srv);
|
||||
gtk_im_context_set_cursor_location(ctx, &rect);
|
||||
Check(waitcount(&srv, first + 1) && getevent(&srv, first).valid,
|
||||
"pre-destruction caret is not valid");
|
||||
gone = 0;
|
||||
g_object_weak_ref(G_OBJECT(win2), weakgone, &gone);
|
||||
gtk_widget_destroy(other);
|
||||
other = NULL;
|
||||
Check(!gone, "module did not retain its client window");
|
||||
first = eventcount(&srv);
|
||||
setpre(&srv, "destroyed");
|
||||
Check(key(ctx, win2, GDK_KEY_q), "destroyed-window key was not eaten");
|
||||
Check(waitcount(&srv, first + 2), "destroyed-window frames timed out");
|
||||
e = getevent(&srv, first);
|
||||
Check(e.type == Ecaret && !e.valid &&
|
||||
getevent(&srv, first + 1).type == Ekey,
|
||||
"destroyed window did not invalidate before the key");
|
||||
gtk_im_context_set_client_window(ctx, win);
|
||||
pump();
|
||||
if(!gone)
|
||||
g_object_weak_unref(G_OBJECT(win2), weakgone, &gone);
|
||||
win2 = win;
|
||||
gtk_im_context_set_cursor_location(ctx, &rect);
|
||||
|
||||
/* PASSWORD and PIN transitions reset an existing composition. */
|
||||
gtk_im_context_set_client_window(ctx, win2);
|
||||
rect.height = 12;
|
||||
gtk_im_context_set_cursor_location(ctx, &rect);
|
||||
setpre(&srv, "secret");
|
||||
clearlog(&log);
|
||||
Check(key(ctx, win2, GDK_KEY_c), "password setup key was not eaten");
|
||||
Check(strcmp(log.pre, "secret") == 0, "password setup preedit missing");
|
||||
clearlog(&log);
|
||||
first = eventcount(&srv);
|
||||
g_object_set(ctx, "input-purpose", GTK_INPUT_PURPOSE_PASSWORD, NULL);
|
||||
Check(waitcount(&srv, first + 1), "password reset timed out");
|
||||
Check(getevent(&srv, first).type == Ereset, "password did not reset owner");
|
||||
Check(strcmp(log.event, "CE") == 0, "password clear order was %s", log.event);
|
||||
clearlog(&log);
|
||||
first = eventcount(&srv);
|
||||
Check(key(ctx, win2, GDK_KEY_x), "password fallback did not commit");
|
||||
Check(eventcount(&srv) == first && strcmp(log.commit, "x") == 0,
|
||||
"password key reached daemon or did not commit");
|
||||
g_object_set(ctx, "input-purpose", GTK_INPUT_PURPOSE_FREE_FORM, NULL);
|
||||
setpre(&srv, "pin");
|
||||
clearlog(&log);
|
||||
Check(key(ctx, win2, GDK_KEY_d), "PIN setup key was not eaten");
|
||||
clearlog(&log);
|
||||
first = eventcount(&srv);
|
||||
g_object_set(ctx, "input-purpose", GTK_INPUT_PURPOSE_PIN, NULL);
|
||||
Check(waitcount(&srv, first + 1), "PIN reset timed out");
|
||||
Check(getevent(&srv, first).type == Ereset && strcmp(log.event, "CE") == 0,
|
||||
"PIN transition did not reset and clear");
|
||||
|
||||
g_object_set(ctx, "input-purpose", GTK_INPUT_PURPOSE_FREE_FORM, NULL);
|
||||
setpre(&srv, "reset");
|
||||
clearlog(&log);
|
||||
Check(key(ctx, win2, GDK_KEY_e), "reset setup key was not eaten");
|
||||
clearlog(&log);
|
||||
first = eventcount(&srv);
|
||||
gtk_im_context_reset(ctx);
|
||||
Check(waitcount(&srv, first + 1), "explicit reset timed out");
|
||||
Check(getevent(&srv, first).type == Ereset && strcmp(log.event, "CE") == 0,
|
||||
"explicit reset did not clear preedit");
|
||||
setpre(&srv, "focus");
|
||||
clearlog(&log);
|
||||
Check(key(ctx, win2, GDK_KEY_f), "focus setup key was not eaten");
|
||||
clearlog(&log);
|
||||
first = eventcount(&srv);
|
||||
closes = closecount(&srv);
|
||||
gtk_im_context_focus_out(ctx);
|
||||
Check(waitcount(&srv, first + 1) && getevent(&srv, first).type == Ereset,
|
||||
"focus-out reset missing");
|
||||
Check(waitclose(&srv, closes + 1), "focus-out did not close connection");
|
||||
Check(strcmp(log.event, "CE") == 0, "focus-out clear order was %s", log.event);
|
||||
|
||||
/* Finalization closes its connection and drops the retained GdkWindow. */
|
||||
gtk_im_context_set_client_window(ctx, win2);
|
||||
setpre(&srv, "final");
|
||||
clearlog(&log);
|
||||
Check(key(ctx, win2, GDK_KEY_g), "finalization setup key was not eaten");
|
||||
closes = closecount(&srv);
|
||||
g_object_unref(ctx);
|
||||
ctx = NULL;
|
||||
Check(waitclose(&srv, closes + 1), "finalization did not close connection");
|
||||
|
||||
/* A new module talking to an old daemon keeps legacy inline preedit. */
|
||||
setold(&srv, 1);
|
||||
clearlog(&log);
|
||||
oldctx = newcontext(&log);
|
||||
gtk_im_context_set_client_window(oldctx, win2);
|
||||
rect.x = 2;
|
||||
rect.y = 3;
|
||||
rect.height = 10;
|
||||
gtk_im_context_set_cursor_location(oldctx, &rect);
|
||||
gtk_im_context_set_use_preedit(oldctx, FALSE);
|
||||
setpre(&srv, "legacy");
|
||||
first = eventcount(&srv);
|
||||
Check(key(oldctx, win2, GDK_KEY_h), "old-daemon key was not eaten");
|
||||
Check(waitcount(&srv, first + 2), "old-daemon exchange timed out");
|
||||
e = getevent(&srv, first);
|
||||
Check(e.type == Ecap && e.want == 0, "old-daemon probe framing is wrong");
|
||||
e = getevent(&srv, first + 1);
|
||||
Check(e.type == Ekey && e.want == 1, "old fallback did not request preedit");
|
||||
for(i = first; i < eventcount(&srv); i++)
|
||||
Check(getevent(&srv, i).type != Ecaret,
|
||||
"caret extension sent to old daemon");
|
||||
Check(strcmp(log.event, "SC") == 0 && strcmp(log.pre, "legacy") == 0,
|
||||
"old fallback preedit was %s (%s)", log.event, log.pre);
|
||||
closes = closecount(&srv);
|
||||
g_object_unref(oldctx);
|
||||
oldctx = NULL;
|
||||
Check(waitclose(&srv, closes + 1), "old-daemon context did not close");
|
||||
|
||||
/* A failed first connection retains the existing plain-key fallback. */
|
||||
stopserver(&srv);
|
||||
serverup = 0;
|
||||
unlink(socket);
|
||||
clearlog(&log);
|
||||
badctx = newcontext(&log);
|
||||
Check(key(badctx, win2, GDK_KEY_z), "connection failure did not fall back");
|
||||
Check(strcmp(log.commit, "z") == 0 && log.pre[0] == '\0',
|
||||
"connection failure left stale preedit");
|
||||
|
||||
ok = 1;
|
||||
out:
|
||||
if(badctx != NULL)
|
||||
g_object_unref(badctx);
|
||||
if(oldctx != NULL)
|
||||
g_object_unref(oldctx);
|
||||
if(ctx != NULL)
|
||||
g_object_unref(ctx);
|
||||
if(other != NULL)
|
||||
gtk_widget_destroy(other);
|
||||
if(top != NULL)
|
||||
gtk_widget_destroy(top);
|
||||
pump();
|
||||
if(serverup)
|
||||
stopserver(&srv);
|
||||
if(socket[0] != '\0')
|
||||
unlink(socket);
|
||||
if(cache[0] != '\0')
|
||||
unlink(cache);
|
||||
if(runtime[0] != '\0')
|
||||
rmdir(runtime);
|
||||
if(rootmade)
|
||||
rmdir(root);
|
||||
stopxvfb(&xvfb);
|
||||
free(module);
|
||||
if(ok)
|
||||
printf("gtk_live_test: ok (scale %d)\n", scale);
|
||||
return !ok;
|
||||
}
|
||||
Reference in New Issue
Block a user