initial import for sdlhaa & sdlhim
[sdlhildon] / sdlhim / src / SDL_him.c
1 /* This file is part of SDL_him - SDL Hildon Input Method addon
2  * Copyright (C) 2010 Javier S. Pedro
3  * Copyright (C) 2005-2010 Nokia Corporation and/or its subsidiary(-ies).
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 3 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA or see <http://www.gnu.org/licenses/>.
19  */
20
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <assert.h>
25
26 #include <X11/Xlib.h>
27 #include <X11/Xutil.h>
28 #include <X11/Xatom.h>
29 #include <X11/XKBlib.h>
30 #include <X11/extensions/XKBstr.h>
31 #include <SDL.h>
32 #include <SDL_syswm.h>
33
34 #include "SDL_him.h"
35
36 /* This pulls Gtk+ headers, but we do not link with Gtk+, Gdk or GLib */
37 #include <gdk/gdk.h>
38 #include <hildon-im-protocol.h>
39
40 /* Specific Maemo configuration */
41 #define COMPOSE_KEY XK_Multi_key
42 #define LEVEL_KEY XK_ISO_Level3_Shift
43 #define LevelMask Mod5Mask
44
45 #define NUMERIC_LEVEL 2
46 #define LOCKABLE_LEVEL 4
47
48 #include "modifier.h"
49 #include "unicode.h"
50 #include "ds.h"
51 #include "atoms.h"
52
53 static Display *display;
54
55 static Uint8 event_base;
56
57 static Bool initialized = False;
58 static Bool enabled = False;
59
60 static HildonIMOptionMask options = 0;
61 static HildonIMTrigger trigger_mode = HILDON_IM_TRIGGER_NONE;
62 static HildonGtkInputMode input_mode = HILDON_GTK_INPUT_MODE_FULL;
63 static HildonIMCommitMode commit_mode = HILDON_IM_COMMIT_DIRECT;
64 static HildonIMInternalModifierMask mod_mask = 0;
65
66 static Uint32 combining_char = 0;
67 static KeySym last_keysym = XK_VoidSymbol;
68 static Bool auto_upper = False;
69 static Bool space_after_commit = False;
70
71 static dstring_t preedit_buffer = { 0, 0, 0 };
72
73 /** Gets the window id of the Hildon IM GUI.
74         We'll use it as target for our messages. 
75  */
76 static Window find_hildon_im_window()
77 {
78         const Window rootWindow = RootWindow(display, DefaultScreen(display));
79         Window window;
80         int status;
81         Atom actual_type;
82         int actual_format;
83         unsigned long nitems, bytes_after;
84         unsigned char *prop = NULL;
85
86         /* Use RootWindow's HILDON_IM_WINDOW property to find HIM's window */
87         status = XGetWindowProperty(display, rootWindow,
88                 ATOM(HILDON_IM_WINDOW), 0, 1, False, XA_WINDOW,
89                 &actual_type, &actual_format,
90                 &nitems, &bytes_after, &prop);
91
92         if (status != Success || actual_type != XA_WINDOW ||
93                         actual_format != HILDON_IM_WINDOW_ID_FORMAT || nitems != 1)  {
94                 SDL_SetError("Failed to get Hildon IM Window id");
95                 return -1;
96         }
97
98         window = *(Window*)prop;
99         XFree(prop);
100
101         return window;
102 }
103
104 /** Gets the window id for the current shown SDL window. */
105 static Window find_app_window()
106 {
107         SDL_SysWMinfo info;
108         SDL_Surface *screen = SDL_GetVideoSurface();
109
110         SDL_VERSION(&info.version);
111         if (SDL_GetWMInfo(&info) != 1) {
112                 // Shouldn't happen
113                 assert(0);
114                 return 0;
115         }
116
117         if (screen->flags & SDL_FULLSCREEN)
118                 return info.info.x11.fswindow;
119         else
120                 return info.info.x11.wmwindow;
121 }
122
123 /* This comes mostly straight from Qt Maemo.
124  Not sure what's exactly doing, or wheter I can replace it with XLookupKeysym */
125 static KeySym get_keysym_for_level(KeyCode keycode, int level)
126 {
127         XkbDescPtr xkb = XkbGetMap(display, XkbAllClientInfoMask, XkbUseCoreKbd);
128         if (!xkb)
129                 return NoSymbol;
130
131         KeySym keysym = XkbKeySymEntry(xkb, keycode, level, 0);
132
133         /* check that the keysym is not repeated in levels 0 and requested */
134         KeySym keysym_test = XkbKeySymEntry(xkb, keycode, 0, 1);
135         if (keysym == keysym_test) {
136                 return NoSymbol;
137         }
138
139         return keysym;
140 }
141
142 static void send_input_mode()
143 {
144 #if MAEMO_VERSION == 5
145         Window im_window = find_hildon_im_window();
146         Window app_window = find_app_window();
147         if (!app_window || !im_window) return;
148
149         XEvent e = { 0 };
150         e.xclient.type = ClientMessage;
151         e.xclient.window = im_window;
152         e.xclient.message_type = ATOM(HILDON_IM_INPUT_MODE);
153         e.xclient.format = HILDON_IM_INPUT_MODE_FORMAT;
154
155         HildonIMInputModeMessage *msg = (HildonIMInputModeMessage*)(&e.xclient.data);
156
157         msg->input_mode = input_mode;
158         msg->default_input_mode = HILDON_GTK_INPUT_MODE_FULL;
159
160         XSendEvent(display, im_window, False, 0, &e);
161         XFlush(display);
162 #elif MAEMO_VERSION == 4
163 /* On Diablo, input_mode is sent with the activation message. */
164 /* See send_hildon_command() */
165 #else
166 #error Missing send_input_mode implementation
167 #endif
168 }
169
170 /** Sends a client message with the specified command to the IM window */
171 static void send_hildon_command(HildonIMCommand cmd)
172 {
173         Window im_window = find_hildon_im_window();
174         Window app_window = find_app_window();
175         if (!app_window || !im_window) return;
176
177         XEvent e = { 0 };
178         e.xclient.type = ClientMessage;
179         e.xclient.window = im_window;
180         e.xclient.message_type = ATOM(HILDON_IM_ACTIVATE);
181         e.xclient.format = HILDON_IM_ACTIVATE_FORMAT;
182
183         HildonIMActivateMessage *msg = (HildonIMActivateMessage*)(&e.xclient.data);
184
185         /* input_window is the window of the focused widget (text box, etc.) */
186         /* SDL doesn't do widgets, so we send the toplevel window */
187         msg->input_window = app_window;
188         /* app_window is the toplevel window containing input_window */
189         msg->app_window = app_window;
190 #if MAEMO_VERSION == 4
191         msg->input_mode = input_mode;
192 #endif
193         msg->cmd = cmd;
194         msg->trigger = trigger_mode;
195
196         XSendEvent(display, im_window, False, 0, &e);
197         XFlush(display);
198 }
199
200 /** Forward a key event to the hildon input method window.
201         Why'd we need such a thing? Well, to get the input method GUI to be
202         up to date, seems.
203  */
204 static void send_hildon_key_event
205 (Bool pressed, unsigned state, KeySym keysym, KeyCode keycode)
206 {
207         Window im_window = find_hildon_im_window();
208         Window app_window = find_app_window();
209         if (!app_window || !im_window) return;
210
211         XEvent e = { 0 };
212         e.xclient.type = ClientMessage;
213         e.xclient.window = im_window;
214         e.xclient.message_type = ATOM(HILDON_IM_KEY_EVENT);
215         e.xclient.format = HILDON_IM_KEY_EVENT_FORMAT;
216
217         HildonIMKeyEventMessage *msg = (HildonIMKeyEventMessage*)(&e.xclient.data);
218
219         msg->input_window = app_window;
220
221         /* This protocol is way too gnomish. */
222         if (pressed) {
223                 msg->type = GDK_KEY_PRESS;
224         } else {
225                 msg->type = GDK_KEY_RELEASE;
226         }
227         msg->state = state;
228         msg->keyval = keysym;
229         msg->hardware_keycode = keycode;
230
231         XSendEvent(display, im_window, False, 0, &e);
232         XFlush(display);
233 }
234
235 static void send_surrounding_content(const char * text)
236 {
237         if (!text) return; /* no need to send this if text is empty */
238
239         Window im_window = find_hildon_im_window();
240         Window app_window = find_app_window();
241         if (!app_window || !im_window) return;
242
243         const unsigned long total_len = strlen(text);
244         Bool first_part = True;
245         unsigned long offset = 0;
246
247         while (first_part || (offset < total_len)) {
248                 XEvent e = { 0 };
249                 e.xclient.type = ClientMessage;
250                 e.xclient.window = im_window;
251                 e.xclient.message_type = ATOM(HILDON_IM_SURROUNDING_CONTENT);
252                 e.xclient.format = HILDON_IM_SURROUNDING_CONTENT_FORMAT;
253
254                 unsigned long len = total_len - offset;
255                 if (len > HILDON_IM_CLIENT_MESSAGE_BUFFER_SIZE - 1) {
256                         /* the X11 event is limited in size. */
257                         len = HILDON_IM_CLIENT_MESSAGE_BUFFER_SIZE - 1;
258                 }
259
260                 HildonIMSurroundingContentMessage *msg = (HildonIMSurroundingContentMessage*)(&e.xclient.data);
261                 memcpy(msg->surrounding, text + offset, len);
262
263                 if (first_part) {
264                         msg->msg_flag = HILDON_IM_MSG_START;
265                 } else if (offset == total_len) {
266                         msg->msg_flag = HILDON_IM_MSG_END;
267                 } else {
268                         msg->msg_flag = HILDON_IM_MSG_CONTINUE;
269                 }
270
271                 XSendEvent(display, im_window, False, 0, &e);
272
273                 offset += len;
274                 first_part = False;
275         }
276
277         XFlush(display);
278 }
279
280 static void send_surrounding_header(unsigned int cursor_offset)
281 {
282         Window im_window = find_hildon_im_window();
283         Window app_window = find_app_window();
284         if (!app_window || !im_window) return;
285
286         XEvent e = { 0 };
287         e.xclient.type = ClientMessage;
288         e.xclient.window = im_window;
289         e.xclient.message_type = ATOM(HILDON_IM_SURROUNDING);
290         e.xclient.format = HILDON_IM_SURROUNDING_FORMAT;
291
292         HildonIMSurroundingMessage *msg = (HildonIMSurroundingMessage*)(&e.xclient.data);
293
294         msg->commit_mode = commit_mode;
295         msg->cursor_offset = cursor_offset;
296
297         XSendEvent(display, im_window, False, 0, &e);
298         XFlush(display);
299 }
300
301 static void send_selection_reply(Bool has_selection)
302 {
303         Window im_window = find_hildon_im_window();
304         if (!im_window) return;
305
306         XEvent e = { 0 };
307         e.xclient.type = ClientMessage;
308         e.xclient.window = im_window;
309         e.xclient.message_type = ATOM(HILDON_IM_CLIPBOARD_SELECTION_REPLY);
310         e.xclient.format = HILDON_IM_CLIPBOARD_SELECTION_REPLY_FORMAT;
311         e.xclient.data.l[0] = has_selection;
312
313         XSendEvent(display, im_window, False, 0, &e);
314         XFlush(display);
315 }
316
317 static void send_sdl_text_event(Uint8 code, const char * text)
318 {
319         HIM_TextInputEvent event;
320         event.type = event_base;
321         event.code = code;
322
323         /* TODO: The SDL event size is limited. Send more than one. */
324         strncpy(event.text, text, HIM_TEXTINPUTEVENT_TEXT_SIZE);
325         event.text[HIM_TEXTINPUTEVENT_TEXT_SIZE] = '\0';
326
327         SDL_PushEvent((SDL_Event*)&event);
328 }
329
330 static void send_sdl_request_surrounding_event(Bool full)
331 {
332         HIM_RequestSurroundingEvent event;
333         event.type = event_base;
334         event.code = HIM_REQUESTSURROUNDINGEVENT;
335         event.full = full ? 1 : 0;
336
337         SDL_PushEvent((SDL_Event*)&event);
338 }
339
340 static void send_sdl_clipboard_event(HIM_ClipboardAction action)
341 {
342         HIM_ClipboardEvent event;
343         event.type = event_base;
344         event.code = HIM_CLIPBOARDEVENT;
345         event.action = action;
346
347         SDL_PushEvent((SDL_Event*)&event);
348 }
349
350 static void set_sdl_cursor_location(Bool is_relative, int offset)
351 {
352         HIM_CursorMoveEvent event;
353         event.type = event_base;
354         event.code = HIM_CURSORMOVEEVENT;
355         event.relative = is_relative ? 1 : 0;
356         event.offset = offset;
357
358         SDL_PushEvent((SDL_Event*)&event);
359 }
360
361 static void commit_preedit_buffer()
362 {
363         if (space_after_commit) {
364                 ds_append(&preedit_buffer, " ");
365         }
366         if (preedit_buffer.len > 0) {
367                 send_sdl_text_event(HIM_TEXTINPUTEVENT, preedit_buffer.str);
368                 ds_clear(&preedit_buffer);
369         }
370 }
371
372 static void cancel_preedit()
373 {
374         ds_clear(&preedit_buffer);
375 }
376
377 static void check_sentence_start()
378 {
379         if (!enabled) return;
380
381         if ((input_mode & (HILDON_GTK_INPUT_MODE_ALPHA |
382                 HILDON_GTK_INPUT_MODE_AUTOCAP)) != 
383                 (HILDON_GTK_INPUT_MODE_ALPHA |
384                 HILDON_GTK_INPUT_MODE_AUTOCAP)) {
385                 /* If autocap is off, but the mode contains alpha, send autocap message.
386                 The important part is that when entering a numerical entry the autocap
387                 is not defined, and the plugin sets the mode appropriate for the language */
388                 if (input_mode & HILDON_GTK_INPUT_MODE_ALPHA) {
389                         auto_upper = False;
390                         send_hildon_command(HILDON_IM_LOW);
391                 }
392
393                 return;
394         } else if (input_mode & HILDON_GTK_INPUT_MODE_INVISIBLE) {
395                 /* No autocap for passwords */
396                 auto_upper = False;
397                 send_hildon_command(HILDON_IM_LOW);
398         }
399
400         // TODO Analyze surrounding for autocapitalization
401 }
402
403 /**
404         @param flag HILDON_IM_MSG_START, HILDON_IM_MSG_CONTINUE, HILDON_IM_MSG_END
405         @param str the Utf8 string
406  */
407 static void insert_utf8(int flag, const char * str)
408 {
409         if (commit_mode == HILDON_IM_COMMIT_BUFFERED) {
410                 if (flag == HILDON_IM_MSG_START) {
411                         ds_set(&preedit_buffer, str);
412                 } else {
413                         ds_append(&preedit_buffer, str);
414                 }
415                 send_sdl_text_event(HIM_TEXTEDITINGEVENT, preedit_buffer.str);
416         } else { /* Direct, Redirect, and Proxy modes. */
417                 send_sdl_text_event(HIM_TEXTINPUTEVENT, str);
418         }
419 }
420
421 static void insert_special_key(SDLKey key)
422 {
423         HIM_SpecialKeyEvent event = { event_base, HIM_SPECIALKEYEVENT, key };
424         SDL_PushEvent((SDL_Event*)&event);
425 }
426
427 static void set_commit_mode(HildonIMCommitMode new_mode)
428 {
429         ds_clear(&preedit_buffer);
430         commit_mode = new_mode;
431 }
432
433 static void set_mask_state(HildonIMInternalModifierMask *mask,
434         HildonIMInternalModifierMask lock_mask,
435         HildonIMInternalModifierMask sticky_mask,
436         Bool was_press_and_release)
437 {
438         /* Locking Fn is disabled in TELE and NUMERIC */
439         if (!(input_mode & HILDON_GTK_INPUT_MODE_ALPHA) &&
440                 !(input_mode & HILDON_GTK_INPUT_MODE_HEXA)  &&
441                 ((input_mode & HILDON_GTK_INPUT_MODE_TELE) || 
442                  (input_mode & HILDON_GTK_INPUT_MODE_NUMERIC))) {
443                 if (*mask & lock_mask) {
444                         /* already locked, remove lock and set it to sticky */
445                         *mask &= ~(lock_mask | sticky_mask);
446                         *mask |= sticky_mask;
447                 } else if (*mask & sticky_mask) {
448                         /* the key is already sticky, it's fine */
449                 } else if (was_press_and_release) {
450                         /* Pressing the key for the first time stickies the key for one character,
451                          * but only if no characters were entered while holding the key down */
452                         *mask |= sticky_mask;
453                 }
454
455                 return;
456         }
457
458         if (*mask & lock_mask) {
459                 /* Pressing the key while already locked clears the state */
460                 *mask &= ~(lock_mask | sticky_mask);
461
462 #if MAEMO_VERSION == 5
463                 if (lock_mask & HILDON_IM_SHIFT_LOCK_MASK) {
464                         send_hildon_command(HILDON_IM_SHIFT_UNLOCKED);
465                 } else if (lock_mask & HILDON_IM_LEVEL_LOCK_MASK) {
466                         send_hildon_command(HILDON_IM_MOD_UNLOCKED);
467                 }
468 #endif
469
470         } else if (*mask & sticky_mask) {
471                 /* When the key is already sticky, a second press locks the key */
472                 *mask |= lock_mask;
473
474 #if MAEMO_VERSION == 5
475                 if (lock_mask & HILDON_IM_SHIFT_LOCK_MASK) {
476                         send_hildon_command(HILDON_IM_SHIFT_LOCKED);
477                 } else if (lock_mask & HILDON_IM_LEVEL_LOCK_MASK) {
478                         send_hildon_command(HILDON_IM_MOD_LOCKED);
479                 }
480 #endif
481
482         } else if (was_press_and_release) {
483                 /* Pressing the key for the first time stickies the key for one character,
484                  * but only if no characters were entered while holding the key down */
485                 *mask |= sticky_mask;
486         }
487 }
488
489 static int handle_x_event(const XEvent *e)
490 {
491         if (e->type == ClientMessage) {
492                 /* Receive messages from HIM */
493                 if (e->xclient.message_type == ATOM(HILDON_IM_INSERT_UTF8)) {
494                         /* Text insertion message */
495                         HildonIMInsertUtf8Message *msg =
496                                 (HildonIMInsertUtf8Message*)&e->xclient.data;
497                         assert(e->xclient.format == HILDON_IM_INSERT_UTF8_FORMAT);
498
499                         insert_utf8(msg->msg_flag, msg->utf8_str);
500
501                         return 1;
502                 } else if (e->xclient.message_type == ATOM(HILDON_IM_COM)) {
503                         HildonIMComMessage *msg = (HildonIMComMessage*)&e->xclient.data;
504
505                         options = msg->options;
506
507                         switch (msg->type) {
508                         case HILDON_IM_CONTEXT_HANDLE_ENTER:
509                                 insert_special_key(SDLK_RETURN);
510                                 break;
511                         case HILDON_IM_CONTEXT_HANDLE_TAB:
512                                 insert_special_key(SDLK_TAB);
513                                 break;
514                         case HILDON_IM_CONTEXT_HANDLE_BACKSPACE:
515                                 insert_special_key(SDLK_BACKSPACE);
516                                 break;
517                         case HILDON_IM_CONTEXT_HANDLE_SPACE:
518                                 insert_special_key(SDLK_SPACE);
519                                 break;
520
521                         case HILDON_IM_CONTEXT_CLIPBOARD_CUT:
522                                 send_sdl_clipboard_event(HIM_CLIPBOARD_CUT);
523                                 break;
524                         case HILDON_IM_CONTEXT_CLIPBOARD_COPY:
525                                 send_sdl_clipboard_event(HIM_CLIPBOARD_COPY);
526                                 break;
527                         case HILDON_IM_CONTEXT_CLIPBOARD_PASTE:
528                                 send_sdl_clipboard_event(HIM_CLIPBOARD_PASTE);
529                                 break;
530                         case HILDON_IM_CONTEXT_CLIPBOARD_SELECTION_QUERY:
531                                 send_sdl_clipboard_event(HIM_CLIPBOARD_REQUEST_SELECTION);
532                                 break;
533
534                         case HILDON_IM_CONTEXT_DIRECT_MODE:
535                                 set_commit_mode(HILDON_IM_COMMIT_DIRECT);
536                                 break;
537                         case HILDON_IM_CONTEXT_BUFFERED_MODE:
538                                 set_commit_mode(HILDON_IM_COMMIT_BUFFERED);
539                                 break;
540                         case HILDON_IM_CONTEXT_REDIRECT_MODE:
541                                 set_commit_mode(HILDON_IM_COMMIT_REDIRECT);
542                                 break;
543                         case HILDON_IM_CONTEXT_SURROUNDING_MODE:
544                                 set_commit_mode(HILDON_IM_COMMIT_SURROUNDING);
545                                 break;
546
547                         case HILDON_IM_CONTEXT_CONFIRM_SENTENCE_START:
548                                 check_sentence_start();
549                                 break;
550                         case HILDON_IM_CONTEXT_FLUSH_PREEDIT:
551                                 commit_preedit_buffer();
552                                 break;
553
554                         case HILDON_IM_CONTEXT_REQUEST_SURROUNDING:
555                                 send_sdl_request_surrounding_event(False);
556                                 break;
557                         case HILDON_IM_CONTEXT_CLEAR_STICKY:
558                                 mod_mask &= ~(HILDON_IM_SHIFT_STICKY_MASK |
559                                         HILDON_IM_SHIFT_LOCK_MASK |
560                                         HILDON_IM_LEVEL_STICKY_MASK |
561                                         HILDON_IM_LEVEL_LOCK_MASK);
562                                 break;
563
564                         case HILDON_IM_CONTEXT_WIDGET_CHANGED:
565                                 mod_mask = 0; /* Clear modifier status */
566                                 break;
567                         case HILDON_IM_CONTEXT_ENTER_ON_FOCUS:
568                                 /* We don't have the concept of focused widget. */
569                                 insert_special_key(SDLK_RETURN);
570                                 break;
571
572 #if MAEMO_VERSION == 5
573                         case HILDON_IM_CONTEXT_PREEDIT_MODE:
574                                 set_commit_mode(HILDON_IM_COMMIT_PREEDIT);
575                                 break;
576
577                         case HILDON_IM_CONTEXT_REQUEST_SURROUNDING_FULL:
578                                 send_sdl_request_surrounding_event(True);
579                                 break;
580                         case HILDON_IM_CONTEXT_CANCEL_PREEDIT:
581                                 cancel_preedit();
582                                 break;
583
584                         case HILDON_IM_CONTEXT_SPACE_AFTER_COMMIT:
585                                 space_after_commit = True;
586                                 break;
587                         case HILDON_IM_CONTEXT_NO_SPACE_AFTER_COMMIT:
588                                 space_after_commit = False;
589                                 break;
590 #endif
591                                 
592                         default:
593                                 fprintf(stderr, "Unknown Hildon IM COM message %d\n",
594                                         msg->type);
595                                 break;
596                         }
597
598                         return 1;
599                 } else if (e->xclient.message_type == ATOM(HILDON_IM_SURROUNDING_CONTENT)) {
600                         assert(e->xclient.format == HILDON_IM_SURROUNDING_CONTENT_FORMAT);
601                         // TODO Surrounding content
602                         fprintf(stderr, "Surrounding content is not implemented\n");
603                 } else if (e->xclient.message_type == ATOM(HILDON_IM_SURROUNDING)) {
604                         assert(e->xclient.format == HILDON_IM_SURROUNDING_FORMAT);
605                         HildonIMSurroundingMessage *msg = (HildonIMSurroundingMessage*)(&e->xclient.data);
606                         set_sdl_cursor_location(msg->offset_is_relative, msg->cursor_offset);
607                 }
608         }
609
610         return 0;
611 }
612
613 static unsigned sdl_mod_to_x_state(const SDLMod mod)
614 {
615         unsigned state = 0;
616         if (mod & KMOD_SHIFT) state |= ShiftMask;
617         if (mod & KMOD_CAPS) state |= LockMask;
618         if (mod & KMOD_CTRL) state |= ControlMask;
619         if (mod & KMOD_MODE) state |= LevelMask;
620         return state;
621 }
622
623 /** @return 1 hides the event from the SDL queue */
624 static int filter_keypress(const SDL_KeyboardEvent *event)
625 {
626         /* Basically, convert a SDL KeyboardEvent back into a XKeyEvent. */
627         const Bool pressed = event->state == SDL_PRESSED;
628         KeyCode keycode = event->keysym.scancode; // As in, Xlib KeyCode.
629         unsigned int mods_rtrn, state = sdl_mod_to_x_state(event->keysym.mod);
630         KeySym keysym;
631         XkbLookupKeySym(display, keycode, state, &mods_rtrn, &keysym);
632
633         Uint32 c = 0; /* To be replaced by the commited unicode character. */
634
635         /** A special setting that causes "normal" keys not to be filtered,
636                 even if HIM will latter send a commit event. Great for games.
637          */
638         const int normal_key_return = input_mode & HIM_MODE_NO_FILTER ? 0 : 1;
639
640         /* A dead key will not be immediately commited, but combined with the next key */
641         if (keysym >= XK_dead_grave && keysym <= XK_dead_horn) {
642                 mod_mask |= HILDON_IM_DEAD_KEY_MASK;
643         } else {
644                 mod_mask &= ~HILDON_IM_DEAD_KEY_MASK;
645         }
646
647         /* A normal dead key first press */
648         if ((mod_mask & HILDON_IM_DEAD_KEY_MASK) && combining_char == 0) {
649                 combining_char = dead_key_to_unicode_combining_character(keysym);
650                 return normal_key_return; /* Treat dead keys as "normal keys" */
651         }
652
653         /* Pressing any key while the compose key is pressed will keep that
654                 character from being directly submitted to the application. This
655                 allows the IM process to override the interpretation of the key */
656         if (keysym == COMPOSE_KEY) {
657                 if (pressed) {
658                         mod_mask |= HILDON_IM_COMPOSE_MASK;
659                 } else {
660                         mod_mask &= ~HILDON_IM_COMPOSE_MASK;
661                 }
662         }
663
664         /* Sticky and locking keys initialization */
665         if (!pressed) {
666                 /* Set the appropiate mask when Shift or Fn is released. */
667                 if (keysym == XK_Shift_L || keysym == XK_Shift_R) {
668                         set_mask_state(&mod_mask,
669                                 HILDON_IM_SHIFT_LOCK_MASK, HILDON_IM_SHIFT_STICKY_MASK,
670                                 last_keysym == XK_Shift_L || last_keysym == XK_Shift_R);
671                 } else if (keysym == LEVEL_KEY) {
672                         set_mask_state(&mod_mask,
673                                 HILDON_IM_LEVEL_LOCK_MASK, HILDON_IM_LEVEL_STICKY_MASK,
674                                 last_keysym == LEVEL_KEY);
675                 }
676         }
677
678         /* Update last_keysym now; we do not need it later. */
679         last_keysym = keysym;
680
681         /* We let the input method know of things like Return, Tab, etc. */
682         if (keysym == XK_Return || keysym == XK_KP_Enter
683          || keysym == XK_ISO_Enter || keysym == XK_Tab) {
684                 send_hildon_key_event(pressed, state, keysym, keycode);
685                 return 0;
686         }
687
688         /* When the level key is in sticky or locked state, translate the
689            keyboard state as if that level key was being held down. */
690         if ((mod_mask & (HILDON_IM_LEVEL_STICKY_MASK | HILDON_IM_LEVEL_LOCK_MASK)) ||
691                         state == LevelMask) {
692                 /* Use Xkb to query the keymap and get the key we would get if we
693                         were to be holding Fn */
694                 unsigned int mods_rtrn;
695                 KeySym new_keysym;
696                 if (XkbLookupKeySym(display, keycode, LevelMask, &mods_rtrn, &new_keysym)) {
697                         keysym = new_keysym;
698                 }
699         } else if (options & HILDON_IM_AUTOLEVEL_NUMERIC && 
700                 (input_mode & HILDON_GTK_INPUT_MODE_FULL) == HILDON_GTK_INPUT_MODE_NUMERIC) {
701                 keysym = get_keysym_for_level(keycode, NUMERIC_LEVEL);
702         } else if (options & HILDON_IM_LOCK_LEVEL) {
703                 keysym = get_keysym_for_level(keycode, LOCKABLE_LEVEL);
704         }
705
706
707         /* Hardware keyboard autocapitalization */
708         if (auto_upper && (input_mode & HILDON_GTK_INPUT_MODE_AUTOCAP)) {
709                 KeySym lower, upper;
710                 XConvertCase(keysym, &lower, &upper);
711
712                 if (state & ShiftMask) {
713                         keysym = lower; // Since we're latter going to shift it :)
714                 } else {
715                         keysym = upper;
716                 }
717         }
718
719         /* Shift lock or holding the shift key down forces uppercase */
720         if (mod_mask & HILDON_IM_SHIFT_LOCK_MASK || state & ShiftMask) {
721                 KeySym lower, upper;
722                 XConvertCase(keysym, &lower, &upper);
723
724                 keysym = upper;
725         } else if (mod_mask & HILDON_IM_SHIFT_STICKY_MASK) {
726                 KeySym lower, upper;
727                 XConvertCase(keysym, &lower, &upper);
728
729                 if (lower == upper) {
730                         /* Non printable character: check the keyboard map */
731                         unsigned int mods_rtrn;
732                         KeySym new_keysym;
733                         if (XkbLookupKeySym(display, keycode, ShiftMask, &mods_rtrn, &new_keysym)) {
734                                 keysym = new_keysym;
735                         }
736                 } else if (keysym == upper) {
737                         /* Previously upper case (means caps lock is on) ==> lower case. */
738                         keysym = lower;
739                 } else {
740                         keysym = upper;
741                 }
742         }
743
744         /* Sticky and lock state reset */
745         if (!pressed) {
746                 if (keysym != XK_Shift_L && keysym != XK_Shift_R) {
747                         /* Pressing anything other than shift, if not locked,
748                            resets shift state (this is the other important part of
749                            keys. */
750                         if (!(mod_mask & HILDON_IM_SHIFT_LOCK_MASK)) {
751                                 mod_mask &= ~HILDON_IM_SHIFT_STICKY_MASK;
752                         }
753                 }
754                 if (keysym != LEVEL_KEY) {
755                         /* Same for Fn/Level key */
756                         if (!(mod_mask & HILDON_IM_LEVEL_LOCK_MASK)) {
757                                 mod_mask &= ~HILDON_IM_LEVEL_STICKY_MASK;
758                         }
759                 }
760         }
761
762         /* From now on, just ignore release key events. */
763         /* Save for Ctrl+Anykey. Forward those to HIM. CtrlC CtrlV support? */
764         if (!pressed || state & ControlMask) {
765                 send_hildon_key_event(pressed, state, keysym, keycode);
766                 return 0;
767         }
768
769         /* Pressing a dead key twice, or if followed by a space, inputs 
770                 the dead key's character representation. */
771         if ((mod_mask & HILDON_IM_DEAD_KEY_MASK || keysym == XK_space) &&
772                 combining_char) {
773                 Uint32 last = dead_key_to_unicode_combining_character(keysym);
774                 if (last == combining_char || keysym == XK_space) {
775                         /* Pressed twice or followed by space => print dead key. */
776                         c = combining_character_to_unicode(combining_char);
777                 } else {
778                         /* Some other thing. Consider as regular keypress. */
779                         c = keysym_to_unicode(keysym);
780                 }
781                 combining_char = 0;
782         } else {
783                 /* A regular keypress. */
784                 if (mod_mask & HILDON_IM_COMPOSE_MASK) {
785                         /* Chr key pressed. HIM handles all of this. */
786                         send_hildon_key_event(pressed, state, keysym, keycode);
787                         return 1;
788                 } else {
789                         /* A completely regular keypress. */
790                         c = keysym_to_unicode(keysym);
791                 }
792         }
793
794         if (c) {
795                 /* Prepare a buffer for text input event */
796                 HIM_TextInputEvent event;
797                 event.type = event_base;
798                 event.code = HIM_TEXTINPUTEVENT;
799
800                 /* Check if we have pending diactrics... */
801                 if (combining_char) {
802                         /* This is not a full unicode normalizer, but works. */
803                         int new_c = compose_unicode_characters(c, combining_char);
804
805                         if (new_c != NOT_COMPOSITE) {
806                                 c = new_c;
807                         }
808
809                         combining_char = 0;
810                 }
811
812                 unicode_to_utf8(c, event.text);
813                 send_hildon_key_event(pressed, state, unicode_to_keysym(c), keycode);
814
815                 SDL_PushEvent((SDL_Event*)&event);
816
817                 return normal_key_return;
818         } else {
819                 /* We didn't output any string. */
820                 send_hildon_key_event(pressed, state, keysym, keycode);
821
822                 /* Non-printable characters invalidate any previous dead keys */
823                 /* Save for shift, which you might need. */
824                 if (keysym != XK_Shift_L && keysym != XK_Shift_R) {
825                         combining_char = 0;
826                 }
827
828                 return 0;
829         }
830
831         return 0;
832 }
833
834 int HIM_Init(Uint32 flags, Uint8 event)
835 {
836         SDL_SysWMinfo info;
837
838         SDL_VERSION(&info.version);
839         if (SDL_GetWMInfo(&info) != 1) {
840                 SDL_SetError("SDL_him is incompatible with this SDL version");
841                 return -1;
842         }
843
844         if (event != 0 && (event < SDL_USEREVENT || event >= SDL_NUMEVENTS)) {
845                 SDL_SetError("Invalid event number");
846                 return -1;
847         }
848
849         display = info.info.x11.display;
850         event_base = event;
851
852         if (!initialized) {
853                 XInternAtoms(display, (char**)atom_names,
854                                         HILDON_IM_NUM_ATOMS, True, atom_values);
855                 if (ATOM(HILDON_IM_WINDOW) == None) {
856                         /* a required atom was not found */
857                         SDL_SetError("Hildon Input Method is not loaded");
858                         return -1;
859                 }
860                 initialized = True;
861
862                 /* Enable some SDL features we need */
863                 SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
864         }
865
866         enabled = False;
867
868         return 0;
869 }
870
871 void HIM_Quit()
872 {
873         if (enabled) {
874                 HIM_Disable();
875         }
876 }
877
878 void HIM_Enable(HIM_Mode mode)
879 {
880         input_mode = mode;
881
882         /* Send the input mode before the command */
883         send_input_mode();
884
885         /* Default commit mode required by Fremantle */
886         commit_mode = HILDON_IM_COMMIT_REDIRECT;
887
888         /* Reset some stuff, like if we had changed focused widget. */
889         mod_mask = 0;
890         last_keysym = XK_VoidSymbol;
891         combining_char = 0;
892
893         send_hildon_command(HILDON_IM_SETCLIENT);
894
895         enabled = True;
896 }
897
898 void HIM_Disable()
899 {
900         enabled = False;
901         cancel_preedit();
902         send_hildon_command(HILDON_IM_CLEAR);
903 }
904
905 void HIM_ShowKeyboard(HIM_Trigger reason)
906 {
907         trigger_mode = reason;
908         send_hildon_command(HILDON_IM_SETNSHOW);
909 }
910
911 void HIM_HideKeyboard()
912 {
913         send_hildon_command(HILDON_IM_HIDE);
914 }
915
916 int HIM_FilterEvent(const SDL_Event *event)
917 {
918         switch (event->type) {
919                 case SDL_SYSWMEVENT:
920                         return handle_x_event(&event->syswm.msg->event.xevent);
921                         break;
922                 case SDL_KEYDOWN:
923                 case SDL_KEYUP:
924                         if (enabled) {
925                                 /* Only process keypresses at all if enabled */
926                                 return filter_keypress(&event->key);
927                         }
928                         break;
929         }
930
931         return 0;
932 }
933
934 void HIM_SendSurrounding(const char * text, unsigned long cursor)
935 {
936         send_surrounding_content(text);
937         send_surrounding_header(cursor);
938 }
939
940 void HIM_SendSelection(const char * text)
941 {
942         Bool has_selection = text && text[0] != '\0';
943         send_selection_reply(has_selection);
944 }
945