initial import for sdlhaa & sdlhim
[sdlhildon] / sdlhim / src / SDL_him.c
diff --git a/sdlhim/src/SDL_him.c b/sdlhim/src/SDL_him.c
new file mode 100644 (file)
index 0000000..8169be5
--- /dev/null
@@ -0,0 +1,945 @@
+/* This file is part of SDL_him - SDL Hildon Input Method addon
+ * Copyright (C) 2010 Javier S. Pedro
+ * Copyright (C) 2005-2010 Nokia Corporation and/or its subsidiary(-ies).
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.         See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA or see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <X11/XKBlib.h>
+#include <X11/extensions/XKBstr.h>
+#include <SDL.h>
+#include <SDL_syswm.h>
+
+#include "SDL_him.h"
+
+/* This pulls Gtk+ headers, but we do not link with Gtk+, Gdk or GLib */
+#include <gdk/gdk.h>
+#include <hildon-im-protocol.h>
+
+/* Specific Maemo configuration */
+#define COMPOSE_KEY XK_Multi_key
+#define LEVEL_KEY XK_ISO_Level3_Shift
+#define LevelMask Mod5Mask
+
+#define NUMERIC_LEVEL 2
+#define LOCKABLE_LEVEL 4
+
+#include "modifier.h"
+#include "unicode.h"
+#include "ds.h"
+#include "atoms.h"
+
+static Display *display;
+
+static Uint8 event_base;
+
+static Bool initialized = False;
+static Bool enabled = False;
+
+static HildonIMOptionMask options = 0;
+static HildonIMTrigger trigger_mode = HILDON_IM_TRIGGER_NONE;
+static HildonGtkInputMode input_mode = HILDON_GTK_INPUT_MODE_FULL;
+static HildonIMCommitMode commit_mode = HILDON_IM_COMMIT_DIRECT;
+static HildonIMInternalModifierMask mod_mask = 0;
+
+static Uint32 combining_char = 0;
+static KeySym last_keysym = XK_VoidSymbol;
+static Bool auto_upper = False;
+static Bool space_after_commit = False;
+
+static dstring_t preedit_buffer = { 0, 0, 0 };
+
+/** Gets the window id of the Hildon IM GUI.
+       We'll use it as target for our messages. 
+ */
+static Window find_hildon_im_window()
+{
+       const Window rootWindow = RootWindow(display, DefaultScreen(display));
+       Window window;
+       int status;
+       Atom actual_type;
+       int actual_format;
+       unsigned long nitems, bytes_after;
+       unsigned char *prop = NULL;
+
+       /* Use RootWindow's HILDON_IM_WINDOW property to find HIM's window */
+       status = XGetWindowProperty(display, rootWindow,
+               ATOM(HILDON_IM_WINDOW), 0, 1, False, XA_WINDOW,
+               &actual_type, &actual_format,
+               &nitems, &bytes_after, &prop);
+
+       if (status != Success || actual_type != XA_WINDOW ||
+                       actual_format != HILDON_IM_WINDOW_ID_FORMAT || nitems != 1)  {
+               SDL_SetError("Failed to get Hildon IM Window id");
+               return -1;
+       }
+
+       window = *(Window*)prop;
+       XFree(prop);
+
+       return window;
+}
+
+/** Gets the window id for the current shown SDL window. */
+static Window find_app_window()
+{
+       SDL_SysWMinfo info;
+       SDL_Surface *screen = SDL_GetVideoSurface();
+
+       SDL_VERSION(&info.version);
+       if (SDL_GetWMInfo(&info) != 1) {
+               // Shouldn't happen
+               assert(0);
+               return 0;
+       }
+
+       if (screen->flags & SDL_FULLSCREEN)
+               return info.info.x11.fswindow;
+       else
+               return info.info.x11.wmwindow;
+}
+
+/* This comes mostly straight from Qt Maemo.
+ Not sure what's exactly doing, or wheter I can replace it with XLookupKeysym */
+static KeySym get_keysym_for_level(KeyCode keycode, int level)
+{
+       XkbDescPtr xkb = XkbGetMap(display, XkbAllClientInfoMask, XkbUseCoreKbd);
+       if (!xkb)
+               return NoSymbol;
+
+       KeySym keysym = XkbKeySymEntry(xkb, keycode, level, 0);
+
+       /* check that the keysym is not repeated in levels 0 and requested */
+       KeySym keysym_test = XkbKeySymEntry(xkb, keycode, 0, 1);
+       if (keysym == keysym_test) {
+               return NoSymbol;
+       }
+
+       return keysym;
+}
+
+static void send_input_mode()
+{
+#if MAEMO_VERSION == 5
+       Window im_window = find_hildon_im_window();
+       Window app_window = find_app_window();
+       if (!app_window || !im_window) return;
+
+       XEvent e = { 0 };
+       e.xclient.type = ClientMessage;
+       e.xclient.window = im_window;
+       e.xclient.message_type = ATOM(HILDON_IM_INPUT_MODE);
+       e.xclient.format = HILDON_IM_INPUT_MODE_FORMAT;
+
+       HildonIMInputModeMessage *msg = (HildonIMInputModeMessage*)(&e.xclient.data);
+
+       msg->input_mode = input_mode;
+       msg->default_input_mode = HILDON_GTK_INPUT_MODE_FULL;
+
+       XSendEvent(display, im_window, False, 0, &e);
+       XFlush(display);
+#elif MAEMO_VERSION == 4
+/* On Diablo, input_mode is sent with the activation message. */
+/* See send_hildon_command() */
+#else
+#error Missing send_input_mode implementation
+#endif
+}
+
+/** Sends a client message with the specified command to the IM window */
+static void send_hildon_command(HildonIMCommand cmd)
+{
+       Window im_window = find_hildon_im_window();
+       Window app_window = find_app_window();
+       if (!app_window || !im_window) return;
+
+       XEvent e = { 0 };
+       e.xclient.type = ClientMessage;
+       e.xclient.window = im_window;
+       e.xclient.message_type = ATOM(HILDON_IM_ACTIVATE);
+       e.xclient.format = HILDON_IM_ACTIVATE_FORMAT;
+
+       HildonIMActivateMessage *msg = (HildonIMActivateMessage*)(&e.xclient.data);
+
+       /* input_window is the window of the focused widget (text box, etc.) */
+       /* SDL doesn't do widgets, so we send the toplevel window */
+       msg->input_window = app_window;
+       /* app_window is the toplevel window containing input_window */
+       msg->app_window = app_window;
+#if MAEMO_VERSION == 4
+       msg->input_mode = input_mode;
+#endif
+       msg->cmd = cmd;
+       msg->trigger = trigger_mode;
+
+       XSendEvent(display, im_window, False, 0, &e);
+       XFlush(display);
+}
+
+/** Forward a key event to the hildon input method window.
+       Why'd we need such a thing? Well, to get the input method GUI to be
+       up to date, seems.
+ */
+static void send_hildon_key_event
+(Bool pressed, unsigned state, KeySym keysym, KeyCode keycode)
+{
+       Window im_window = find_hildon_im_window();
+       Window app_window = find_app_window();
+       if (!app_window || !im_window) return;
+
+       XEvent e = { 0 };
+       e.xclient.type = ClientMessage;
+       e.xclient.window = im_window;
+       e.xclient.message_type = ATOM(HILDON_IM_KEY_EVENT);
+       e.xclient.format = HILDON_IM_KEY_EVENT_FORMAT;
+
+       HildonIMKeyEventMessage *msg = (HildonIMKeyEventMessage*)(&e.xclient.data);
+
+       msg->input_window = app_window;
+
+       /* This protocol is way too gnomish. */
+       if (pressed) {
+               msg->type = GDK_KEY_PRESS;
+       } else {
+               msg->type = GDK_KEY_RELEASE;
+       }
+       msg->state = state;
+       msg->keyval = keysym;
+       msg->hardware_keycode = keycode;
+
+       XSendEvent(display, im_window, False, 0, &e);
+       XFlush(display);
+}
+
+static void send_surrounding_content(const char * text)
+{
+       if (!text) return; /* no need to send this if text is empty */
+
+       Window im_window = find_hildon_im_window();
+       Window app_window = find_app_window();
+       if (!app_window || !im_window) return;
+
+       const unsigned long total_len = strlen(text);
+       Bool first_part = True;
+       unsigned long offset = 0;
+
+       while (first_part || (offset < total_len)) {
+               XEvent e = { 0 };
+               e.xclient.type = ClientMessage;
+               e.xclient.window = im_window;
+               e.xclient.message_type = ATOM(HILDON_IM_SURROUNDING_CONTENT);
+               e.xclient.format = HILDON_IM_SURROUNDING_CONTENT_FORMAT;
+
+               unsigned long len = total_len - offset;
+               if (len > HILDON_IM_CLIENT_MESSAGE_BUFFER_SIZE - 1) {
+                       /* the X11 event is limited in size. */
+                       len = HILDON_IM_CLIENT_MESSAGE_BUFFER_SIZE - 1;
+               }
+
+               HildonIMSurroundingContentMessage *msg = (HildonIMSurroundingContentMessage*)(&e.xclient.data);
+               memcpy(msg->surrounding, text + offset, len);
+
+               if (first_part) {
+                       msg->msg_flag = HILDON_IM_MSG_START;
+               } else if (offset == total_len) {
+                       msg->msg_flag = HILDON_IM_MSG_END;
+               } else {
+                       msg->msg_flag = HILDON_IM_MSG_CONTINUE;
+               }
+
+               XSendEvent(display, im_window, False, 0, &e);
+
+               offset += len;
+               first_part = False;
+       }
+
+       XFlush(display);
+}
+
+static void send_surrounding_header(unsigned int cursor_offset)
+{
+       Window im_window = find_hildon_im_window();
+       Window app_window = find_app_window();
+       if (!app_window || !im_window) return;
+
+       XEvent e = { 0 };
+       e.xclient.type = ClientMessage;
+       e.xclient.window = im_window;
+       e.xclient.message_type = ATOM(HILDON_IM_SURROUNDING);
+       e.xclient.format = HILDON_IM_SURROUNDING_FORMAT;
+
+       HildonIMSurroundingMessage *msg = (HildonIMSurroundingMessage*)(&e.xclient.data);
+
+       msg->commit_mode = commit_mode;
+       msg->cursor_offset = cursor_offset;
+
+       XSendEvent(display, im_window, False, 0, &e);
+       XFlush(display);
+}
+
+static void send_selection_reply(Bool has_selection)
+{
+       Window im_window = find_hildon_im_window();
+       if (!im_window) return;
+
+       XEvent e = { 0 };
+       e.xclient.type = ClientMessage;
+       e.xclient.window = im_window;
+       e.xclient.message_type = ATOM(HILDON_IM_CLIPBOARD_SELECTION_REPLY);
+       e.xclient.format = HILDON_IM_CLIPBOARD_SELECTION_REPLY_FORMAT;
+       e.xclient.data.l[0] = has_selection;
+
+       XSendEvent(display, im_window, False, 0, &e);
+       XFlush(display);
+}
+
+static void send_sdl_text_event(Uint8 code, const char * text)
+{
+       HIM_TextInputEvent event;
+       event.type = event_base;
+       event.code = code;
+
+       /* TODO: The SDL event size is limited. Send more than one. */
+       strncpy(event.text, text, HIM_TEXTINPUTEVENT_TEXT_SIZE);
+       event.text[HIM_TEXTINPUTEVENT_TEXT_SIZE] = '\0';
+
+       SDL_PushEvent((SDL_Event*)&event);
+}
+
+static void send_sdl_request_surrounding_event(Bool full)
+{
+       HIM_RequestSurroundingEvent event;
+       event.type = event_base;
+       event.code = HIM_REQUESTSURROUNDINGEVENT;
+       event.full = full ? 1 : 0;
+
+       SDL_PushEvent((SDL_Event*)&event);
+}
+
+static void send_sdl_clipboard_event(HIM_ClipboardAction action)
+{
+       HIM_ClipboardEvent event;
+       event.type = event_base;
+       event.code = HIM_CLIPBOARDEVENT;
+       event.action = action;
+
+       SDL_PushEvent((SDL_Event*)&event);
+}
+
+static void set_sdl_cursor_location(Bool is_relative, int offset)
+{
+       HIM_CursorMoveEvent event;
+       event.type = event_base;
+       event.code = HIM_CURSORMOVEEVENT;
+       event.relative = is_relative ? 1 : 0;
+       event.offset = offset;
+
+       SDL_PushEvent((SDL_Event*)&event);
+}
+
+static void commit_preedit_buffer()
+{
+       if (space_after_commit) {
+               ds_append(&preedit_buffer, " ");
+       }
+       if (preedit_buffer.len > 0) {
+               send_sdl_text_event(HIM_TEXTINPUTEVENT, preedit_buffer.str);
+               ds_clear(&preedit_buffer);
+       }
+}
+
+static void cancel_preedit()
+{
+       ds_clear(&preedit_buffer);
+}
+
+static void check_sentence_start()
+{
+       if (!enabled) return;
+
+       if ((input_mode & (HILDON_GTK_INPUT_MODE_ALPHA |
+               HILDON_GTK_INPUT_MODE_AUTOCAP)) != 
+               (HILDON_GTK_INPUT_MODE_ALPHA |
+               HILDON_GTK_INPUT_MODE_AUTOCAP)) {
+               /* If autocap is off, but the mode contains alpha, send autocap message.
+               The important part is that when entering a numerical entry the autocap
+               is not defined, and the plugin sets the mode appropriate for the language */
+               if (input_mode & HILDON_GTK_INPUT_MODE_ALPHA) {
+                       auto_upper = False;
+                       send_hildon_command(HILDON_IM_LOW);
+               }
+
+               return;
+       } else if (input_mode & HILDON_GTK_INPUT_MODE_INVISIBLE) {
+               /* No autocap for passwords */
+               auto_upper = False;
+               send_hildon_command(HILDON_IM_LOW);
+       }
+
+       // TODO Analyze surrounding for autocapitalization
+}
+
+/**
+       @param flag HILDON_IM_MSG_START, HILDON_IM_MSG_CONTINUE, HILDON_IM_MSG_END
+       @param str the Utf8 string
+ */
+static void insert_utf8(int flag, const char * str)
+{
+       if (commit_mode == HILDON_IM_COMMIT_BUFFERED) {
+               if (flag == HILDON_IM_MSG_START) {
+                       ds_set(&preedit_buffer, str);
+               } else {
+                       ds_append(&preedit_buffer, str);
+               }
+               send_sdl_text_event(HIM_TEXTEDITINGEVENT, preedit_buffer.str);
+       } else { /* Direct, Redirect, and Proxy modes. */
+               send_sdl_text_event(HIM_TEXTINPUTEVENT, str);
+       }
+}
+
+static void insert_special_key(SDLKey key)
+{
+       HIM_SpecialKeyEvent event = { event_base, HIM_SPECIALKEYEVENT, key };
+       SDL_PushEvent((SDL_Event*)&event);
+}
+
+static void set_commit_mode(HildonIMCommitMode new_mode)
+{
+       ds_clear(&preedit_buffer);
+       commit_mode = new_mode;
+}
+
+static void set_mask_state(HildonIMInternalModifierMask *mask,
+       HildonIMInternalModifierMask lock_mask,
+       HildonIMInternalModifierMask sticky_mask,
+       Bool was_press_and_release)
+{
+       /* Locking Fn is disabled in TELE and NUMERIC */
+       if (!(input_mode & HILDON_GTK_INPUT_MODE_ALPHA) &&
+               !(input_mode & HILDON_GTK_INPUT_MODE_HEXA)  &&
+               ((input_mode & HILDON_GTK_INPUT_MODE_TELE) || 
+                (input_mode & HILDON_GTK_INPUT_MODE_NUMERIC))) {
+               if (*mask & lock_mask) {
+                       /* already locked, remove lock and set it to sticky */
+                       *mask &= ~(lock_mask | sticky_mask);
+                       *mask |= sticky_mask;
+               } else if (*mask & sticky_mask) {
+                       /* the key is already sticky, it's fine */
+               } else if (was_press_and_release) {
+                       /* Pressing the key for the first time stickies the key for one character,
+                        * but only if no characters were entered while holding the key down */
+                       *mask |= sticky_mask;
+               }
+
+               return;
+       }
+
+       if (*mask & lock_mask) {
+               /* Pressing the key while already locked clears the state */
+               *mask &= ~(lock_mask | sticky_mask);
+
+#if MAEMO_VERSION == 5
+               if (lock_mask & HILDON_IM_SHIFT_LOCK_MASK) {
+                       send_hildon_command(HILDON_IM_SHIFT_UNLOCKED);
+               } else if (lock_mask & HILDON_IM_LEVEL_LOCK_MASK) {
+                       send_hildon_command(HILDON_IM_MOD_UNLOCKED);
+               }
+#endif
+
+       } else if (*mask & sticky_mask) {
+               /* When the key is already sticky, a second press locks the key */
+               *mask |= lock_mask;
+
+#if MAEMO_VERSION == 5
+               if (lock_mask & HILDON_IM_SHIFT_LOCK_MASK) {
+                       send_hildon_command(HILDON_IM_SHIFT_LOCKED);
+               } else if (lock_mask & HILDON_IM_LEVEL_LOCK_MASK) {
+                       send_hildon_command(HILDON_IM_MOD_LOCKED);
+               }
+#endif
+
+       } else if (was_press_and_release) {
+               /* Pressing the key for the first time stickies the key for one character,
+                * but only if no characters were entered while holding the key down */
+               *mask |= sticky_mask;
+       }
+}
+
+static int handle_x_event(const XEvent *e)
+{
+       if (e->type == ClientMessage) {
+               /* Receive messages from HIM */
+               if (e->xclient.message_type == ATOM(HILDON_IM_INSERT_UTF8)) {
+                       /* Text insertion message */
+                       HildonIMInsertUtf8Message *msg =
+                               (HildonIMInsertUtf8Message*)&e->xclient.data;
+                       assert(e->xclient.format == HILDON_IM_INSERT_UTF8_FORMAT);
+
+                       insert_utf8(msg->msg_flag, msg->utf8_str);
+
+                       return 1;
+               } else if (e->xclient.message_type == ATOM(HILDON_IM_COM)) {
+                       HildonIMComMessage *msg = (HildonIMComMessage*)&e->xclient.data;
+
+                       options = msg->options;
+
+                       switch (msg->type) {
+                       case HILDON_IM_CONTEXT_HANDLE_ENTER:
+                               insert_special_key(SDLK_RETURN);
+                               break;
+                       case HILDON_IM_CONTEXT_HANDLE_TAB:
+                               insert_special_key(SDLK_TAB);
+                               break;
+                       case HILDON_IM_CONTEXT_HANDLE_BACKSPACE:
+                               insert_special_key(SDLK_BACKSPACE);
+                               break;
+                       case HILDON_IM_CONTEXT_HANDLE_SPACE:
+                               insert_special_key(SDLK_SPACE);
+                               break;
+
+                       case HILDON_IM_CONTEXT_CLIPBOARD_CUT:
+                               send_sdl_clipboard_event(HIM_CLIPBOARD_CUT);
+                               break;
+                       case HILDON_IM_CONTEXT_CLIPBOARD_COPY:
+                               send_sdl_clipboard_event(HIM_CLIPBOARD_COPY);
+                               break;
+                       case HILDON_IM_CONTEXT_CLIPBOARD_PASTE:
+                               send_sdl_clipboard_event(HIM_CLIPBOARD_PASTE);
+                               break;
+                       case HILDON_IM_CONTEXT_CLIPBOARD_SELECTION_QUERY:
+                               send_sdl_clipboard_event(HIM_CLIPBOARD_REQUEST_SELECTION);
+                               break;
+
+                       case HILDON_IM_CONTEXT_DIRECT_MODE:
+                               set_commit_mode(HILDON_IM_COMMIT_DIRECT);
+                               break;
+                       case HILDON_IM_CONTEXT_BUFFERED_MODE:
+                               set_commit_mode(HILDON_IM_COMMIT_BUFFERED);
+                               break;
+                       case HILDON_IM_CONTEXT_REDIRECT_MODE:
+                               set_commit_mode(HILDON_IM_COMMIT_REDIRECT);
+                               break;
+                       case HILDON_IM_CONTEXT_SURROUNDING_MODE:
+                               set_commit_mode(HILDON_IM_COMMIT_SURROUNDING);
+                               break;
+
+                       case HILDON_IM_CONTEXT_CONFIRM_SENTENCE_START:
+                               check_sentence_start();
+                               break;
+                       case HILDON_IM_CONTEXT_FLUSH_PREEDIT:
+                               commit_preedit_buffer();
+                               break;
+
+                       case HILDON_IM_CONTEXT_REQUEST_SURROUNDING:
+                               send_sdl_request_surrounding_event(False);
+                               break;
+                       case HILDON_IM_CONTEXT_CLEAR_STICKY:
+                               mod_mask &= ~(HILDON_IM_SHIFT_STICKY_MASK |
+                                       HILDON_IM_SHIFT_LOCK_MASK |
+                                       HILDON_IM_LEVEL_STICKY_MASK |
+                                       HILDON_IM_LEVEL_LOCK_MASK);
+                               break;
+
+                       case HILDON_IM_CONTEXT_WIDGET_CHANGED:
+                               mod_mask = 0; /* Clear modifier status */
+                               break;
+                       case HILDON_IM_CONTEXT_ENTER_ON_FOCUS:
+                               /* We don't have the concept of focused widget. */
+                               insert_special_key(SDLK_RETURN);
+                               break;
+
+#if MAEMO_VERSION == 5
+                       case HILDON_IM_CONTEXT_PREEDIT_MODE:
+                               set_commit_mode(HILDON_IM_COMMIT_PREEDIT);
+                               break;
+
+                       case HILDON_IM_CONTEXT_REQUEST_SURROUNDING_FULL:
+                               send_sdl_request_surrounding_event(True);
+                               break;
+                       case HILDON_IM_CONTEXT_CANCEL_PREEDIT:
+                               cancel_preedit();
+                               break;
+
+                       case HILDON_IM_CONTEXT_SPACE_AFTER_COMMIT:
+                               space_after_commit = True;
+                               break;
+                       case HILDON_IM_CONTEXT_NO_SPACE_AFTER_COMMIT:
+                               space_after_commit = False;
+                               break;
+#endif
+                               
+                       default:
+                               fprintf(stderr, "Unknown Hildon IM COM message %d\n",
+                                       msg->type);
+                               break;
+                       }
+
+                       return 1;
+               } else if (e->xclient.message_type == ATOM(HILDON_IM_SURROUNDING_CONTENT)) {
+                       assert(e->xclient.format == HILDON_IM_SURROUNDING_CONTENT_FORMAT);
+                       // TODO Surrounding content
+                       fprintf(stderr, "Surrounding content is not implemented\n");
+               } else if (e->xclient.message_type == ATOM(HILDON_IM_SURROUNDING)) {
+                       assert(e->xclient.format == HILDON_IM_SURROUNDING_FORMAT);
+                       HildonIMSurroundingMessage *msg = (HildonIMSurroundingMessage*)(&e->xclient.data);
+                       set_sdl_cursor_location(msg->offset_is_relative, msg->cursor_offset);
+               }
+       }
+
+       return 0;
+}
+
+static unsigned sdl_mod_to_x_state(const SDLMod mod)
+{
+       unsigned state = 0;
+       if (mod & KMOD_SHIFT) state |= ShiftMask;
+       if (mod & KMOD_CAPS) state |= LockMask;
+       if (mod & KMOD_CTRL) state |= ControlMask;
+       if (mod & KMOD_MODE) state |= LevelMask;
+       return state;
+}
+
+/** @return 1 hides the event from the SDL queue */
+static int filter_keypress(const SDL_KeyboardEvent *event)
+{
+       /* Basically, convert a SDL KeyboardEvent back into a XKeyEvent. */
+       const Bool pressed = event->state == SDL_PRESSED;
+       KeyCode keycode = event->keysym.scancode; // As in, Xlib KeyCode.
+       unsigned int mods_rtrn, state = sdl_mod_to_x_state(event->keysym.mod);
+       KeySym keysym;
+       XkbLookupKeySym(display, keycode, state, &mods_rtrn, &keysym);
+
+       Uint32 c = 0; /* To be replaced by the commited unicode character. */
+
+       /** A special setting that causes "normal" keys not to be filtered,
+               even if HIM will latter send a commit event. Great for games.
+        */
+       const int normal_key_return = input_mode & HIM_MODE_NO_FILTER ? 0 : 1;
+
+       /* A dead key will not be immediately commited, but combined with the next key */
+       if (keysym >= XK_dead_grave && keysym <= XK_dead_horn) {
+               mod_mask |= HILDON_IM_DEAD_KEY_MASK;
+       } else {
+               mod_mask &= ~HILDON_IM_DEAD_KEY_MASK;
+       }
+
+       /* A normal dead key first press */
+       if ((mod_mask & HILDON_IM_DEAD_KEY_MASK) && combining_char == 0) {
+               combining_char = dead_key_to_unicode_combining_character(keysym);
+               return normal_key_return; /* Treat dead keys as "normal keys" */
+       }
+
+       /* Pressing any key while the compose key is pressed will keep that
+               character from being directly submitted to the application. This
+               allows the IM process to override the interpretation of the key */
+       if (keysym == COMPOSE_KEY) {
+               if (pressed) {
+                       mod_mask |= HILDON_IM_COMPOSE_MASK;
+               } else {
+                       mod_mask &= ~HILDON_IM_COMPOSE_MASK;
+               }
+       }
+
+       /* Sticky and locking keys initialization */
+       if (!pressed) {
+               /* Set the appropiate mask when Shift or Fn is released. */
+               if (keysym == XK_Shift_L || keysym == XK_Shift_R) {
+                       set_mask_state(&mod_mask,
+                               HILDON_IM_SHIFT_LOCK_MASK, HILDON_IM_SHIFT_STICKY_MASK,
+                               last_keysym == XK_Shift_L || last_keysym == XK_Shift_R);
+               } else if (keysym == LEVEL_KEY) {
+                       set_mask_state(&mod_mask,
+                               HILDON_IM_LEVEL_LOCK_MASK, HILDON_IM_LEVEL_STICKY_MASK,
+                               last_keysym == LEVEL_KEY);
+               }
+       }
+
+       /* Update last_keysym now; we do not need it later. */
+       last_keysym = keysym;
+
+       /* We let the input method know of things like Return, Tab, etc. */
+       if (keysym == XK_Return || keysym == XK_KP_Enter
+        || keysym == XK_ISO_Enter || keysym == XK_Tab) {
+               send_hildon_key_event(pressed, state, keysym, keycode);
+               return 0;
+       }
+
+       /* When the level key is in sticky or locked state, translate the
+          keyboard state as if that level key was being held down. */
+       if ((mod_mask & (HILDON_IM_LEVEL_STICKY_MASK | HILDON_IM_LEVEL_LOCK_MASK)) ||
+                       state == LevelMask) {
+               /* Use Xkb to query the keymap and get the key we would get if we
+                       were to be holding Fn */
+               unsigned int mods_rtrn;
+               KeySym new_keysym;
+               if (XkbLookupKeySym(display, keycode, LevelMask, &mods_rtrn, &new_keysym)) {
+                       keysym = new_keysym;
+               }
+       } else if (options & HILDON_IM_AUTOLEVEL_NUMERIC && 
+               (input_mode & HILDON_GTK_INPUT_MODE_FULL) == HILDON_GTK_INPUT_MODE_NUMERIC) {
+               keysym = get_keysym_for_level(keycode, NUMERIC_LEVEL);
+       } else if (options & HILDON_IM_LOCK_LEVEL) {
+               keysym = get_keysym_for_level(keycode, LOCKABLE_LEVEL);
+       }
+
+
+       /* Hardware keyboard autocapitalization */
+       if (auto_upper && (input_mode & HILDON_GTK_INPUT_MODE_AUTOCAP)) {
+               KeySym lower, upper;
+               XConvertCase(keysym, &lower, &upper);
+
+               if (state & ShiftMask) {
+                       keysym = lower; // Since we're latter going to shift it :)
+               } else {
+                       keysym = upper;
+               }
+       }
+
+       /* Shift lock or holding the shift key down forces uppercase */
+       if (mod_mask & HILDON_IM_SHIFT_LOCK_MASK || state & ShiftMask) {
+               KeySym lower, upper;
+               XConvertCase(keysym, &lower, &upper);
+
+               keysym = upper;
+       } else if (mod_mask & HILDON_IM_SHIFT_STICKY_MASK) {
+               KeySym lower, upper;
+               XConvertCase(keysym, &lower, &upper);
+
+               if (lower == upper) {
+                       /* Non printable character: check the keyboard map */
+                       unsigned int mods_rtrn;
+                       KeySym new_keysym;
+                       if (XkbLookupKeySym(display, keycode, ShiftMask, &mods_rtrn, &new_keysym)) {
+                               keysym = new_keysym;
+                       }
+               } else if (keysym == upper) {
+                       /* Previously upper case (means caps lock is on) ==> lower case. */
+                       keysym = lower;
+               } else {
+                       keysym = upper;
+               }
+       }
+
+       /* Sticky and lock state reset */
+       if (!pressed) {
+               if (keysym != XK_Shift_L && keysym != XK_Shift_R) {
+                       /* Pressing anything other than shift, if not locked,
+                          resets shift state (this is the other important part of
+                          keys. */
+                       if (!(mod_mask & HILDON_IM_SHIFT_LOCK_MASK)) {
+                               mod_mask &= ~HILDON_IM_SHIFT_STICKY_MASK;
+                       }
+               }
+               if (keysym != LEVEL_KEY) {
+                       /* Same for Fn/Level key */
+                       if (!(mod_mask & HILDON_IM_LEVEL_LOCK_MASK)) {
+                               mod_mask &= ~HILDON_IM_LEVEL_STICKY_MASK;
+                       }
+               }
+       }
+
+       /* From now on, just ignore release key events. */
+       /* Save for Ctrl+Anykey. Forward those to HIM. CtrlC CtrlV support? */
+       if (!pressed || state & ControlMask) {
+               send_hildon_key_event(pressed, state, keysym, keycode);
+               return 0;
+       }
+
+       /* Pressing a dead key twice, or if followed by a space, inputs 
+               the dead key's character representation. */
+       if ((mod_mask & HILDON_IM_DEAD_KEY_MASK || keysym == XK_space) &&
+               combining_char) {
+               Uint32 last = dead_key_to_unicode_combining_character(keysym);
+               if (last == combining_char || keysym == XK_space) {
+                       /* Pressed twice or followed by space => print dead key. */
+                       c = combining_character_to_unicode(combining_char);
+               } else {
+                       /* Some other thing. Consider as regular keypress. */
+                       c = keysym_to_unicode(keysym);
+               }
+               combining_char = 0;
+       } else {
+               /* A regular keypress. */
+               if (mod_mask & HILDON_IM_COMPOSE_MASK) {
+                       /* Chr key pressed. HIM handles all of this. */
+                       send_hildon_key_event(pressed, state, keysym, keycode);
+                       return 1;
+               } else {
+                       /* A completely regular keypress. */
+                       c = keysym_to_unicode(keysym);
+               }
+       }
+
+       if (c) {
+               /* Prepare a buffer for text input event */
+               HIM_TextInputEvent event;
+               event.type = event_base;
+               event.code = HIM_TEXTINPUTEVENT;
+
+               /* Check if we have pending diactrics... */
+               if (combining_char) {
+                       /* This is not a full unicode normalizer, but works. */
+                       int new_c = compose_unicode_characters(c, combining_char);
+
+                       if (new_c != NOT_COMPOSITE) {
+                               c = new_c;
+                       }
+
+                       combining_char = 0;
+               }
+
+               unicode_to_utf8(c, event.text);
+               send_hildon_key_event(pressed, state, unicode_to_keysym(c), keycode);
+
+               SDL_PushEvent((SDL_Event*)&event);
+
+               return normal_key_return;
+       } else {
+               /* We didn't output any string. */
+               send_hildon_key_event(pressed, state, keysym, keycode);
+
+               /* Non-printable characters invalidate any previous dead keys */
+               /* Save for shift, which you might need. */
+               if (keysym != XK_Shift_L && keysym != XK_Shift_R) {
+                       combining_char = 0;
+               }
+
+               return 0;
+       }
+
+       return 0;
+}
+
+int HIM_Init(Uint32 flags, Uint8 event)
+{
+       SDL_SysWMinfo info;
+
+       SDL_VERSION(&info.version);
+       if (SDL_GetWMInfo(&info) != 1) {
+               SDL_SetError("SDL_him is incompatible with this SDL version");
+               return -1;
+       }
+
+       if (event != 0 && (event < SDL_USEREVENT || event >= SDL_NUMEVENTS)) {
+               SDL_SetError("Invalid event number");
+               return -1;
+       }
+
+       display = info.info.x11.display;
+       event_base = event;
+
+       if (!initialized) {
+               XInternAtoms(display, (char**)atom_names,
+                                       HILDON_IM_NUM_ATOMS, True, atom_values);
+               if (ATOM(HILDON_IM_WINDOW) == None) {
+                       /* a required atom was not found */
+                       SDL_SetError("Hildon Input Method is not loaded");
+                       return -1;
+               }
+               initialized = True;
+
+               /* Enable some SDL features we need */
+               SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
+       }
+
+       enabled = False;
+
+       return 0;
+}
+
+void HIM_Quit()
+{
+       if (enabled) {
+               HIM_Disable();
+       }
+}
+
+void HIM_Enable(HIM_Mode mode)
+{
+       input_mode = mode;
+
+       /* Send the input mode before the command */
+       send_input_mode();
+
+       /* Default commit mode required by Fremantle */
+       commit_mode = HILDON_IM_COMMIT_REDIRECT;
+
+       /* Reset some stuff, like if we had changed focused widget. */
+       mod_mask = 0;
+       last_keysym = XK_VoidSymbol;
+       combining_char = 0;
+
+       send_hildon_command(HILDON_IM_SETCLIENT);
+
+       enabled = True;
+}
+
+void HIM_Disable()
+{
+       enabled = False;
+       cancel_preedit();
+       send_hildon_command(HILDON_IM_CLEAR);
+}
+
+void HIM_ShowKeyboard(HIM_Trigger reason)
+{
+       trigger_mode = reason;
+       send_hildon_command(HILDON_IM_SETNSHOW);
+}
+
+void HIM_HideKeyboard()
+{
+       send_hildon_command(HILDON_IM_HIDE);
+}
+
+int HIM_FilterEvent(const SDL_Event *event)
+{
+       switch (event->type) {
+               case SDL_SYSWMEVENT:
+                       return handle_x_event(&event->syswm.msg->event.xevent);
+                       break;
+               case SDL_KEYDOWN:
+               case SDL_KEYUP:
+                       if (enabled) {
+                               /* Only process keypresses at all if enabled */
+                               return filter_keypress(&event->key);
+                       }
+                       break;
+       }
+
+       return 0;
+}
+
+void HIM_SendSurrounding(const char * text, unsigned long cursor)
+{
+       send_surrounding_content(text);
+       send_surrounding_header(cursor);
+}
+
+void HIM_SendSelection(const char * text)
+{
+       Bool has_selection = text && text[0] != '\0';
+       send_selection_reply(has_selection);
+}
+