From bfbf55700091bb6084b3cbe151a762816db0e3f0 Mon Sep 17 00:00:00 2001 From: "Javier S. Pedro" Date: Wed, 11 Apr 2012 03:12:40 +0200 Subject: initial import --- context.c | 644 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 644 insertions(+) create mode 100644 context.c (limited to 'context.c') 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 +#include +#include +#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; +} -- cgit v1.2.3