Files
strans/gtk/main.c

599 lines
11 KiB
C

#include <errno.h>
#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;
struct Im
{
GtkIMContextSimple parent;
int fd;
int usepreedit;
int ext;
int private;
int simpleactive;
int insimple;
int simpledone;
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;
struct ImClass
{
GtkIMContextSimpleClass parent;
};
static GType imtype;
static GObjectClass *parentobject;
static GtkIMContextClass *parentim;
static void
setpreedit(Im *im, const char *s, int n)
{
int was;
if(n < 0 || n > Ipcfieldmax)
n = 0;
if(n == 0 && im->prelen == 0)
return;
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
srvdrop(Im *im, int notify)
{
if(im->fd >= 0)
close(im->fd);
im->fd = -1;
im->ext = 0;
im->caretsent = 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
readresp(Im *im, int want, char *commit, int ncommit, char *pre, int npre,
Ipcresp *resp)
{
if(ipcreadresp(im->fd, want, commit, ncommit, pre, npre,
resp) < 0)
return -1;
if(!validtext(commit, resp->commitlen) ||
(want && !validtext(pre, resp->preeditlen))){
errno = EPROTO;
return -1;
}
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];
char pre[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, commit, sizeof commit,
pre, sizeof pre, &resp) < 0){
srvclose(im);
return -1;
}
if(im->usepreedit)
setpreedit(im, pre, resp.preeditlen);
im->ext = resp.eaten != 0;
if(!im->usepreedit)
setpreedit(im, "", 0);
if(im->ext && sendcaret(im) < 0){
srvclose(im);
return -1;
}
return 0;
}
static uint32_t
kget(uint32_t gdk)
{
uint32_t u;
u = gdk_keyval_to_unicode(gdk);
if((gdk & 0xff000000) == 0x01000000 && u != 0)
return u;
if(gdk >= 0xff00 && gdk <= 0xffff)
return Kspec + (gdk - 0xff00);
return u;
}
static uint32_t
mget(uint32_t state)
{
uint32_t m;
m = 0;
if(state & GDK_SHIFT_MASK)
m |= Mshift;
if(state & GDK_CONTROL_MASK)
m |= Mctrl;
if(state & GDK_MOD1_MASK)
m |= Malt;
if(state & GDK_SUPER_MASK)
m |= Msuper;
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
sendreset(Im *im)
{
unsigned char buf[Ipcreqsz];
char commit[Ipcfieldmax+1];
char pre[Ipcfieldmax+1];
Ipcresp resp;
if(im->fd < 0){
setpreedit(im, "", 0);
return;
}
ipcpackreset(buf, im->usepreedit);
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
readresp(im, im->usepreedit, commit, sizeof commit,
pre, sizeof pre, &resp) < 0){
srvclose(im);
return;
}
setpreedit(im, "", 0);
}
static gboolean
kpress(GtkIMContext *ctx, GdkEventKey *ev)
{
Im *im;
unsigned char buf[Ipcreqsz];
char commit[Ipcfieldmax+1];
char pre[Ipcfieldmax+1];
uint32_t key, mod;
Ipcresp resp;
im = (Im*)ctx;
if(ev->type != GDK_KEY_PRESS){
if(im->simpleactive)
return simplefilter(ctx, ev, 1);
return FALSE;
}
if(im->simpleactive)
return simplefilter(ctx, ev, 0);
key = kget(ev->keyval);
mod = mget(ev->state);
if(im->private || key == 0)
return simplefilter(ctx, ev, 0);
if(srvconnect(im) < 0)
return simplefilter(ctx, ev, 0);
/* A retained GDK window may have moved since the last cursor report. */
if(sendcaret(im) < 0){
srvclose(im);
return simplefilter(ctx, ev, 0);
}
ipcpackreq(buf, im->usepreedit, mod, key);
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
readresp(im, im->usepreedit, commit, sizeof commit,
pre, sizeof pre, &resp) < 0){
srvclose(im);
return simplefilter(ctx, ev, 0);
}
if(im->usepreedit && commit[0] != '\0' && im->prelen > 0)
setpreedit(im, "", 0);
if(commit[0] != '\0')
g_signal_emit_by_name(ctx, "commit", commit);
if(im->usepreedit)
setpreedit(im, pre, resp.preeditlen);
if(resp.eaten)
return TRUE;
return simplefilter(ctx, ev, 0);
}
static void
getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs,
gint *cursor_pos)
{
Im *im;
PangoAttribute *u;
im = (Im*)ctx;
if(im->simpleactive){
parentim->get_preedit_string(ctx, str, attrs, cursor_pos);
return;
}
if(str)
*str = g_strdup(im->pre);
if(attrs){
*attrs = pango_attr_list_new();
if(im->prelen > 0){
u = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
u->start_index = 0;
u->end_index = im->prelen;
pango_attr_list_insert(*attrs, u);
}
}
if(cursor_pos)
*cursor_pos = g_utf8_strlen(im->pre, -1);
}
static void
reset(GtkIMContext *ctx)
{
if(parentim->reset != NULL)
parentim->reset(ctx);
((Im*)ctx)->simpleactive = 0;
sendreset((Im*)ctx);
}
static void
focusout(GtkIMContext *ctx)
{
Im *im;
im = (Im*)ctx;
if(parentim->focus_out != NULL)
parentim->focus_out(ctx);
im->simpleactive = 0;
sendreset(im);
/* Closing the context connection releases engine ownership. */
srvclose(im);
}
static void
setusepreedit(GtkIMContext *ctx, gboolean use)
{
Im *im;
unsigned char buf[Ipcreqsz];
char commit[Ipcfieldmax+1];
char pre[Ipcfieldmax+1];
Ipcresp resp;
im = (Im*)ctx;
use = use != FALSE;
if(parentim->set_use_preedit != NULL)
parentim->set_use_preedit(ctx, use);
if(im->usepreedit == use)
return;
im->usepreedit = use;
if(!use)
setpreedit(im, "", 0);
if(im->fd < 0 || !im->ext)
return;
ipcpackcap(buf, use);
if(ipcsend(im->fd, buf, sizeof buf) < 0 ||
readresp(im, use, commit, sizeof commit,
pre, sizeof pre, &resp) < 0 ||
!resp.eaten){
srvclose(im);
return;
}
if(use)
setpreedit(im, pre, resp.preeditlen);
}
static void
setclientwindow(GtkIMContext *ctx, GdkWindow *win)
{
Im *im;
im = (Im*)ctx;
if(win != NULL && gdk_window_is_destroyed(win))
win = NULL;
if(parentim->set_client_window != NULL)
parentim->set_client_window(ctx, win);
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(parentim->set_cursor_location != NULL)
parentim->set_cursor_location(ctx, area);
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)
reset(GTK_IM_CONTEXT(im));
}
static void
dispose(GObject *obj)
{
Im *im;
im = (Im*)obj;
srvdrop(im, 0);
dropwindow(im);
parentobject->dispose(obj);
}
static void
init(Im *im)
{
GtkInputPurpose purpose;
im->fd = -1;
im->usepreedit = 1;
im->pre[0] = '\0';
g_object_get(im, "input-purpose", &purpose, NULL);
im->private = isprivate(purpose);
g_signal_connect(im, "notify::input-purpose",
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
classinit(ImClass *klass)
{
GtkIMContextClass *ic;
GObjectClass *oc;
ic = GTK_IM_CONTEXT_CLASS(klass);
oc = G_OBJECT_CLASS(klass);
parentim = g_type_class_peek_parent(klass);
parentobject = G_OBJECT_CLASS(parentim);
ic->filter_keypress = kpress;
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->dispose = dispose;
}
static const GtkIMContextInfo info = {
"strans",
"strans",
"strans",
"",
"*",
};
static const GtkIMContextInfo *infolist[] = { &info };
G_MODULE_EXPORT void
im_module_init(GTypeModule *mod)
{
static const GTypeInfo ti = {
sizeof(ImClass),
NULL, NULL,
(GClassInitFunc)classinit,
NULL, NULL,
sizeof(Im),
0,
(GInstanceInitFunc)init,
};
imtype = g_type_module_register_type(mod, GTK_TYPE_IM_CONTEXT_SIMPLE,
"strans-gtk", &ti, 0);
}
G_MODULE_EXPORT void
im_module_exit(void)
{
}
G_MODULE_EXPORT void
im_module_list(const GtkIMContextInfo ***contexts, int *n)
{
*contexts = infolist;
*n = 1;
}
G_MODULE_EXPORT GtkIMContext*
im_module_create(const char *id)
{
if(strcmp(id, "strans") == 0)
return g_object_new(imtype, NULL);
return NULL;
}