Enable IBus integration
[profile/ivi/weekeyboard.git] / src / wkb-main.c
1 /*
2  * Copyright © 2013 Intel Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20
21 #include <Eina.h>
22 #include <Ecore.h>
23 #include <Ecore_Wayland.h>
24 #include <Ecore_Evas.h>
25 #include <Edje.h>
26
27 #include <linux/input.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include "wkb-log.h"
33 #include "wkb-ibus.h"
34
35 #include "input-method-client-protocol.h"
36 #include "text-client-protocol.h"
37
38 struct weekeyboard
39 {
40    Ecore_Evas *ee;
41    Ecore_Wl_Window *win;
42    Evas_Object *edje_obj;
43    const char *ee_engine;
44    char **ignore_keys;
45
46    struct wl_surface *surface;
47    struct wl_input_panel *ip;
48    struct wl_input_method *im;
49    struct wl_output *output;
50    struct wl_input_method_context *im_ctx;
51
52    char *surrounding_text;
53    char *preedit_str;
54    char *language;
55
56    uint32_t text_direction;
57    uint32_t preedit_style;
58    uint32_t content_hint;
59    uint32_t content_purpose;
60    uint32_t surrounding_cursor;
61
62    Eina_Bool context_changed;
63 };
64
65 static void
66 _cb_wkb_delete_request(Ecore_Evas *ee EINA_UNUSED)
67 {
68    if (!wkb_ibus_shutdown())
69       ecore_main_loop_quit();
70 }
71
72 static char *
73 _wkb_insert_text(const char *text, uint32_t offset, const char *insert)
74 {
75    char *new_text = malloc(strlen(text) + strlen(insert) + 1);
76
77    strncat(new_text, text, offset);
78    new_text[offset] = '\0';
79    strcat(new_text, insert);
80    strcat(new_text, text + offset);
81
82    return new_text;
83 }
84
85 static void
86 _wkb_commit_preedit_str(struct weekeyboard *wkb)
87 {
88    char *surrounding_text;
89
90    if (!wkb->preedit_str || !strlen(wkb->preedit_str) == 0)
91       return;
92
93    wl_input_method_context_cursor_position(wkb->im_ctx, 0, 0);
94    wl_input_method_context_commit_string(wkb->im_ctx, wkb_ibus_input_context_serial(), wkb->preedit_str);
95
96    if (wkb->surrounding_text)
97      {
98         surrounding_text = _wkb_insert_text(wkb->surrounding_text, wkb->surrounding_cursor, wkb->preedit_str);
99         free(wkb->surrounding_text);
100         wkb->surrounding_text = surrounding_text;
101         wkb->surrounding_cursor += strlen(wkb->preedit_str);
102      }
103    else
104      {
105         wkb->surrounding_text = strdup(wkb->preedit_str);
106         wkb->surrounding_cursor = strlen(wkb->preedit_str);
107      }
108
109    free(wkb->preedit_str);
110    wkb->preedit_str = strdup("");
111 }
112
113 static void
114 _wkb_send_preedit_str(struct weekeyboard *wkb, int cursor)
115 {
116    unsigned int index = strlen(wkb->preedit_str);
117
118    if (wkb->preedit_style)
119       wl_input_method_context_preedit_styling(wkb->im_ctx, 0, strlen(wkb->preedit_str), wkb->preedit_style);
120
121    if (cursor > 0)
122       index = cursor;
123
124    wl_input_method_context_preedit_cursor(wkb->im_ctx, index);
125    wl_input_method_context_preedit_string(wkb->im_ctx, wkb_ibus_input_context_serial(), wkb->preedit_str, wkb->preedit_str);
126 }
127
128 static void
129 _wkb_update_preedit_str(struct weekeyboard *wkb, const char *key)
130 {
131    char *tmp;
132
133    if (!wkb->preedit_str)
134       wkb->preedit_str = strdup("");
135
136    tmp = calloc(1, strlen(wkb->preedit_str) + strlen(key) + 1);
137    sprintf(tmp, "%s%s", wkb->preedit_str, key);
138    free(wkb->preedit_str);
139    wkb->preedit_str = tmp;
140
141    if (strcmp(key, " ") == 0)
142       _wkb_commit_preedit_str(wkb);
143    else
144       _wkb_send_preedit_str(wkb, -1);
145 }
146
147 static Eina_Bool
148 _wkb_ignore_key(struct weekeyboard *wkb, const char *key)
149 {
150    int i;
151
152    if (!wkb->ignore_keys)
153        return EINA_FALSE;
154
155    for (i = 0; wkb->ignore_keys[i] != NULL; i++)
156       if (!strcmp(key, wkb->ignore_keys[i]))
157          return EINA_TRUE;
158
159    return EINA_FALSE;
160 }
161
162 static void
163 _cb_wkb_on_key_down(void *data, Evas_Object *obj, const char *emission EINA_UNUSED, const char *source)
164 {
165    struct weekeyboard *wkb = data;
166    char *src;
167    const char *key;
168
169    src = strdup(source);
170    key = strtok(src, ":"); /* ignore group */
171    key = strtok(NULL, ":");
172    if (key == NULL)
173        key = ":";
174
175    if (_wkb_ignore_key(wkb, key))
176      {
177         DBG("Ignoring key: '%s'", key);
178         goto end;
179      }
180
181    wkb_ibus_input_context_process_key_event(key);
182 #if 0
183    else if (strcmp(key, "backspace") == 0)
184      {
185         if (strlen(wkb->preedit_str) == 0)
186           {
187              wl_input_method_context_delete_surrounding_text(wkb->im_ctx, -1, 1);
188              wl_input_method_context_commit_string(wkb->im_ctx, wkb->serial, "");
189           }
190         else
191           {
192              wkb->preedit_str[strlen(wkb->preedit_str) - 1] = '\0';
193              _wkb_send_preedit_str(wkb, -1);
194           }
195
196         goto end;
197      }
198    else if (strcmp(key, "enter") == 0)
199      {
200         _wkb_commit_preedit_str(wkb);
201         wl_input_method_context_keysym(wkb->im_ctx, wkb->serial, 0,
202                         XKB_KEY_Return, WL_KEYBOARD_KEY_STATE_PRESSED,
203                         0);
204         goto end;
205      }
206    else if (strcmp(key, "space") == 0)
207      {
208         key = " ";
209      }
210
211    DBG("Key pressed: '%s'", key);
212
213    _wkb_update_preedit_str(wkb, key);
214 #endif
215 end:
216    free(src);
217 }
218
219 static void
220 _wkb_im_ctx_surrounding_text(void *data, struct wl_input_method_context *im_ctx, const char *text, uint32_t cursor, uint32_t anchor)
221 {
222 #if 0
223    struct weekeyboard *wkb = data;
224
225    free(wkb->surrounding_text);
226    wkb->surrounding_text = strdup(text);
227    wkb->surrounding_cursor = cursor;
228 #endif
229 }
230
231 static void
232 _wkb_im_ctx_reset(void *data, struct wl_input_method_context *im_ctx)
233 {
234 #if 0
235    struct weekeyboard *wkb = data;
236
237    if (strlen(wkb->preedit_str))
238      {
239         free(wkb->preedit_str);
240         wkb->preedit_str = strdup("");
241      }
242 #endif
243 }
244
245 static void
246 _wkb_im_ctx_content_type(void *data, struct wl_input_method_context *im_ctx, uint32_t hint, uint32_t purpose)
247 {
248    struct weekeyboard *wkb = data;
249
250    DBG("im_context = %p hint = %d purpose = %d", im_ctx, hint, purpose);
251
252    if (!wkb->context_changed)
253       return;
254
255    switch (purpose)
256      {
257       case WL_TEXT_INPUT_CONTENT_PURPOSE_DIGITS:
258       case WL_TEXT_INPUT_CONTENT_PURPOSE_NUMBER:
259            {
260               edje_object_signal_emit(wkb->edje_obj, "show,numeric", "");
261               break;
262            }
263       default:
264            {
265               edje_object_signal_emit(wkb->edje_obj, "show,alphanumeric", "");
266               break;
267            }
268      }
269
270    wkb->content_hint = hint;
271    wkb->content_purpose = purpose;
272
273    wkb->context_changed = EINA_FALSE;
274 }
275
276 static void
277 _wkb_im_ctx_invoke_action(void *data, struct wl_input_method_context *im_ctx, uint32_t button, uint32_t index)
278 {
279 #if 0
280    struct weekeyboard *wkb = data;
281
282    if (button != BTN_LEFT)
283       return;
284
285    _wkb_send_preedit_str(wkb, index);
286 #endif
287 }
288
289 static void
290 _wkb_im_ctx_commit_state(void *data, struct wl_input_method_context *im_ctx, uint32_t serial)
291 {
292    struct weekeyboard *wkb = data;
293
294    if (wkb->surrounding_text)
295       INF("Surrounding text updated: %s", wkb->surrounding_text);
296
297    wkb_ibus_input_context_set_serial(serial);
298 #if 0
299    /* FIXME */
300    wl_input_method_context_language(im_ctx, wkb_ibus_input_context_serial(), "en");//wkb->language);
301    wl_input_method_context_text_direction(im_ctx, wkb_ibus_input_context_serial(), WL_TEXT_INPUT_TEXT_DIRECTION_LTR);//wkb->text_direction);
302 #endif
303 }
304
305 static void
306 _wkb_im_ctx_preferred_language(void *data, struct wl_input_method_context *im_ctx, const char *language)
307 {
308 #if 0
309    struct weekeyboard *wkb = data;
310
311    if (language && wkb->language && !strcmp(language, wkb->language))
312       return;
313
314    if (wkb->language)
315      {
316         free(wkb->language);
317         wkb->language = NULL;
318      }
319
320    if (language)
321      {
322         wkb->language = strdup(language);
323         INF("Language changed, new: '%s'", language);
324      }
325 #endif
326 }
327
328 static const struct wl_input_method_context_listener wkb_im_context_listener = {
329      _wkb_im_ctx_surrounding_text,
330      _wkb_im_ctx_reset,
331      _wkb_im_ctx_content_type,
332      _wkb_im_ctx_invoke_action,
333      _wkb_im_ctx_commit_state,
334      _wkb_im_ctx_preferred_language,
335 };
336
337 static void
338 _wkb_im_activate(void *data, struct wl_input_method *input_method, struct wl_input_method_context *im_ctx)
339 {
340    struct weekeyboard *wkb = data;
341
342    DBG("Activate");
343
344    if (wkb->im_ctx)
345       wl_input_method_context_destroy(wkb->im_ctx);
346
347    if (wkb->preedit_str)
348       free(wkb->preedit_str);
349
350    wkb->preedit_str = strdup("");
351    wkb->content_hint = WL_TEXT_INPUT_CONTENT_HINT_NONE;
352    wkb->content_purpose = WL_TEXT_INPUT_CONTENT_PURPOSE_NORMAL;
353
354    free(wkb->language);
355    wkb->language = NULL;
356
357    free(wkb->surrounding_text);
358    wkb->surrounding_text = NULL;
359
360    wkb_ibus_input_context_set_serial(0);
361
362    wkb->im_ctx = im_ctx;
363    wl_input_method_context_add_listener(im_ctx, &wkb_im_context_listener, wkb);
364    wkb_ibus_input_context_create(im_ctx);
365
366 #if 0
367    struct wl_array modifiers_map;
368    wl_array_init(&modifiers_map);
369
370    keysym_modifiers_add(&modifiers_map, "Shift");
371    keysym_modifiers_add(&modifiers_map, "Control");
372    keysym_modifiers_add(&modifiers_map, "Mod1");
373
374    wl_input_method_context_modifiers_map(im_ctx, &modifiers_map);
375
376    wkb->keysym.shift_mask = keysym_modifiers_get_mask(&modifiers_map, "Shift");
377
378    wl_array_release(&modifiers_map);
379    */
380
381    /* FIXME */
382    wl_input_method_context_language(im_ctx, wkb_ibus_input_context_serial(), "en");//wkb->language);
383    wl_input_method_context_text_direction(im_ctx, wkb_ibus_input_context_serial(), WL_TEXT_INPUT_TEXT_DIRECTION_LTR);//wkb->text_direction);
384 #endif
385    wkb->context_changed = EINA_TRUE;
386    evas_object_show(wkb->edje_obj);
387 }
388
389 static void
390 _wkb_im_deactivate(void *data, struct wl_input_method *input_method, struct wl_input_method_context *im_ctx)
391 {
392    struct weekeyboard *wkb = data;
393
394    DBG("Deactivate");
395
396    wkb_ibus_input_context_destroy();
397
398    if (wkb->im_ctx)
399      {
400         wl_input_method_context_destroy(wkb->im_ctx);
401         wkb->im_ctx = NULL;
402      }
403
404    evas_object_hide(wkb->edje_obj);
405 }
406
407 static const struct wl_input_method_listener wkb_im_listener = {
408      _wkb_im_activate,
409      _wkb_im_deactivate
410 };
411
412
413 static Eina_Bool
414 _wkb_ui_setup(struct weekeyboard *wkb)
415 {
416    char path[PATH_MAX];
417    Evas *evas;
418    Evas_Coord w, h;
419    char *ignore_keys;
420
421    ecore_evas_alpha_set(wkb->ee, EINA_TRUE);
422    ecore_evas_title_set(wkb->ee, "Weekeyboard");
423
424    evas = ecore_evas_get(wkb->ee);
425    wkb->edje_obj = edje_object_add(evas);
426    /*ecore_evas_object_associate(wkb->ee, edje_obj, ECORE_EVAS_OBJECT_ASSOCIATE_BASE);*/
427
428    /* Check which theme we should use according to the screen width */
429    ecore_wl_screen_size_get(&w, &h);
430    if (w >= 720)
431       w = 720;
432    else
433       w = 600;
434
435    sprintf(path, PKGDATADIR"/default_%d.edj", w);
436    DBG("Loading edje file: '%s'", path);
437
438    if (!edje_object_file_set(wkb->edje_obj, path, "main"))
439      {
440         int err = edje_object_load_error_get(wkb->edje_obj);
441         ERR("Unable to load the edje file: '%s'", edje_load_error_str(err));
442         return EINA_FALSE;
443      }
444
445    edje_object_size_min_get(wkb->edje_obj, &w, &h);
446    if (w == 0 || h == 0)
447      {
448         edje_object_size_min_restricted_calc(wkb->edje_obj, &w, &h, w, h);
449         if (w == 0 || h == 0)
450            edje_object_parts_extends_calc(wkb->edje_obj, NULL, NULL, &w, &h);
451      }
452
453    ecore_evas_move_resize(wkb->ee, 0, 0, w, h);
454    evas_object_move(wkb->edje_obj, 0, 0);
455    evas_object_resize(wkb->edje_obj, w, h);
456    evas_object_size_hint_min_set(wkb->edje_obj, w, h);
457    evas_object_size_hint_max_set(wkb->edje_obj, w, h);
458
459    edje_object_signal_callback_add(wkb->edje_obj, "key_down", "*", _cb_wkb_on_key_down, wkb);
460    ecore_evas_callback_delete_request_set(wkb->ee, _cb_wkb_delete_request);
461
462    /*
463     * The keyboard surface is bigger than it appears so that we can show the
464     * key pressed animation without requiring the use of subsurfaces. Here we
465     * resize the input region of the surface to match the keyboard background
466     * image, so that we can pass mouse events to the surfaces that may be
467     * located below the keyboard.
468     */
469    if (wkb->win)
470      {
471         int x, y, w, h;
472         struct wl_region *input = wl_compositor_create_region(wkb->win->display->wl.compositor);
473
474         edje_object_part_geometry_get(wkb->edje_obj, "background", &x, &y, &w, &h);
475         wl_region_add(input, x, y, w, h);
476         wl_surface_set_input_region(wkb->surface, input);
477         wl_region_destroy(input);
478      }
479
480    /* special keys */
481    ignore_keys = edje_file_data_get(path, "ignore-keys");
482    if (!ignore_keys)
483      {
484         ERR("Special keys file not found in: '%s'", path);
485         goto end;
486      }
487
488    DBG("Got ignore keys: '%s'", ignore_keys);
489    wkb->ignore_keys = eina_str_split(ignore_keys, "\n", 0);
490    free(ignore_keys);
491
492 end:
493    ecore_evas_show(wkb->ee);
494    return EINA_TRUE;
495 }
496
497 static void
498 _wkb_setup(struct weekeyboard *wkb)
499 {
500    struct wl_list *globals;
501    struct wl_registry *registry;
502    Ecore_Wl_Global *global;
503
504    struct wl_input_panel_surface *ips;
505
506    globals = ecore_wl_globals_get();
507    registry = ecore_wl_registry_get();
508    wl_list_for_each(global, globals, link)
509      {
510         if (strcmp(global->interface, "wl_input_panel") == 0)
511            wkb->ip = wl_registry_bind(registry, global->id, &wl_input_panel_interface, 1);
512         else if (strcmp(global->interface, "wl_input_method") == 0)
513            wkb->im = wl_registry_bind(registry, global->id, &wl_input_method_interface, 1);
514         else if (strcmp(global->interface, "wl_output") == 0)
515            wkb->output = wl_registry_bind(registry, global->id, &wl_output_interface, 1);
516      }
517
518    /* Set input panel surface */
519    DBG("Setting up input panel");
520    wkb->win = ecore_evas_wayland_window_get(wkb->ee);
521    ecore_wl_window_type_set(wkb->win, ECORE_WL_WINDOW_TYPE_NONE);
522    wkb->surface = ecore_wl_window_surface_create(wkb->win);
523    ips = wl_input_panel_get_input_panel_surface(wkb->ip, wkb->surface);
524    wl_input_panel_surface_set_toplevel(ips, wkb->output, WL_INPUT_PANEL_SURFACE_POSITION_CENTER_BOTTOM);
525
526    /* Input method listener */
527    DBG("Adding wl_input_method listener");
528    wl_input_method_add_listener(wkb->im, &wkb_im_listener, wkb);
529 }
530
531 static void
532 _wkb_free(struct weekeyboard *wkb)
533 {
534    if (wkb->im_ctx)
535       wl_input_method_context_destroy(wkb->im_ctx);
536
537    if (wkb->edje_obj)
538       evas_object_del(wkb->edje_obj);
539
540    if (wkb->ignore_keys)
541      {
542         free(*wkb->ignore_keys);
543         free(wkb->ignore_keys);
544      }
545
546    free(wkb->preedit_str);
547    free(wkb->surrounding_text);
548 }
549
550 static Eina_Bool
551 _wkb_check_evas_engine(struct weekeyboard *wkb)
552 {
553    Eina_Bool ret = EINA_FALSE;
554    char *env = getenv("ECORE_EVAS_ENGINE");
555
556    if (!env)
557      {
558         if (ecore_evas_engine_type_supported_get(ECORE_EVAS_ENGINE_WAYLAND_SHM))
559            env = "wayland_shm";
560         else if (ecore_evas_engine_type_supported_get(ECORE_EVAS_ENGINE_WAYLAND_EGL))
561            env = "wayland_egl";
562         else
563           {
564              ERR("ERROR: Ecore_Evas does must be compiled with support for Wayland engines");
565              goto err;
566           }
567      }
568    else if (strcmp(env, "wayland_shm") != 0 && strcmp(env, "wayland_egl") != 0)
569      {
570         ERR("ERROR: ECORE_EVAS_ENGINE must be set to either 'wayland_shm' or 'wayland_egl'");
571         goto err;
572      }
573
574    wkb->ee_engine = env;
575    ret = EINA_TRUE;
576
577 err:
578    return ret;
579 }
580
581 static Eina_Bool
582 _wkb_check_ibus_connection(void *data)
583 {
584    static int tries = 0;
585
586    if (tries++ > 5)
587      {
588         CRITICAL("Unable to establish connection to IBus.");
589         return ECORE_CALLBACK_DONE;
590      }
591
592    return !wkb_ibus_is_connected();
593 }
594
595 int
596 main(int argc EINA_UNUSED, char **argv EINA_UNUSED)
597 {
598    struct weekeyboard wkb = {0};
599    int ret = EXIT_FAILURE;
600
601    if (!wkb_log_init("weekeyboard"))
602       return ret;
603
604    if (!ecore_evas_init())
605       goto ee_err;
606
607    if (!edje_init())
608       goto edj_err;
609
610    if (!_wkb_check_evas_engine(&wkb))
611       goto engine_err;
612
613    DBG("Selected engine: '%s'", wkb.ee_engine);
614    wkb.ee = ecore_evas_new(wkb.ee_engine, 0, 0, 1, 1, "frame=0");
615
616    if (!wkb.ee)
617      {
618         ERR("ERROR: Unable to create Ecore_Evas object");
619         goto edj_err;
620      }
621
622    _wkb_setup(&wkb);
623
624    wkb_ibus_init();
625
626    if (!_wkb_ui_setup(&wkb))
627       goto end;
628
629    wkb_ibus_connect();
630    ecore_timer_add(1, _wkb_check_ibus_connection, NULL);
631    ecore_main_loop_begin();
632
633    ret = EXIT_SUCCESS;
634
635 end:
636    _wkb_free(&wkb);
637    ecore_evas_free(wkb.ee);
638
639 engine_err:
640    edje_shutdown();
641
642 edj_err:
643    ecore_evas_shutdown();
644
645 ee_err:
646    wkb_log_shutdown();
647
648    return ret;
649 }