fix(gtk): restore compose fallback and clean lifecycle
This commit is contained in:
164
gtk/main.c
164
gtk/main.c
@@ -1,3 +1,4 @@
|
|||||||
|
#include <errno.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -12,12 +13,14 @@
|
|||||||
typedef struct Im Im;
|
typedef struct Im Im;
|
||||||
struct Im
|
struct Im
|
||||||
{
|
{
|
||||||
GtkIMContext parent;
|
GtkIMContextSimple parent;
|
||||||
int fd;
|
int fd;
|
||||||
int usepreedit;
|
int usepreedit;
|
||||||
int effective;
|
|
||||||
int ext;
|
int ext;
|
||||||
int private;
|
int private;
|
||||||
|
int simpleactive;
|
||||||
|
int insimple;
|
||||||
|
int simpledone;
|
||||||
char pre[Ipcfieldmax+1];
|
char pre[Ipcfieldmax+1];
|
||||||
int prelen;
|
int prelen;
|
||||||
GdkWindow *win;
|
GdkWindow *win;
|
||||||
@@ -33,10 +36,12 @@ struct Im
|
|||||||
typedef struct ImClass ImClass;
|
typedef struct ImClass ImClass;
|
||||||
struct ImClass
|
struct ImClass
|
||||||
{
|
{
|
||||||
GtkIMContextClass parent;
|
GtkIMContextSimpleClass parent;
|
||||||
};
|
};
|
||||||
|
|
||||||
static GType imtype;
|
static GType imtype;
|
||||||
|
static GObjectClass *parentobject;
|
||||||
|
static GtkIMContextClass *parentim;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setpreedit(Im *im, const char *s, int n)
|
setpreedit(Im *im, const char *s, int n)
|
||||||
@@ -60,15 +65,32 @@ setpreedit(Im *im, const char *s, int n)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
srvclose(Im *im)
|
srvdrop(Im *im, int notify)
|
||||||
{
|
{
|
||||||
if(im->fd >= 0)
|
if(im->fd >= 0)
|
||||||
close(im->fd);
|
close(im->fd);
|
||||||
im->fd = -1;
|
im->fd = -1;
|
||||||
im->ext = 0;
|
im->ext = 0;
|
||||||
im->effective = 1;
|
|
||||||
im->caretsent = 0;
|
im->caretsent = 0;
|
||||||
setpreedit(im, "", 0);
|
if(notify)
|
||||||
|
setpreedit(im, "", 0);
|
||||||
|
else{
|
||||||
|
im->pre[0] = '\0';
|
||||||
|
im->prelen = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
srvclose(Im *im)
|
||||||
|
{
|
||||||
|
srvdrop(im, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
validtext(const char *s, size_t n)
|
||||||
|
{
|
||||||
|
return s[n] == '\0' && memchr(s, '\0', n) == NULL &&
|
||||||
|
g_utf8_validate(s, n, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -80,6 +102,11 @@ readresp(Im *im, int want, int update, char *commit, int ncommit,
|
|||||||
if(ipcreadresp(im->fd, want, commit, ncommit, pre, sizeof pre,
|
if(ipcreadresp(im->fd, want, commit, ncommit, pre, sizeof pre,
|
||||||
resp) < 0)
|
resp) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
if(!validtext(commit, resp->commitlen) ||
|
||||||
|
(want && !validtext(pre, resp->preeditlen))){
|
||||||
|
errno = EPROTO;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
if(update)
|
if(update)
|
||||||
setpreedit(im, pre, resp->preeditlen);
|
setpreedit(im, pre, resp->preeditlen);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -188,8 +215,7 @@ srvconnect(Im *im)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
im->ext = resp.eaten != 0;
|
im->ext = resp.eaten != 0;
|
||||||
im->effective = im->ext ? im->usepreedit : 1;
|
if(!im->usepreedit)
|
||||||
if(!im->effective)
|
|
||||||
setpreedit(im, "", 0);
|
setpreedit(im, "", 0);
|
||||||
if(im->ext && sendcaret(im) < 0){
|
if(im->ext && sendcaret(im) < 0){
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
@@ -228,6 +254,40 @@ mget(uint32_t state)
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
simplecommit(GtkIMContext *ctx, const char *s, Im *im)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
(void)s;
|
||||||
|
if(im->insimple)
|
||||||
|
im->simpledone = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
simpleend(GtkIMContext *ctx, Im *im)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
if(im->insimple)
|
||||||
|
im->simpledone = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
simplefilter(GtkIMContext *ctx, GdkEventKey *ev, int release)
|
||||||
|
{
|
||||||
|
Im *im;
|
||||||
|
gboolean r;
|
||||||
|
|
||||||
|
im = (Im*)ctx;
|
||||||
|
im->simpleactive = 1;
|
||||||
|
im->simpledone = 0;
|
||||||
|
im->insimple = 1;
|
||||||
|
r = parentim->filter_keypress(ctx, ev);
|
||||||
|
im->insimple = 0;
|
||||||
|
if((!r && !release) || im->simpledone)
|
||||||
|
im->simpleactive = 0;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sendreset(Im *im)
|
sendreset(Im *im)
|
||||||
{
|
{
|
||||||
@@ -239,9 +299,9 @@ sendreset(Im *im)
|
|||||||
setpreedit(im, "", 0);
|
setpreedit(im, "", 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ipcpackreset(buf, im->effective);
|
ipcpackreset(buf, im->usepreedit);
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||||
readresp(im, im->effective, 0, commit,
|
readresp(im, im->usepreedit, 0, commit,
|
||||||
sizeof commit, &resp) < 0){
|
sizeof commit, &resp) < 0){
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
return;
|
return;
|
||||||
@@ -249,22 +309,6 @@ sendreset(Im *im)
|
|||||||
setpreedit(im, "", 0);
|
setpreedit(im, "", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
|
||||||
plaincommit(GtkIMContext *ctx, uint32_t key, uint32_t mod)
|
|
||||||
{
|
|
||||||
char u[8];
|
|
||||||
int n;
|
|
||||||
|
|
||||||
if(mod & (Mctrl|Malt|Msuper))
|
|
||||||
return FALSE;
|
|
||||||
if(key < 0x20 || key >= Kspec)
|
|
||||||
return FALSE;
|
|
||||||
n = g_unichar_to_utf8(key, u);
|
|
||||||
u[n] = '\0';
|
|
||||||
g_signal_emit_by_name(ctx, "commit", u);
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
||||||
{
|
{
|
||||||
@@ -275,33 +319,36 @@ kpress(GtkIMContext *ctx, GdkEventKey *ev)
|
|||||||
Ipcresp resp;
|
Ipcresp resp;
|
||||||
|
|
||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
if(ev->type != GDK_KEY_PRESS)
|
if(ev->type != GDK_KEY_PRESS){
|
||||||
|
if(im->simpleactive)
|
||||||
|
return simplefilter(ctx, ev, 1);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
}
|
||||||
|
if(im->simpleactive)
|
||||||
|
return simplefilter(ctx, ev, 0);
|
||||||
key = kget(ev->keyval);
|
key = kget(ev->keyval);
|
||||||
if(key == 0)
|
|
||||||
return FALSE;
|
|
||||||
mod = mget(ev->state);
|
mod = mget(ev->state);
|
||||||
if(im->private)
|
if(im->private || key == 0)
|
||||||
return plaincommit(ctx, key, mod);
|
return simplefilter(ctx, ev, 0);
|
||||||
if(srvconnect(im) < 0)
|
if(srvconnect(im) < 0)
|
||||||
return plaincommit(ctx, key, mod);
|
return simplefilter(ctx, ev, 0);
|
||||||
/* A retained GDK window may have moved since the last cursor report. */
|
/* A retained GDK window may have moved since the last cursor report. */
|
||||||
if(sendcaret(im) < 0){
|
if(sendcaret(im) < 0){
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
return plaincommit(ctx, key, mod);
|
return simplefilter(ctx, ev, 0);
|
||||||
}
|
}
|
||||||
ipcpackreq(buf, im->effective, mod, key);
|
ipcpackreq(buf, im->usepreedit, mod, key);
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||||
readresp(im, im->effective, im->effective, commit,
|
readresp(im, im->usepreedit, im->usepreedit, commit,
|
||||||
sizeof commit, &resp) < 0){
|
sizeof commit, &resp) < 0){
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
return plaincommit(ctx, key, mod);
|
return simplefilter(ctx, ev, 0);
|
||||||
}
|
}
|
||||||
if(commit[0] != '\0')
|
if(commit[0] != '\0')
|
||||||
g_signal_emit_by_name(ctx, "commit", commit);
|
g_signal_emit_by_name(ctx, "commit", commit);
|
||||||
if(resp.eaten)
|
if(resp.eaten)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
return plaincommit(ctx, key, mod);
|
return simplefilter(ctx, ev, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -312,6 +359,10 @@ getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs,
|
|||||||
PangoAttribute *u;
|
PangoAttribute *u;
|
||||||
|
|
||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
|
if(im->simpleactive){
|
||||||
|
parentim->get_preedit_string(ctx, str, attrs, cursor_pos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(str)
|
if(str)
|
||||||
*str = g_strdup(im->pre);
|
*str = g_strdup(im->pre);
|
||||||
if(attrs){
|
if(attrs){
|
||||||
@@ -330,6 +381,9 @@ getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs,
|
|||||||
static void
|
static void
|
||||||
reset(GtkIMContext *ctx)
|
reset(GtkIMContext *ctx)
|
||||||
{
|
{
|
||||||
|
if(parentim->reset != NULL)
|
||||||
|
parentim->reset(ctx);
|
||||||
|
((Im*)ctx)->simpleactive = 0;
|
||||||
sendreset((Im*)ctx);
|
sendreset((Im*)ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,6 +393,9 @@ focusout(GtkIMContext *ctx)
|
|||||||
Im *im;
|
Im *im;
|
||||||
|
|
||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
|
if(parentim->focus_out != NULL)
|
||||||
|
parentim->focus_out(ctx);
|
||||||
|
im->simpleactive = 0;
|
||||||
sendreset(im);
|
sendreset(im);
|
||||||
/* Closing the context connection releases engine ownership. */
|
/* Closing the context connection releases engine ownership. */
|
||||||
srvclose(im);
|
srvclose(im);
|
||||||
@@ -354,13 +411,15 @@ setusepreedit(GtkIMContext *ctx, gboolean use)
|
|||||||
|
|
||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
use = use != FALSE;
|
use = use != FALSE;
|
||||||
|
if(parentim->set_use_preedit != NULL)
|
||||||
|
parentim->set_use_preedit(ctx, use);
|
||||||
if(im->usepreedit == use)
|
if(im->usepreedit == use)
|
||||||
return;
|
return;
|
||||||
im->usepreedit = use;
|
im->usepreedit = use;
|
||||||
if(im->fd < 0 || !im->ext){
|
if(!use)
|
||||||
im->effective = im->ext ? use : 1;
|
setpreedit(im, "", 0);
|
||||||
|
if(im->fd < 0 || !im->ext)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
ipcpackcap(buf, use);
|
ipcpackcap(buf, use);
|
||||||
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
|
||||||
readresp(im, use, use, commit, sizeof commit, &resp) < 0 ||
|
readresp(im, use, use, commit, sizeof commit, &resp) < 0 ||
|
||||||
@@ -368,9 +427,6 @@ setusepreedit(GtkIMContext *ctx, gboolean use)
|
|||||||
srvclose(im);
|
srvclose(im);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
im->effective = use;
|
|
||||||
if(!use)
|
|
||||||
setpreedit(im, "", 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -381,6 +437,8 @@ setclientwindow(GtkIMContext *ctx, GdkWindow *win)
|
|||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
if(win != NULL && gdk_window_is_destroyed(win))
|
if(win != NULL && gdk_window_is_destroyed(win))
|
||||||
win = NULL;
|
win = NULL;
|
||||||
|
if(parentim->set_client_window != NULL)
|
||||||
|
parentim->set_client_window(ctx, win);
|
||||||
if(im->win == win)
|
if(im->win == win)
|
||||||
return;
|
return;
|
||||||
if(win != NULL)
|
if(win != NULL)
|
||||||
@@ -397,6 +455,8 @@ setcursorlocation(GtkIMContext *ctx, GdkRectangle *area)
|
|||||||
Im *im;
|
Im *im;
|
||||||
|
|
||||||
im = (Im*)ctx;
|
im = (Im*)ctx;
|
||||||
|
if(parentim->set_cursor_location != NULL)
|
||||||
|
parentim->set_cursor_location(ctx, area);
|
||||||
if(im->win != NULL && gdk_window_is_destroyed(im->win))
|
if(im->win != NULL && gdk_window_is_destroyed(im->win))
|
||||||
dropwindow(im);
|
dropwindow(im);
|
||||||
if(area == NULL || im->win == NULL)
|
if(area == NULL || im->win == NULL)
|
||||||
@@ -432,18 +492,18 @@ purposechanged(GObject *obj, GParamSpec *pspec, gpointer data)
|
|||||||
return;
|
return;
|
||||||
im->private = private;
|
im->private = private;
|
||||||
if(private)
|
if(private)
|
||||||
sendreset(im);
|
reset(GTK_IM_CONTEXT(im));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
finalize(GObject *obj)
|
dispose(GObject *obj)
|
||||||
{
|
{
|
||||||
Im *im;
|
Im *im;
|
||||||
|
|
||||||
im = (Im*)obj;
|
im = (Im*)obj;
|
||||||
srvclose(im);
|
srvdrop(im, 0);
|
||||||
dropwindow(im);
|
dropwindow(im);
|
||||||
G_OBJECT_CLASS(g_type_class_peek_parent(G_OBJECT_GET_CLASS(obj)))->finalize(obj);
|
parentobject->dispose(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -453,12 +513,13 @@ init(Im *im)
|
|||||||
|
|
||||||
im->fd = -1;
|
im->fd = -1;
|
||||||
im->usepreedit = 1;
|
im->usepreedit = 1;
|
||||||
im->effective = 1;
|
|
||||||
im->pre[0] = '\0';
|
im->pre[0] = '\0';
|
||||||
g_object_get(im, "input-purpose", &purpose, NULL);
|
g_object_get(im, "input-purpose", &purpose, NULL);
|
||||||
im->private = isprivate(purpose);
|
im->private = isprivate(purpose);
|
||||||
g_signal_connect(im, "notify::input-purpose",
|
g_signal_connect(im, "notify::input-purpose",
|
||||||
G_CALLBACK(purposechanged), NULL);
|
G_CALLBACK(purposechanged), NULL);
|
||||||
|
g_signal_connect(im, "commit", G_CALLBACK(simplecommit), im);
|
||||||
|
g_signal_connect(im, "preedit-end", G_CALLBACK(simpleend), im);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -469,6 +530,8 @@ classinit(ImClass *klass)
|
|||||||
|
|
||||||
ic = GTK_IM_CONTEXT_CLASS(klass);
|
ic = GTK_IM_CONTEXT_CLASS(klass);
|
||||||
oc = G_OBJECT_CLASS(klass);
|
oc = G_OBJECT_CLASS(klass);
|
||||||
|
parentim = g_type_class_peek_parent(klass);
|
||||||
|
parentobject = G_OBJECT_CLASS(parentim);
|
||||||
ic->filter_keypress = kpress;
|
ic->filter_keypress = kpress;
|
||||||
ic->get_preedit_string = getpreedit;
|
ic->get_preedit_string = getpreedit;
|
||||||
ic->reset = reset;
|
ic->reset = reset;
|
||||||
@@ -476,7 +539,7 @@ classinit(ImClass *klass)
|
|||||||
ic->set_use_preedit = setusepreedit;
|
ic->set_use_preedit = setusepreedit;
|
||||||
ic->set_client_window = setclientwindow;
|
ic->set_client_window = setclientwindow;
|
||||||
ic->set_cursor_location = setcursorlocation;
|
ic->set_cursor_location = setcursorlocation;
|
||||||
oc->finalize = finalize;
|
oc->dispose = dispose;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const GtkIMContextInfo info = {
|
static const GtkIMContextInfo info = {
|
||||||
@@ -501,7 +564,8 @@ im_module_init(GTypeModule *mod)
|
|||||||
0,
|
0,
|
||||||
(GInstanceInitFunc)init,
|
(GInstanceInitFunc)init,
|
||||||
};
|
};
|
||||||
imtype = g_type_module_register_type(mod, GTK_TYPE_IM_CONTEXT, "strans-gtk", &ti, 0);
|
imtype = g_type_module_register_type(mod, GTK_TYPE_IM_CONTEXT_SIMPLE,
|
||||||
|
"strans-gtk", &ti, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
G_MODULE_EXPORT void
|
G_MODULE_EXPORT void
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ enum
|
|||||||
Ereset,
|
Ereset,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Rnormal,
|
||||||
|
Rbadcommit,
|
||||||
|
Rbadpreedit,
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct Event Event;
|
typedef struct Event Event;
|
||||||
typedef struct Server Server;
|
typedef struct Server Server;
|
||||||
typedef struct Siglog Siglog;
|
typedef struct Siglog Siglog;
|
||||||
@@ -58,6 +65,9 @@ struct Server
|
|||||||
int wake[2];
|
int wake[2];
|
||||||
int stop;
|
int stop;
|
||||||
int old;
|
int old;
|
||||||
|
int eaten;
|
||||||
|
int stall;
|
||||||
|
int response;
|
||||||
int nclose;
|
int nclose;
|
||||||
char pre[Ipcfieldmax+1];
|
char pre[Ipcfieldmax+1];
|
||||||
Event ev[Maxevent];
|
Event ev[Maxevent];
|
||||||
@@ -128,16 +138,35 @@ closed(Server *s)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
sendresponse(Server *s, int fd, int eaten, int want)
|
sendresponse(Server *s, int fd, int eaten, int want, int key)
|
||||||
{
|
{
|
||||||
unsigned char buf[Ipcmaxresp];
|
unsigned char buf[Ipcmaxresp];
|
||||||
|
const char *commit;
|
||||||
char pre[Ipcfieldmax+1];
|
char pre[Ipcfieldmax+1];
|
||||||
int n;
|
int n, response, stall;
|
||||||
|
size_t ncommit, npreedit;
|
||||||
|
|
||||||
pthread_mutex_lock(&s->lock);
|
pthread_mutex_lock(&s->lock);
|
||||||
memcpy(pre, s->pre, sizeof pre);
|
memcpy(pre, s->pre, sizeof pre);
|
||||||
|
response = s->response;
|
||||||
|
stall = s->stall;
|
||||||
pthread_mutex_unlock(&s->lock);
|
pthread_mutex_unlock(&s->lock);
|
||||||
n = ipcpackresp(buf, sizeof buf, eaten, "", 0, pre, strlen(pre), want);
|
if(stall)
|
||||||
|
return 1;
|
||||||
|
commit = "";
|
||||||
|
ncommit = 0;
|
||||||
|
npreedit = strlen(pre);
|
||||||
|
if(key && response == Rbadcommit){
|
||||||
|
commit = "\xc3(";
|
||||||
|
ncommit = 2;
|
||||||
|
}else if(key && response == Rbadpreedit){
|
||||||
|
pre[0] = 'x';
|
||||||
|
pre[1] = 0;
|
||||||
|
pre[2] = 'y';
|
||||||
|
npreedit = 3;
|
||||||
|
}
|
||||||
|
n = ipcpackresp(buf, sizeof buf, eaten, commit, ncommit,
|
||||||
|
pre, npreedit, want);
|
||||||
return n >= 0 && ipcsend(fd, buf, n) == 0;
|
return n >= 0 && ipcsend(fd, buf, n) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +190,7 @@ request(Server *s, int fd)
|
|||||||
pthread_mutex_lock(&s->lock);
|
pthread_mutex_lock(&s->lock);
|
||||||
old = s->old;
|
old = s->old;
|
||||||
pthread_mutex_unlock(&s->lock);
|
pthread_mutex_unlock(&s->lock);
|
||||||
return sendresponse(s, fd, !old, e.want);
|
return sendresponse(s, fd, !old, e.want, 0);
|
||||||
case Ipccaret:
|
case Ipccaret:
|
||||||
if(ipcreadn(fd, buf + Ipcreqsz, Ipccaretsz - Ipcreqsz) < 0 ||
|
if(ipcreadn(fd, buf + Ipcreqsz, Ipccaretsz - Ipcreqsz) < 0 ||
|
||||||
ipcunpackcaret(buf, &e.valid, &e.x, &e.y, &e.h) < 0)
|
ipcunpackcaret(buf, &e.valid, &e.x, &e.y, &e.h) < 0)
|
||||||
@@ -175,7 +204,11 @@ request(Server *s, int fd)
|
|||||||
e.key = key;
|
e.key = key;
|
||||||
e.type = ipcreqreset(buf) ? Ereset : Ekey;
|
e.type = ipcreqreset(buf) ? Ereset : Ekey;
|
||||||
record(s, &e);
|
record(s, &e);
|
||||||
return sendresponse(s, fd, e.type == Ekey, want);
|
pthread_mutex_lock(&s->lock);
|
||||||
|
old = s->eaten;
|
||||||
|
pthread_mutex_unlock(&s->lock);
|
||||||
|
return sendresponse(s, fd, e.type == Ekey && old, want,
|
||||||
|
e.type == Ekey);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -230,6 +263,7 @@ startserver(Server *s, char *path)
|
|||||||
int cond;
|
int cond;
|
||||||
|
|
||||||
memset(s, 0, sizeof *s);
|
memset(s, 0, sizeof *s);
|
||||||
|
s->eaten = 1;
|
||||||
s->listenfd = -1;
|
s->listenfd = -1;
|
||||||
s->wake[0] = s->wake[1] = -1;
|
s->wake[0] = s->wake[1] = -1;
|
||||||
if(pthread_mutex_init(&s->lock, NULL) != 0)
|
if(pthread_mutex_init(&s->lock, NULL) != 0)
|
||||||
@@ -317,6 +351,16 @@ setold(Server *s, int old)
|
|||||||
pthread_mutex_unlock(&s->lock);
|
pthread_mutex_unlock(&s->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
setreply(Server *s, int eaten, int response, int stall)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&s->lock);
|
||||||
|
s->eaten = eaten;
|
||||||
|
s->response = response;
|
||||||
|
s->stall = stall;
|
||||||
|
pthread_mutex_unlock(&s->lock);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
eventcount(Server *s)
|
eventcount(Server *s)
|
||||||
{
|
{
|
||||||
@@ -540,16 +584,6 @@ commit(GtkIMContext *ctx, gchar *s, Siglog *l)
|
|||||||
logsig(l, 'K');
|
logsig(l, 'K');
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
weakgone(void *v, GObject *obj)
|
|
||||||
{
|
|
||||||
int *gone;
|
|
||||||
|
|
||||||
(void)obj;
|
|
||||||
gone = v;
|
|
||||||
*gone = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
clearlog(Siglog *l)
|
clearlog(Siglog *l)
|
||||||
{
|
{
|
||||||
@@ -571,21 +605,35 @@ newcontext(Siglog *l)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
key(GtkIMContext *ctx, GdkWindow *win, guint keyval)
|
keyevent(GtkIMContext *ctx, GdkWindow *win, GdkEventType type,
|
||||||
|
guint keyval, guint state)
|
||||||
{
|
{
|
||||||
GdkEvent *ev;
|
GdkEvent *ev;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
ev = gdk_event_new(GDK_KEY_PRESS);
|
ev = gdk_event_new(type);
|
||||||
ev->key.window = g_object_ref(win);
|
ev->key.window = g_object_ref(win);
|
||||||
ev->key.send_event = TRUE;
|
ev->key.send_event = TRUE;
|
||||||
ev->key.time = GDK_CURRENT_TIME;
|
ev->key.time = GDK_CURRENT_TIME;
|
||||||
ev->key.keyval = keyval;
|
ev->key.keyval = keyval;
|
||||||
|
ev->key.state = state;
|
||||||
r = gtk_im_context_filter_keypress(ctx, &ev->key);
|
r = gtk_im_context_filter_keypress(ctx, &ev->key);
|
||||||
gdk_event_free(ev);
|
gdk_event_free(ev);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
keystate(GtkIMContext *ctx, GdkWindow *win, guint keyval, guint state)
|
||||||
|
{
|
||||||
|
return keyevent(ctx, win, GDK_KEY_PRESS, keyval, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
key(GtkIMContext *ctx, GdkWindow *win, guint keyval)
|
||||||
|
{
|
||||||
|
return keystate(ctx, win, keyval, 0);
|
||||||
|
}
|
||||||
|
|
||||||
static GtkWidget*
|
static GtkWidget*
|
||||||
newwindow(int x, int y)
|
newwindow(int x, int y)
|
||||||
{
|
{
|
||||||
@@ -607,19 +655,25 @@ main(int argc, char **argv)
|
|||||||
Server srv;
|
Server srv;
|
||||||
Siglog log;
|
Siglog log;
|
||||||
GtkIMContext *ctx, *oldctx, *badctx;
|
GtkIMContext *ctx, *oldctx, *badctx;
|
||||||
GtkWidget *top, *other;
|
GtkWidget *top;
|
||||||
GdkWindow *win, *win2;
|
GdkWindow *win;
|
||||||
GdkRectangle rect;
|
GdkRectangle rect;
|
||||||
Event e;
|
Event e;
|
||||||
|
static const GtkInputPurpose purposes[] = {
|
||||||
|
GTK_INPUT_PURPOSE_PASSWORD,
|
||||||
|
GTK_INPUT_PURPOSE_PIN,
|
||||||
|
};
|
||||||
|
static const int badresponses[] = { Rbadcommit, Rbadpreedit };
|
||||||
char root[] = "/tmp/strans-gtk.XXXXXX";
|
char root[] = "/tmp/strans-gtk.XXXXXX";
|
||||||
char runtime[320] = "", socket[384] = "", cache[384] = "";
|
char runtime[320] = "", socket[384] = "", cache[384] = "";
|
||||||
char display[32];
|
char display[32];
|
||||||
char *module;
|
char *module;
|
||||||
pid_t xvfb;
|
pid_t xvfb;
|
||||||
int first, i, ox, oy, scale, closes, gone, ok, rootmade, serverup;
|
int first, i, ox, oy, scale, closes, ok, rootmade, serverup;
|
||||||
|
int64_t stalledms, started;
|
||||||
|
|
||||||
ctx = oldctx = badctx = NULL;
|
ctx = oldctx = badctx = NULL;
|
||||||
top = other = NULL;
|
top = NULL;
|
||||||
xvfb = -1;
|
xvfb = -1;
|
||||||
rootmade = 0;
|
rootmade = 0;
|
||||||
serverup = 0;
|
serverup = 0;
|
||||||
@@ -688,153 +742,60 @@ main(int argc, char **argv)
|
|||||||
Check(strcmp(log.event, "SC") == 0 && strcmp(log.pre, "pre") == 0,
|
Check(strcmp(log.event, "SC") == 0 && strcmp(log.pre, "pre") == 0,
|
||||||
"initial preedit signals were %s (%s)", log.event, log.pre);
|
"initial preedit signals were %s (%s)", log.event, log.pre);
|
||||||
|
|
||||||
|
/* Unconsumed input uses GtkIMContextSimple, including its stateful forms. */
|
||||||
|
setpre(&srv, "");
|
||||||
|
setreply(&srv, 0, Rnormal, 0);
|
||||||
clearlog(&log);
|
clearlog(&log);
|
||||||
first = eventcount(&srv);
|
Check(key(ctx, win, GDK_KEY_Multi_key) &&
|
||||||
gtk_im_context_set_use_preedit(ctx, FALSE);
|
key(ctx, win, GDK_KEY_apostrophe) && key(ctx, win, GDK_KEY_e),
|
||||||
Check(waitcount(&srv, first + 1), "disable capability timed out");
|
"Compose sequence was not consumed");
|
||||||
e = getevent(&srv, first);
|
Check(strcmp(log.commit, "é") == 0, "Compose committed %s", log.commit);
|
||||||
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);
|
clearlog(&log);
|
||||||
gtk_im_context_set_use_preedit(ctx, FALSE);
|
Check(key(ctx, win, GDK_KEY_dead_acute) && key(ctx, win, GDK_KEY_e),
|
||||||
Check(log.n == 0, "repeated disable emitted %s", log.event);
|
"dead-key sequence was not consumed");
|
||||||
first = eventcount(&srv);
|
Check(strcmp(log.commit, "é") == 0, "dead key committed %s", log.commit);
|
||||||
gtk_im_context_set_use_preedit(ctx, TRUE);
|
clearlog(&log);
|
||||||
Check(waitcount(&srv, first + 1), "enable capability timed out");
|
Check(keystate(ctx, win, GDK_KEY_U, GDK_CONTROL_MASK|GDK_SHIFT_MASK),
|
||||||
e = getevent(&srv, first);
|
"Unicode prefix was not consumed");
|
||||||
Check(e.type == Ecap && e.want == 1, "enable capability frame missing");
|
(void)keyevent(ctx, win, GDK_KEY_RELEASE, GDK_KEY_U,
|
||||||
Check(strcmp(log.event, "SC") == 0 && strcmp(log.pre, "pre") == 0,
|
GDK_CONTROL_MASK|GDK_SHIFT_MASK);
|
||||||
"enable signal order was %s (%s)", log.event, log.pre);
|
(void)keyevent(ctx, win, GDK_KEY_RELEASE, GDK_KEY_Shift_L,
|
||||||
|
GDK_CONTROL_MASK);
|
||||||
/* The retained relative rectangle follows a moved window before a key. */
|
(void)keyevent(ctx, win, GDK_KEY_RELEASE, GDK_KEY_Control_L, 0);
|
||||||
gtk_window_move(GTK_WINDOW(top), 137, 89);
|
Check(key(ctx, win, GDK_KEY_0), "Unicode digit 1 was not consumed");
|
||||||
pump();
|
Check(key(ctx, win, GDK_KEY_0), "Unicode digit 2 was not consumed");
|
||||||
first = eventcount(&srv);
|
Check(key(ctx, win, GDK_KEY_e), "Unicode digit 3 was not consumed");
|
||||||
setpre(&srv, "pre2");
|
Check(key(ctx, win, GDK_KEY_9), "Unicode digit 4 was not consumed");
|
||||||
Check(key(ctx, win, GDK_KEY_b), "key after move was not eaten");
|
(void)key(ctx, win, GDK_KEY_Return);
|
||||||
Check(waitcount(&srv, first + 2), "moved caret/key timed out");
|
Check(strcmp(log.commit, "é") == 0, "Unicode entry committed %s", log.commit);
|
||||||
e = getevent(&srv, first);
|
setreply(&srv, 1, Rnormal, 0);
|
||||||
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. */
|
/* PASSWORD and PIN transitions reset an existing composition. */
|
||||||
gtk_im_context_set_client_window(ctx, win2);
|
|
||||||
rect.height = 12;
|
rect.height = 12;
|
||||||
gtk_im_context_set_cursor_location(ctx, &rect);
|
gtk_im_context_set_cursor_location(ctx, &rect);
|
||||||
setpre(&srv, "secret");
|
for(i = 0; i < (int)(sizeof purposes / sizeof purposes[0]); i++){
|
||||||
clearlog(&log);
|
g_object_set(ctx, "input-purpose", GTK_INPUT_PURPOSE_FREE_FORM, NULL);
|
||||||
Check(key(ctx, win2, GDK_KEY_c), "password setup key was not eaten");
|
setpre(&srv, i == 0 ? "password" : "pin");
|
||||||
Check(strcmp(log.pre, "secret") == 0, "password setup preedit missing");
|
clearlog(&log);
|
||||||
clearlog(&log);
|
Check(key(ctx, win, GDK_KEY_c + i), "private setup key was not eaten");
|
||||||
first = eventcount(&srv);
|
clearlog(&log);
|
||||||
g_object_set(ctx, "input-purpose", GTK_INPUT_PURPOSE_PASSWORD, NULL);
|
first = eventcount(&srv);
|
||||||
Check(waitcount(&srv, first + 1), "password reset timed out");
|
g_object_set(ctx, "input-purpose", purposes[i], NULL);
|
||||||
Check(getevent(&srv, first).type == Ereset, "password did not reset owner");
|
Check(waitcount(&srv, first + 1), "private reset timed out");
|
||||||
Check(strcmp(log.event, "CE") == 0, "password clear order was %s", log.event);
|
Check(getevent(&srv, first).type == Ereset &&
|
||||||
clearlog(&log);
|
strcmp(log.event, "CE") == 0,
|
||||||
first = eventcount(&srv);
|
"private transition did not reset and clear");
|
||||||
Check(key(ctx, win2, GDK_KEY_x), "password fallback did not commit");
|
clearlog(&log);
|
||||||
Check(eventcount(&srv) == first && strcmp(log.commit, "x") == 0,
|
first = eventcount(&srv);
|
||||||
"password key reached daemon or did not commit");
|
Check(key(ctx, win, GDK_KEY_x), "private fallback did not commit");
|
||||||
g_object_set(ctx, "input-purpose", GTK_INPUT_PURPOSE_FREE_FORM, NULL);
|
Check(eventcount(&srv) == first && strcmp(log.commit, "x") == 0,
|
||||||
setpre(&srv, "pin");
|
"private key reached daemon or did not commit");
|
||||||
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);
|
g_object_set(ctx, "input-purpose", GTK_INPUT_PURPOSE_FREE_FORM, NULL);
|
||||||
setpre(&srv, "reset");
|
setpre(&srv, "reset");
|
||||||
clearlog(&log);
|
clearlog(&log);
|
||||||
Check(key(ctx, win2, GDK_KEY_e), "reset setup key was not eaten");
|
Check(key(ctx, win, GDK_KEY_e), "reset setup key was not eaten");
|
||||||
clearlog(&log);
|
clearlog(&log);
|
||||||
first = eventcount(&srv);
|
first = eventcount(&srv);
|
||||||
gtk_im_context_reset(ctx);
|
gtk_im_context_reset(ctx);
|
||||||
@@ -843,7 +804,7 @@ main(int argc, char **argv)
|
|||||||
"explicit reset did not clear preedit");
|
"explicit reset did not clear preedit");
|
||||||
setpre(&srv, "focus");
|
setpre(&srv, "focus");
|
||||||
clearlog(&log);
|
clearlog(&log);
|
||||||
Check(key(ctx, win2, GDK_KEY_f), "focus setup key was not eaten");
|
Check(key(ctx, win, GDK_KEY_f), "focus setup key was not eaten");
|
||||||
clearlog(&log);
|
clearlog(&log);
|
||||||
first = eventcount(&srv);
|
first = eventcount(&srv);
|
||||||
closes = closecount(&srv);
|
closes = closecount(&srv);
|
||||||
@@ -853,21 +814,58 @@ main(int argc, char **argv)
|
|||||||
Check(waitclose(&srv, closes + 1), "focus-out did not close connection");
|
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);
|
Check(strcmp(log.event, "CE") == 0, "focus-out clear order was %s", log.event);
|
||||||
|
|
||||||
/* Finalization closes its connection and drops the retained GdkWindow. */
|
/* Dispose is repeatable, closes the connection, and emits no preedit. */
|
||||||
gtk_im_context_set_client_window(ctx, win2);
|
gtk_im_context_set_client_window(ctx, win);
|
||||||
setpre(&srv, "final");
|
setpre(&srv, "final");
|
||||||
clearlog(&log);
|
clearlog(&log);
|
||||||
Check(key(ctx, win2, GDK_KEY_g), "finalization setup key was not eaten");
|
Check(key(ctx, win, GDK_KEY_g), "dispose setup key was not eaten");
|
||||||
|
clearlog(&log);
|
||||||
closes = closecount(&srv);
|
closes = closecount(&srv);
|
||||||
|
g_object_run_dispose(G_OBJECT(ctx));
|
||||||
|
g_object_run_dispose(G_OBJECT(ctx));
|
||||||
g_object_unref(ctx);
|
g_object_unref(ctx);
|
||||||
ctx = NULL;
|
ctx = NULL;
|
||||||
Check(waitclose(&srv, closes + 1), "finalization did not close connection");
|
Check(waitclose(&srv, closes + 1), "dispose did not close connection");
|
||||||
|
Check(log.n == 0, "dispose emitted %s", log.event);
|
||||||
|
|
||||||
/* A new module talking to an old daemon keeps legacy inline preedit. */
|
/* Malformed commit and preedit fields close the protocol connection. */
|
||||||
|
for(i = 0; i < (int)(sizeof badresponses / sizeof badresponses[0]); i++){
|
||||||
|
setreply(&srv, 1, badresponses[i], 0);
|
||||||
|
clearlog(&log);
|
||||||
|
badctx = newcontext(&log);
|
||||||
|
gtk_im_context_set_client_window(badctx, win);
|
||||||
|
closes = closecount(&srv);
|
||||||
|
Check(key(badctx, win, GDK_KEY_m + i),
|
||||||
|
"malformed response did not fall back");
|
||||||
|
Check(waitclose(&srv, closes + 1),
|
||||||
|
"malformed response did not close connection");
|
||||||
|
Check(log.commit[0] == 'm' + i && log.commit[1] == '\0',
|
||||||
|
"malformed response fallback committed %s", log.commit);
|
||||||
|
g_object_unref(badctx);
|
||||||
|
badctx = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A peer which accepts a connection but never replies cannot freeze GTK. */
|
||||||
|
setreply(&srv, 1, Rnormal, 1);
|
||||||
|
clearlog(&log);
|
||||||
|
badctx = newcontext(&log);
|
||||||
|
closes = closecount(&srv);
|
||||||
|
started = nowms();
|
||||||
|
Check(key(badctx, win, GDK_KEY_t), "stalled peer did not fall back");
|
||||||
|
stalledms = nowms() - started;
|
||||||
|
Check(stalledms < 4*Ipcwaitms, "stalled peer blocked GTK");
|
||||||
|
Check(waitclose(&srv, closes + 1), "stalled connection was not closed");
|
||||||
|
Check(strcmp(log.commit, "t") == 0, "stalled fallback committed %s",
|
||||||
|
log.commit);
|
||||||
|
g_object_unref(badctx);
|
||||||
|
badctx = NULL;
|
||||||
|
setreply(&srv, 1, Rnormal, 0);
|
||||||
|
|
||||||
|
/* An old daemon still honors set_use_preedit(FALSE). */
|
||||||
setold(&srv, 1);
|
setold(&srv, 1);
|
||||||
clearlog(&log);
|
clearlog(&log);
|
||||||
oldctx = newcontext(&log);
|
oldctx = newcontext(&log);
|
||||||
gtk_im_context_set_client_window(oldctx, win2);
|
gtk_im_context_set_client_window(oldctx, win);
|
||||||
rect.x = 2;
|
rect.x = 2;
|
||||||
rect.y = 3;
|
rect.y = 3;
|
||||||
rect.height = 10;
|
rect.height = 10;
|
||||||
@@ -875,29 +873,29 @@ main(int argc, char **argv)
|
|||||||
gtk_im_context_set_use_preedit(oldctx, FALSE);
|
gtk_im_context_set_use_preedit(oldctx, FALSE);
|
||||||
setpre(&srv, "legacy");
|
setpre(&srv, "legacy");
|
||||||
first = eventcount(&srv);
|
first = eventcount(&srv);
|
||||||
Check(key(oldctx, win2, GDK_KEY_h), "old-daemon key was not eaten");
|
Check(key(oldctx, win, GDK_KEY_h), "old-daemon key was not eaten");
|
||||||
Check(waitcount(&srv, first + 2), "old-daemon exchange timed out");
|
Check(waitcount(&srv, first + 2), "old-daemon exchange timed out");
|
||||||
e = getevent(&srv, first);
|
e = getevent(&srv, first);
|
||||||
Check(e.type == Ecap && e.want == 0, "old-daemon probe framing is wrong");
|
Check(e.type == Ecap && e.want == 0, "old-daemon probe framing is wrong");
|
||||||
e = getevent(&srv, first + 1);
|
e = getevent(&srv, first + 1);
|
||||||
Check(e.type == Ekey && e.want == 1, "old fallback did not request preedit");
|
Check(e.type == Ekey && e.want == 0, "old daemon forced inline preedit");
|
||||||
for(i = first; i < eventcount(&srv); i++)
|
for(i = first; i < eventcount(&srv); i++)
|
||||||
Check(getevent(&srv, i).type != Ecaret,
|
Check(getevent(&srv, i).type != Ecaret,
|
||||||
"caret extension sent to old daemon");
|
"caret extension sent to old daemon");
|
||||||
Check(strcmp(log.event, "SC") == 0 && strcmp(log.pre, "legacy") == 0,
|
Check(log.n == 0 && log.pre[0] == '\0',
|
||||||
"old fallback preedit was %s (%s)", log.event, log.pre);
|
"old daemon emitted inline preedit %s (%s)", log.event, log.pre);
|
||||||
closes = closecount(&srv);
|
closes = closecount(&srv);
|
||||||
g_object_unref(oldctx);
|
g_object_unref(oldctx);
|
||||||
oldctx = NULL;
|
oldctx = NULL;
|
||||||
Check(waitclose(&srv, closes + 1), "old-daemon context did not close");
|
Check(waitclose(&srv, closes + 1), "old-daemon context did not close");
|
||||||
|
|
||||||
/* A failed first connection retains the existing plain-key fallback. */
|
/* A failed first connection uses GtkIMContextSimple. */
|
||||||
stopserver(&srv);
|
stopserver(&srv);
|
||||||
serverup = 0;
|
serverup = 0;
|
||||||
unlink(socket);
|
unlink(socket);
|
||||||
clearlog(&log);
|
clearlog(&log);
|
||||||
badctx = newcontext(&log);
|
badctx = newcontext(&log);
|
||||||
Check(key(badctx, win2, GDK_KEY_z), "connection failure did not fall back");
|
Check(key(badctx, win, GDK_KEY_z), "connection failure did not fall back");
|
||||||
Check(strcmp(log.commit, "z") == 0 && log.pre[0] == '\0',
|
Check(strcmp(log.commit, "z") == 0 && log.pre[0] == '\0',
|
||||||
"connection failure left stale preedit");
|
"connection failure left stale preedit");
|
||||||
|
|
||||||
@@ -909,8 +907,6 @@ out:
|
|||||||
g_object_unref(oldctx);
|
g_object_unref(oldctx);
|
||||||
if(ctx != NULL)
|
if(ctx != NULL)
|
||||||
g_object_unref(ctx);
|
g_object_unref(ctx);
|
||||||
if(other != NULL)
|
|
||||||
gtk_widget_destroy(other);
|
|
||||||
if(top != NULL)
|
if(top != NULL)
|
||||||
gtk_widget_destroy(top);
|
gtk_widget_destroy(top);
|
||||||
pump();
|
pump();
|
||||||
@@ -927,6 +923,7 @@ out:
|
|||||||
stopxvfb(&xvfb);
|
stopxvfb(&xvfb);
|
||||||
free(module);
|
free(module);
|
||||||
if(ok)
|
if(ok)
|
||||||
printf("gtk_live_test: ok (scale %d)\n", scale);
|
printf("gtk_live_test: ok (scale %d, stalled peer %lld ms)\n",
|
||||||
|
scale, (long long)stalledms);
|
||||||
return !ok;
|
return !ok;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user