#include #include #include #include #include #include #ifdef GDK_WINDOWING_X11 #include #endif #include "ipc.h" typedef struct Im Im; 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; struct ImClass { GtkIMContextClass parent; }; static GType imtype; static void setpreedit(Im *im, const char *s, int n) { int was; if(n < 0 || n > Ipcfieldmax) n = 0; if(n == im->prelen && memcmp(im->pre, s, n) == 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 srvclose(Im *im) { if(im->fd >= 0) close(im->fd); im->fd = -1; im->ext = 0; im->effective = 1; im->caretsent = 0; setpreedit(im, "", 0); } static int readresp(Im *im, int want, int update, char *commit, int ncommit, Ipcresp *resp) { char pre[Ipcfieldmax+1]; if(ipcreadresp(im->fd, want, commit, ncommit, pre, sizeof pre, resp) < 0) return -1; 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 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 sendreset(Im *im) { unsigned char buf[Ipcreqsz]; 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; } 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 kpress(GtkIMContext *ctx, GdkEventKey *ev) { Im *im; unsigned char buf[Ipcreqsz]; char commit[Ipcfieldmax+1]; uint32_t key, mod; Ipcresp resp; im = (Im*)ctx; if(ev->type != GDK_KEY_PRESS) return FALSE; key = kget(ev->keyval); if(key == 0) return FALSE; mod = mget(ev->state); if(im->private) return plaincommit(ctx, key, mod); 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, 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); } if(commit[0] != '\0') g_signal_emit_by_name(ctx, "commit", commit); if(resp.eaten) return TRUE; return plaincommit(ctx, key, mod); } static void getpreedit(GtkIMContext *ctx, gchar **str, PangoAttrList **attrs, gint *cursor_pos) { Im *im; PangoAttribute *u; im = (Im*)ctx; 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) { sendreset((Im*)ctx); } static void focusout(GtkIMContext *ctx) { Im *im; im = (Im*)ctx; 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]; 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; 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'; 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 classinit(ImClass *klass) { GtkIMContextClass *ic; GObjectClass *oc; ic = GTK_IM_CONTEXT_CLASS(klass); oc = G_OBJECT_CLASS(klass); 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->finalize = finalize; } 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, "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; }