--- /dev/null
+/* 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);
+}
+