summaryrefslogtreecommitdiff
path: root/context.c
diff options
context:
space:
mode:
authorJavier S. Pedro <maemo@javispedro.com>2012-04-11 03:12:40 +0200
committerJavier S. Pedro <maemo@javispedro.com>2012-04-11 03:12:40 +0200
commitbfbf55700091bb6084b3cbe151a762816db0e3f0 (patch)
treebf7605926a6e8fb06c85b1d1a605d889ca67a79f /context.c
downloadxmimd-bfbf55700091bb6084b3cbe151a762816db0e3f0.tar.gz
xmimd-bfbf55700091bb6084b3cbe151a762816db0e3f0.zip
initial import
Diffstat (limited to 'context.c')
-rw-r--r--context.c644
1 files changed, 644 insertions, 0 deletions
diff --git a/context.c b/context.c
new file mode 100644
index 0000000..6001e0a
--- /dev/null
+++ b/context.c
@@ -0,0 +1,644 @@
+#include <glib.h>
+#include <glib-object.h>
+#include <X11/Xutil.h>
+#include "meego/meego-im-proxy.h"
+#include "meego/qt-translate.h"
+#include "meego/meego-im-defs.h"
+
+#include "ximserver.h"
+#include "xmim.h"
+#include "mimclient.h"
+#include "context.h"
+
+typedef struct _Context
+{
+ guint16 connect_id;
+ guint16 icid;
+ XIMStyle style;
+ Window toplevel_window;
+ Window client_window;
+ Window focus_window;
+ GHashTable *info;
+ gboolean preediting;
+ GString *preedit_str;
+} Context;
+
+static GHashTable *contexts;
+static guint16 last_icid;
+static guint16 focused_icid;
+
+static MeegoIMProxy *proxy;
+
+static char * utf8_to_compound_text(const char *string)
+{
+ XTextProperty tp;
+
+ Xutf8TextListToTextProperty(x_dpy, (char **)&string, 1, XCompoundTextStyle, &tp);
+
+ return (char*) tp.value;
+}
+
+static GValue* context_info_value_new(GType type)
+{
+ GValue *val = g_slice_new0(GValue);
+ return g_value_init(val, type);
+}
+
+static void context_info_value_destroy(GValue *val)
+{
+ g_value_unset(val);
+ g_slice_free(GValue, val);
+}
+
+static GValue *context_info_lookup(Context *ctx, const char* name)
+{
+ return g_hash_table_lookup(ctx->info, name);
+}
+
+static void context_info_unset(Context *ctx, const char *name)
+{
+ g_hash_table_remove(ctx->info, name);
+}
+
+static void context_info_set_bool(Context *ctx, const char* name, gboolean value)
+{
+ GValue *val = context_info_lookup(ctx, name);
+ if (val) {
+ g_value_set_boolean(val, value);
+ } else {
+ val = context_info_value_new(G_TYPE_BOOLEAN);
+ g_value_set_boolean(val, value);
+ g_hash_table_insert(ctx->info, (gpointer)name, val);
+ }
+}
+
+static void context_info_set_int(Context *ctx, const char* name, gint value)
+{
+ GValue *val = context_info_lookup(ctx, name);
+ if (val) {
+ g_value_set_int(val, value);
+ } else {
+ val = context_info_value_new(G_TYPE_INT);
+ g_value_set_int(val, value);
+ g_hash_table_insert(ctx->info, (gpointer)name, val);
+ }
+}
+
+static void context_info_set_uint64(Context *ctx, const char* name, guint64 value)
+{
+ GValue *val = context_info_lookup(ctx, name);
+ if (val) {
+ g_value_set_uint64(val, value);
+ } else {
+ val = context_info_value_new(G_TYPE_UINT64);
+ g_value_set_uint64(val, value);
+ g_hash_table_insert(ctx->info, (gpointer)name, val);
+ }
+}
+
+static void context_info_set_rect(Context *ctx, const char* name, int x, int y, int w, int h)
+{
+ GValue *val = context_info_lookup(ctx, name);
+ if (val) {
+ dbus_g_type_struct_set(val, 0, x, 1, y, 2, w, 3, h, G_MAXUINT);
+ } else {
+ GType type = dbus_g_type_get_struct("GValueArray", G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INVALID);
+ GValueArray *arr = (GValueArray*) dbus_g_type_specialized_construct(type);
+ g_return_if_fail(arr);
+
+ val = context_info_value_new(type);
+ g_value_take_boxed(val, arr);
+
+ if (!dbus_g_type_struct_set(val, 0, x, 1, y, 2, w, 3, h, G_MAXUINT)) {
+ g_value_unset(val);
+ g_return_if_reached();
+ }
+
+ g_hash_table_insert(ctx->info, (gpointer)name, val);
+ }
+}
+
+static Context* context_alloc()
+{
+ Context* ctx = g_slice_new0(Context);
+ g_return_val_if_fail(ctx, NULL);
+ ctx->info = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) context_info_value_destroy);
+ ctx->preedit_str = g_string_new(NULL);
+
+ // TODO Move this somewhere else
+ context_info_set_int(ctx, "contentType", MeegoImFreeTextContentType);
+
+ return ctx;
+}
+
+static void context_free(Context *ctx)
+{
+ g_string_free(ctx->preedit_str, TRUE);
+ g_hash_table_destroy(ctx->info);
+ g_slice_free(Context, ctx);
+}
+
+void contexts_init()
+{
+ contexts = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) context_free);
+ last_icid = 0;
+ focused_icid = 0;
+
+ proxy = meego_im_proxy_get_singleton();
+}
+
+void contexts_destroy()
+{
+ g_hash_table_destroy(contexts);
+}
+
+guint contexts_focused_icid()
+{
+ return focused_icid;
+}
+
+static Context* contexts_lookup(guint icid)
+{
+ return g_hash_table_lookup(contexts, GUINT_TO_POINTER(icid));
+}
+
+static Window find_toplevel_window(Window win)
+{
+ Window root, parent;
+ Window *children = NULL;
+ unsigned int nchildren;
+
+ if (XQueryTree(x_dpy, win, &root, &parent, &children, &nchildren)) {
+ if (children) XFree(children);
+ if (parent == root) {
+ return win;
+ } else {
+ g_return_val_if_fail(win != parent, win);
+ return find_toplevel_window(parent);
+ }
+ }
+
+ g_warn_if_reached();
+ return win;
+}
+
+static gboolean context_update_style(Context *ctx, XIMStyle style)
+{
+ if (ctx->style != style) {
+ ctx->style = style;
+
+ if (ctx->style & XIMPreeditNone) {
+ // No preedit? Set the virtual keyboard in direct/raw mode.
+ context_info_set_int(ctx, "inputMethodMode", MeegoImInputMethodModeDirect);
+ } else {
+ context_info_set_int(ctx, "inputMethodMode", MeegoImInputMethodModeNormal);
+ }
+
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+static gboolean context_update_client_window(Context *ctx, Window w)
+{
+ if (ctx->client_window != w) {
+ ctx->client_window = w;
+
+ if (opt_xephyr) {
+ // Consider the passed window id the top level window id
+ // Remember NOT to use it!, it is not accessible from our X11 server!
+ ctx->toplevel_window = opt_xephyr;
+ } else {
+ ctx->toplevel_window = find_toplevel_window(w);
+ }
+
+ if (ctx->toplevel_window) {
+ context_info_set_uint64(ctx, "winId", ctx->toplevel_window);
+ } else {
+ context_info_unset(ctx, "winId");
+ }
+
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+static gboolean context_update_spot_location(Context *ctx, const XPoint *p)
+{
+ Window win = ctx->focus_window;
+ Window root = DefaultRootWindow(x_dpy);
+ int x, y;
+ Window child;
+
+ if (!win) {
+ win = ctx->client_window;
+ }
+ g_warn_if_fail(win);
+
+ if (win && XTranslateCoordinates(x_dpy, win, root, p->x, p->y, &x, &y, &child)) {
+ g_debug("Root coordinates %d %d", x, y);
+ context_info_set_rect(ctx, "cursorRectangle", x, y, 2, 20);
+ } else {
+ g_warn_if_reached();
+ context_info_unset(ctx, "cursorRectangle");
+ }
+ return TRUE;
+}
+
+static gboolean context_parse_ic_values(Context *ctx, IMChangeICStruct *call_data)
+{
+ XICAttribute *attr;
+ int attr_num;
+ gboolean changed = FALSE;
+
+ ctx->connect_id = call_data->connect_id;
+
+ attr = call_data->ic_attr;
+ attr_num = call_data->ic_attr_num;
+ for (int i = 0; i < attr_num; ++i, ++attr) {
+ if (strcmp(XNInputStyle, attr->name) == 0) {
+ changed |= context_update_style(ctx, *(CARD32 *) attr->value);
+ } else if (strcmp(XNClientWindow, attr->name) == 0) {
+ changed |= context_update_client_window(ctx, *(CARD32 *) attr->value);
+ } else if (strcmp(XNFocusWindow, attr->name) == 0) {
+ ctx->focus_window = *(CARD32 *) attr->value;
+ } else {
+ g_warning("Ignoring unknown ic attribute: %s", attr->name);
+ }
+ }
+
+ attr = call_data->preedit_attr;
+ attr_num = call_data->preedit_attr_num;
+ for (int i = 0; i < attr_num; ++i, ++attr) {
+ if (strcmp(XNSpotLocation, attr->name) == 0) {
+ changed |= context_update_spot_location(ctx, (XPoint*) attr->value);
+ } else {
+ g_debug("Ignoring unknown ic preedit attribute: %s", attr->name);
+ }
+ }
+
+ return changed;
+}
+
+static void context_start_preedit(Context *ctx)
+{
+ g_return_if_fail(!(ctx->style & XIMPreeditNone));
+ if (!ctx->preediting) {
+ IMPreeditCBStruct pcb = {0};
+ pcb.major_code = XIM_PREEDIT_START;
+ pcb.connect_id = ctx->connect_id;
+ pcb.icid = ctx->icid;
+ xims_call_callback((XPointer)&pcb);
+ ctx->preediting = TRUE;
+ }
+}
+
+static void context_stop_preedit(Context *ctx, gboolean commit)
+{
+ if (ctx->preediting) {
+ IMPreeditCBStruct pcb = {0};
+ pcb.major_code = XIM_PREEDIT_DONE;
+ pcb.connect_id = ctx->connect_id;
+ pcb.icid = ctx->icid;
+ xims_call_callback((XPointer)&pcb);
+ ctx->preediting = FALSE;
+
+ if (commit) {
+ xims_commit(ctx->connect_id, ctx->icid, 0, ctx->preedit_str->str);
+ }
+
+ g_string_truncate(ctx->preedit_str, 0);
+ }
+}
+
+static void context_info_print_value(gpointer key, gpointer value, gpointer user_data)
+{
+ GValue *val = value;
+ const gchar *k = key;
+ gchar *v = g_strdup_value_contents(val);
+ g_debug(" %s: %s", k, v);
+}
+
+static void context_info_print(Context *ctx)
+{
+ g_hash_table_foreach(ctx->info, context_info_print_value, NULL);
+}
+
+gboolean context_create(IMChangeICStruct *call_data)
+{
+ // Safeguard against being overrun with entries
+ g_return_val_if_fail(g_hash_table_size(contexts) < G_MAXINT16, FALSE);
+
+ Context *ctx = context_alloc();
+ g_return_val_if_fail(ctx, FALSE);
+
+ guint icid = ++last_icid;
+ while (g_hash_table_lookup(contexts, GUINT_TO_POINTER(icid))) {
+ // icid collision! Try luck with another one
+ icid = ++last_icid;
+ }
+
+ ctx->connect_id = call_data->connect_id;
+ ctx->icid = icid;
+
+ context_parse_ic_values(ctx, call_data);
+
+ g_hash_table_insert(contexts, GUINT_TO_POINTER(icid), ctx);
+
+ call_data->icid = icid;
+
+ return TRUE;
+}
+
+gboolean context_destroy(IMChangeICStruct *call_data)
+{
+ const guint icid = call_data->icid;
+ Context *ctx = g_hash_table_lookup(contexts, GUINT_TO_POINTER(icid));
+ if (icid == focused_icid) {
+ g_message("Destroying focused context");
+ focused_icid = 0;
+ }
+ if (ctx) {
+ g_hash_table_remove(contexts, GUINT_TO_POINTER(icid));
+ return TRUE;
+ } else {
+ g_warn_if_reached();
+ return FALSE;
+ }
+}
+
+gboolean context_set_values(IMChangeICStruct *call_data)
+{
+ Context *ctx = contexts_lookup(call_data->icid);
+ g_return_val_if_fail(ctx, FALSE);
+
+ gboolean changed = context_parse_ic_values(ctx, call_data);
+ if (focused_icid == ctx->icid && changed) {
+ meego_im_proxy_update_widget_info(proxy, ctx->info, FALSE);
+ }
+
+ return TRUE;
+}
+
+gboolean context_get_values(IMChangeICStruct *call_data)
+{
+ Context *ctx = contexts_lookup(call_data->icid);
+ g_return_val_if_fail(ctx, FALSE);
+
+ XICAttribute *ic_attr = call_data->ic_attr;
+ for (int i = 0; i < (int)call_data->ic_attr_num; i++, ic_attr++) {
+ if (strcmp(XNFilterEvents, ic_attr->name) == 0) {
+ ic_attr->value_length = sizeof(CARD32);
+ ic_attr->value = (void *)malloc(sizeof(CARD32));
+ if (ctx->style & XIMPreeditNone) {
+ // No need to filter events in direct mode
+ *(CARD32*)ic_attr->value = 0;
+ } else {
+ *(CARD32*)ic_attr->value = KeyPressMask | KeyReleaseMask;
+ }
+ }
+ }
+ return TRUE;
+}
+
+gboolean context_set_focus(IMChangeFocusStruct *call_data)
+{
+ Context *ctx = contexts_lookup(call_data->icid);
+ g_return_val_if_fail(ctx, FALSE);
+
+ context_info_set_bool(ctx, "focusState", TRUE);
+
+ focused_icid = call_data->icid;
+ if (meego_im_proxy_activate_context(proxy)) {
+ meego_im_proxy_app_orientation_changed(proxy, MeegoImAngle0);
+ meego_im_proxy_update_widget_info(proxy, ctx->info, TRUE);
+
+ if (!(ctx->style & XIMStatusNone)) {
+ meego_im_proxy_show_input_method(proxy);
+ } else g_debug("Not showing the keyboard because of XIMStatusNone");
+ return TRUE;
+ } else {
+ g_warning("Failed to activate a Meego input context");
+ return FALSE;
+ }
+}
+
+gboolean context_unset_focus(IMChangeFocusStruct *call_data)
+{
+ Context *ctx = contexts_lookup(call_data->icid);
+ g_return_val_if_fail(ctx, FALSE);
+
+ context_stop_preedit(ctx, TRUE);
+ context_info_set_bool(ctx, "focusState", FALSE);
+
+ if (call_data->icid == focused_icid) {
+ meego_im_proxy_reset(proxy);
+ meego_im_proxy_hide_input_method(proxy);
+ focused_icid = 0;
+ }
+
+ return TRUE;
+}
+
+gboolean context_forward_event(IMForwardEventStruct *call_data)
+{
+ Context *ctx = contexts_lookup(call_data->icid);
+ g_return_val_if_fail(ctx, FALSE);
+
+ if (!redirect_keys) {
+ g_debug("Not forwarding to mim");
+ xims_forward_event(call_data);
+ return TRUE;
+ }
+
+ if (call_data->event.type == KeyPress || call_data->event.type == KeyRelease) {
+ gint type, code, modifiers;
+ gchar *text;
+ MeegoImKeyEvent e;
+ char smallbuf[32+1];
+ gchar *buf = NULL;
+ char *chars = smallbuf;
+ KeySym keysym;
+ Status status;
+ int len = Xutf8LookupString(x_ic, &call_data->event.xkey, smallbuf, 32, &keysym, &status);
+ if (status == XBufferOverflow) {
+ buf = g_malloc(len + 1);
+ len = Xutf8LookupString(x_ic, &call_data->event.xkey, buf, len, &keysym, &status);
+ chars = buf;
+ }
+
+ e.release = call_data->event.type == KeyRelease;
+ e.state = call_data->event.xkey.state;
+ e.keysym = XLookupKeysym(&call_data->event.xkey, 0);
+
+ switch (status) {
+ case XLookupBoth:
+ case XLookupChars:
+ chars[len] = '\0';
+ e.text = chars;
+ break;
+ default:
+ e.text = NULL;
+ break;
+ }
+
+ meego_im_key_event_encode(&e, &type, &code, &modifiers, &text);
+ g_debug("Forwarding to mim %d %d %d %s", type, code, modifiers, text);
+ meego_im_proxy_process_key_event(proxy, type, code, modifiers, text,
+ FALSE, 1,
+ call_data->event.xkey.keycode, e.state,
+ call_data->event.xkey.time);
+ if (buf) g_free(buf);
+ }
+
+ return TRUE;
+}
+
+gboolean context_reset(IMResetICStruct *call_data)
+{
+ Context *ctx = contexts_lookup(call_data->icid);
+ g_return_val_if_fail(ctx, FALSE);
+
+ call_data->commit_string = utf8_to_compound_text(ctx->preedit_str->str);
+ call_data->length = strlen(call_data->commit_string);
+ meego_im_proxy_reset(proxy);
+ context_stop_preedit(ctx, FALSE);
+
+ // Please note that I've patched IMdkit so that it will call XFree on
+ // call_data->commit_string.
+ // You probably haven't, so take care to avoid more leaks.
+
+ return TRUE;
+}
+
+gboolean context_commit(guint icid, const char *string,
+ int replace_start, int replace_length, int cursor_pos)
+{
+ Context *ctx = contexts_lookup(icid);
+ g_return_val_if_fail(ctx, FALSE);
+ g_return_val_if_fail(string, FALSE);
+
+ context_stop_preedit(ctx, FALSE);
+ xims_commit(ctx->connect_id, icid, 0, string);
+
+ return TRUE;
+}
+
+gboolean context_update_preedit(guint icid, const char *string,
+ int preedit_format_count, const MeegoImPreeditFormat *preedit_formats,
+ int replace_start, int replace_length, int cursor_pos)
+{
+ Context *ctx = contexts_lookup(icid);
+ g_return_val_if_fail(ctx, FALSE);
+ g_return_val_if_fail(string, FALSE);
+
+ if (!(ctx->style & XIMPreeditCallbacks)) {
+ g_message("Got update_preedit msg but preedit callbacks not supported");
+ return TRUE;
+ }
+ glong string_len = g_utf8_strlen(string, -1);
+ if (string_len > 0) {
+ context_start_preedit(ctx); // Will start only if not already started
+ } else {
+ context_stop_preedit(ctx, FALSE);
+ return TRUE;
+ }
+
+ IMPreeditCBStruct s = { 0 };
+ XIMText text = { 0 };
+ XIMFeedback feedback[string_len + 1];
+
+ for (int c = 0; c < string_len; c++) {
+ feedback[c] = XIMUnderline;
+ }
+
+#if 0
+ for (int i = 0; i < preedit_format_count; i++) {
+ const MeegoImPreeditFormat * f = &preedit_formats[i];
+ for (int c = f->start; c < f->start + f->length; c++) {
+ switch (f->face) {
+ case MeegoImPreeditDefault:
+ feedback[c] = XIMUnderline;
+ break;
+ default:
+ feedback[c] = 0;
+ break;
+ }
+ }
+ }
+#endif
+
+ feedback[string_len] = 0;
+
+ char *ctext = utf8_to_compound_text(string);
+
+ text.string.multi_byte = ctext;
+ text.length = strlen(ctext);
+ text.feedback = feedback;
+
+ g_debug("Sending text '%s'(%u)", text.string.multi_byte, text.length);
+
+ s.major_code = XIM_PREEDIT_DRAW;
+ s.connect_id = ctx->connect_id;
+ s.icid = icid;
+
+ s.todo.draw.text = &text;
+
+ if (replace_start == 0 && replace_length == 0) {
+ // Special case: Replace all the string
+ // TODO Is this true? Does start=0, len=0 mean that?
+ replace_start = 0;
+ replace_length = ctx->preedit_str->len;
+
+ g_string_assign(ctx->preedit_str, string);
+ } else {
+ g_warn_if_fail(replace_start < 0);
+ g_warn_if_fail(replace_length < 0);
+
+ g_string_erase(ctx->preedit_str, replace_start, replace_length);
+ g_string_insert(ctx->preedit_str, replace_start, string);
+ }
+
+ s.todo.draw.chg_first = replace_start;
+ s.todo.draw.chg_length = replace_length;
+
+ if (cursor_pos < 0) {
+ // Assume we mean at the end of the preedit string
+ cursor_pos = ctx->preedit_str->len;
+ }
+ s.todo.draw.caret = cursor_pos;
+
+ g_debug(" chg %d %d caret %d", s.todo.draw.chg_first, s.todo.draw.chg_length, s.todo.draw.caret);
+
+ xims_call_callback((XPointer)&s);
+
+ XFree(ctext);
+
+ return TRUE;
+}
+
+gboolean context_send_key(guint icid, const MeegoImKeyEvent* e)
+{
+ Context *ctx = contexts_lookup(icid);
+ g_return_val_if_fail(ctx, FALSE);
+
+ XEvent xev = { 0 };
+ xev.xkey.type = e->release ? KeyRelease : KeyPress;
+ xev.xkey.send_event = True;
+ xev.xkey.display = x_dpy;
+ xev.xkey.window = ctx->client_window;
+ xev.xkey.root = DefaultRootWindow(x_dpy);
+ xev.xkey.subwindow = ctx->focus_window;
+ xev.xkey.time = CurrentTime;
+ xev.xkey.state = e->state;
+ xev.xkey.keycode = XKeysymToKeycode(x_dpy, e->keysym);
+ xev.xkey.same_screen = True;
+
+ xims_insert_event(ctx->connect_id, icid, &xev);
+
+ return TRUE;
+}