#include #include #include #include #ifdef GDK_WINDOWING_X11 #include #endif #include "ipc.h" typedef struct Im Im; struct Im { GtkIMContextSimple parent; int fd; int usepreedit; int ext; int private; int simpleactive; int simpledone; char pre[Ipcfieldmax+1]; int prelen; GdkWindow *win; GdkRectangle cursor; int cursorvalid; int caretsent; unsigned char sent[Ipccaretsz]; }; 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); } /* Both buffers are char[Ipcfieldmax+1]. */ static int readresp(Im *im, int want, char *commit, char *pre, Ipcresp *resp) { if(ipcreadresp(im->fd, want, commit, pre, resp) < 0) return -1; if(!g_utf8_validate(commit, resp->commitlen, NULL) || (want && !g_utf8_validate(pre, resp->preeditlen, NULL))){ 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; } /* Root-relative caret in device pixels; returns whether it is known. */ static int caretget(Im *im, int32_t *x, int32_t *y, int32_t *h) { gint rx, ry, scale; int64_t sx, sy, sh; *x = *y = *h = 0; if(im->win != NULL && gdk_window_is_destroyed(im->win)) dropwindow(im); if(!im->cursorvalid || im->win == NULL || im->cursor.height < 0) return 0; #ifdef GDK_WINDOWING_X11 if(!GDK_IS_X11_WINDOW(im->win)) return 0; #else return 0; #endif scale = gdk_window_get_scale_factor(im->win); if(scale <= 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) return 0; *x = sx; *y = sy; *h = sh; return 1; } static int sendcaret(Im *im) { unsigned char buf[Ipccaretsz]; int32_t x, y, h; int valid; if(im->fd < 0 || !im->ext) return 0; valid = caretget(im, &x, &y, &h); ipcpackcaret(buf, valid, x, y, h); if(im->caretsent && memcmp(buf, im->sent, sizeof buf) == 0) return 0; if(ipcsend(im->fd, buf, sizeof buf) < 0) return -1; memcpy(im->sent, buf, sizeof buf); im->caretsent = 1; 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, pre, &resp) < 0){ srvclose(im); return -1; } im->ext = resp.eaten != 0; if(sendcaret(im) < 0){ srvclose(im); return -1; } return 0; } static void simplecommit(GtkIMContext *ctx, const char *s, Im *im) { (void)ctx; (void)s; im->simpledone = 1; } static void simpleend(GtkIMContext *ctx, Im *im) { (void)ctx; im->simpledone = 1; } /* * GtkIMContextSimple composes dead keys and Compose sequences; it keeps * the key stream until it commits, ends its preedit, or rejects a press. */ static gboolean simplefilter(GtkIMContext *ctx, GdkEventKey *ev, int release) { Im *im; gboolean r; im = (Im*)ctx; im->simpleactive = 1; im->simpledone = 0; r = parentim->filter_keypress(ctx, ev); if((!r && !release) || im->simpledone) im->simpleactive = 0; return r; } /* The daemon hands the pending text back; it becomes committed text. */ 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, pre, &resp) < 0){ srvclose(im); return; } setpreedit(im, "", 0); if(commit[0] != '\0') g_signal_emit_by_name(im, "commit", commit); } 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 = ipckeysym(ev->keyval, gdk_keyval_to_unicode(ev->keyval)); mod = ipcmod(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, 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; /* Closing the connection releases the engine. */ sendreset(im); 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, 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; }