gtk: honor preedit policy and cursor placement
This commit is contained in:
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user