Fix handling of special keys
[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
183 end:
184    free(src);
185 }
186
187 static void
188 _wkb_im_ctx_surrounding_text(void *data, struct wl_input_method_context *im_ctx, const char *text, uint32_t cursor, uint32_t anchor)
189 {
190 #if 0
191    struct weekeyboard *wkb = data;
192
193    free(wkb->surrounding_text);
194    wkb->surrounding_text = strdup(text);
195    wkb->surrounding_cursor = cursor;
196 #endif
197 }
198
199 static void
200 _wkb_im_ctx_reset(void *data, struct wl_input_method_context *im_ctx)
201 {
202 #if 0
203    struct weekeyboard *wkb = data;
204
205    if (strlen(wkb->preedit_str))
206      {
207         free(wkb->preedit_str);
208         wkb->preedit_str = strdup("");
209      }
210 #endif
211 }
212
213 static void
214 _wkb_im_ctx_content_type(void *data, struct wl_input_method_context *im_ctx, uint32_t hint, uint32_t purpose)
215 {
216    struct weekeyboard *wkb = data;
217
218    DBG("im_context = %p hint = %d purpose = %d", im_ctx, hint, purpose);
219
220    if (!wkb->context_changed)
221       return;
222
223    switch (purpose)
224      {
225       case WL_TEXT_INPUT_CONTENT_PURPOSE_DIGITS:
226       case WL_TEXT_INPUT_CONTENT_PURPOSE_NUMBER:
227            {
228               edje_object_signal_emit(wkb->edje_obj, "show,numeric", "");
229               break;
230            }
231       default:
232            {
233               edje_object_signal_emit(wkb->edje_obj, "show,alphanumeric", "");
234               break;
235            }
236      }
237
238    wkb->content_hint = hint;
239    wkb->content_purpose = purpose;
240
241    wkb->context_changed = EINA_FALSE;
242 }
243
244 static void
245 _wkb_im_ctx_invoke_action(void *data, struct wl_input_method_context *im_ctx, uint32_t button, uint32_t index)
246 {
247 #if 0
248    struct weekeyboard *wkb = data;
249
250    if (button != BTN_LEFT)
251       return;
252
253    _wkb_send_preedit_str(wkb, index);
254 #endif
255 }
256
257 static void
258 _wkb_im_ctx_commit_state(void *data, struct wl_input_method_context *im_ctx, uint32_t serial)
259 {
260    struct weekeyboard *wkb = data;
261
262    if (wkb->surrounding_text)
263       INF("Surrounding text updated: %s", wkb->surrounding_text);
264
265    wkb_ibus_input_context_set_serial(serial);
266 #if 0
267    /* FIXME */
268    wl_input_method_context_language(im_ctx, wkb_ibus_input_context_serial(), "en");//wkb->language);
269    wl_input_method_context_text_direction(im_ctx, wkb_ibus_input_context_serial(), WL_TEXT_INPUT_TEXT_DIRECTION_LTR);//wkb->text_direction);
270 #endif
271 }
272
273 static void
274 _wkb_im_ctx_preferred_language(void *data, struct wl_input_method_context *im_ctx, const char *language)
275 {
276 #if 0
277    struct weekeyboard *wkb = data;
278
279    if (language && wkb->language && !strcmp(language, wkb->language))
280       return;
281
282    if (wkb->language)
283      {
284         free(wkb->language);
285         wkb->language = NULL;
286      }
287
288    if (language)
289      {
290         wkb->language = strdup(language);
291         INF("Language changed, new: '%s'", language);
292      }
293 #endif
294 }
295
296 static const struct wl_input_method_context_listener wkb_im_context_listener = {
297      _wkb_im_ctx_surrounding_text,
298      _wkb_im_ctx_reset,
299      _wkb_im_ctx_content_type,
300      _wkb_im_ctx_invoke_action,
301      _wkb_im_ctx_commit_state,
302      _wkb_im_ctx_preferred_language,
303 };
304
305 static void
306 _wkb_im_activate(void *data, struct wl_input_method *input_method, struct wl_input_method_context *im_ctx)
307 {
308    struct weekeyboard *wkb = data;
309
310    DBG("Activate");
311
312    if (wkb->im_ctx)
313       wl_input_method_context_destroy(wkb->im_ctx);
314
315    if (wkb->preedit_str)
316       free(wkb->preedit_str);
317
318    wkb->preedit_str = strdup("");
319    wkb->content_hint = WL_TEXT_INPUT_CONTENT_HINT_NONE;
320    wkb->content_purpose = WL_TEXT_INPUT_CONTENT_PURPOSE_NORMAL;
321
322    free(wkb->language);
323    wkb->language = NULL;
324
325    free(wkb->surrounding_text);
326    wkb->surrounding_text = NULL;
327
328    wkb_ibus_input_context_set_serial(0);
329
330    wkb->im_ctx = im_ctx;
331    wl_input_method_context_add_listener(im_ctx, &wkb_im_context_listener, wkb);
332    wkb_ibus_input_context_create(im_ctx);
333
334 #if 0
335    struct wl_array modifiers_map;
336    wl_array_init(&modifiers_map);
337
338    keysym_modifiers_add(&modifiers_map, "Shift");
339    keysym_modifiers_add(&modifiers_map, "Control");
340    keysym_modifiers_add(&modifiers_map, "Mod1");
341
342    wl_input_method_context_modifiers_map(im_ctx, &modifiers_map);
343
344    wkb->keysym.shift_mask = keysym_modifiers_get_mask(&modifiers_map, "Shift");
345
346    wl_array_release(&modifiers_map);
347    */
348
349    /* FIXME */
350    wl_input_method_context_language(im_ctx, wkb_ibus_input_context_serial(), "en");//wkb->language);
351    wl_input_method_context_text_direction(im_ctx, wkb_ibus_input_context_serial(), WL_TEXT_INPUT_TEXT_DIRECTION_LTR);//wkb->text_direction);
352 #endif
353    wkb->context_changed = EINA_TRUE;
354    evas_object_show(wkb->edje_obj);
355 }
356
357 static void
358 _wkb_im_deactivate(void *data, struct wl_input_method *input_method, struct wl_input_method_context *im_ctx)
359 {
360    struct weekeyboard *wkb = data;
361
362    DBG("Deactivate");
363
364    wkb_ibus_input_context_destroy();
365
366    if (wkb->im_ctx)
367      {
368         wl_input_method_context_destroy(wkb->im_ctx);
369         wkb->im_ctx = NULL;
370      }
371
372    evas_object_hide(wkb->edje_obj);
373 }
374
375 static const struct wl_input_method_listener wkb_im_listener = {
376      _wkb_im_activate,
377      _wkb_im_deactivate
378 };
379
380
381 static Eina_Bool
382 _wkb_ui_setup(struct weekeyboard *wkb)
383 {
384    char path[PATH_MAX];
385    Evas *evas;
386    Evas_Coord w, h;
387    char *ignore_keys;
388
389    ecore_evas_alpha_set(wkb->ee, EINA_TRUE);
390    ecore_evas_title_set(wkb->ee, "Weekeyboard");
391
392    evas = ecore_evas_get(wkb->ee);
393    wkb->edje_obj = edje_object_add(evas);
394    /*ecore_evas_object_associate(wkb->ee, edje_obj, ECORE_EVAS_OBJECT_ASSOCIATE_BASE);*/
395
396    /* Check which theme we should use according to the screen width */
397    ecore_wl_screen_size_get(&w, &h);
398    if (w >= 720)
399       w = 720;
400    else
401       w = 600;
402
403    sprintf(path, PKGDATADIR"/default_%d.edj", w);
404    DBG("Loading edje file: '%s'", path);
405
406    if (!edje_object_file_set(wkb->edje_obj, path, "main"))
407      {
408         int err = edje_object_load_error_get(wkb->edje_obj);
409         ERR("Unable to load the edje file: '%s'", edje_load_error_str(err));
410         return EINA_FALSE;
411      }
412
413    edje_object_size_min_get(wkb->edje_obj, &w, &h);
414    if (w == 0 || h == 0)
415      {
416         edje_object_size_min_restricted_calc(wkb->edje_obj, &w, &h, w, h);
417         if (w == 0 || h == 0)
418            edje_object_parts_extends_calc(wkb->edje_obj, NULL, NULL, &w, &h);
419      }
420
421    ecore_evas_move_resize(wkb->ee, 0, 0, w, h);
422    evas_object_move(wkb->edje_obj, 0, 0);
423    evas_object_resize(wkb->edje_obj, w, h);
424    evas_object_size_hint_min_set(wkb->edje_obj, w, h);
425    evas_object_size_hint_max_set(wkb->edje_obj, w, h);
426
427    edje_object_signal_callback_add(wkb->edje_obj, "key_down", "*", _cb_wkb_on_key_down, wkb);
428    ecore_evas_callback_delete_request_set(wkb->ee, _cb_wkb_delete_request);
429
430    /*
431     * The keyboard surface is bigger than it appears so that we can show the
432     * key pressed animation without requiring the use of subsurfaces. Here we
433     * resize the input region of the surface to match the keyboard background
434     * image, so that we can pass mouse events to the surfaces that may be
435     * located below the keyboard.
436     */
437    if (wkb->win)
438      {
439         int x, y, w, h;
440         struct wl_region *input = wl_compositor_create_region(wkb->win->display->wl.compositor);
441
442         edje_object_part_geometry_get(wkb->edje_obj, "background", &x, &y, &w, &h);
443         wl_region_add(input, x, y, w, h);
444         wl_surface_set_input_region(wkb->surface, input);
445         wl_region_destroy(input);
446      }
447
448    /* special keys */
449    ignore_keys = edje_file_data_get(path, "ignore-keys");
450    if (!ignore_keys)
451      {
452         ERR("Special keys file not found in: '%s'", path);
453         goto end;
454      }
455
456    DBG("Got ignore keys: '%s'", ignore_keys);
457    wkb->ignore_keys = eina_str_split(ignore_keys, "\n", 0);
458    free(ignore_keys);
459
460 end:
461    ecore_evas_show(wkb->ee);
462    return EINA_TRUE;
463 }
464
465 static void
466 _wkb_setup(struct weekeyboard *wkb)
467 {
468    struct wl_list *globals;
469    struct wl_registry *registry;
470    Ecore_Wl_Global *global;
471
472    struct wl_input_panel_surface *ips;
473
474    globals = ecore_wl_globals_get();
475    registry = ecore_wl_registry_get();
476    wl_list_for_each(global, globals, link)
477      {
478         if (strcmp(global->interface, "wl_input_panel") == 0)
479            wkb->ip = wl_registry_bind(registry, global->id, &wl_input_panel_interface, 1);
480         else if (strcmp(global->interface, "wl_input_method") == 0)
481            wkb->im = wl_registry_bind(registry, global->id, &wl_input_method_interface, 1);
482         else if (strcmp(global->interface, "wl_output") == 0)
483            wkb->output = wl_registry_bind(registry, global->id, &wl_output_interface, 1);
484      }
485
486    /* Set input panel surface */
487    DBG("Setting up input panel");
488    wkb->win = ecore_evas_wayland_window_get(wkb->ee);
489    ecore_wl_window_type_set(wkb->win, ECORE_WL_WINDOW_TYPE_NONE);
490    wkb->surface = ecore_wl_window_surface_create(wkb->win);
491    ips = wl_input_panel_get_input_panel_surface(wkb->ip, wkb->surface);
492    wl_input_panel_surface_set_toplevel(ips, wkb->output, WL_INPUT_PANEL_SURFACE_POSITION_CENTER_BOTTOM);
493
494    /* Input method listener */
495    DBG("Adding wl_input_method listener");
496    wl_input_method_add_listener(wkb->im, &wkb_im_listener, wkb);
497 }
498
499 static void
500 _wkb_free(struct weekeyboard *wkb)
501 {
502    if (wkb->im_ctx)
503       wl_input_method_context_destroy(wkb->im_ctx);
504
505    if (wkb->edje_obj)
506       evas_object_del(wkb->edje_obj);
507
508    if (wkb->ignore_keys)
509      {
510         free(*wkb->ignore_keys);
511         free(wkb->ignore_keys);
512      }
513
514    free(wkb->preedit_str);
515    free(wkb->surrounding_text);
516 }
517
518 static Eina_Bool
519 _wkb_check_evas_engine(struct weekeyboard *wkb)
520 {
521    Eina_Bool ret = EINA_FALSE;
522    char *env = getenv("ECORE_EVAS_ENGINE");
523
524    if (!env)
525      {
526         if (ecore_evas_engine_type_supported_get(ECORE_EVAS_ENGINE_WAYLAND_SHM))
527            env = "wayland_shm";
528         else if (ecore_evas_engine_type_supported_get(ECORE_EVAS_ENGINE_WAYLAND_EGL))
529            env = "wayland_egl";
530         else
531           {
532              ERR("ERROR: Ecore_Evas does must be compiled with support for Wayland engines");
533              goto err;
534           }
535      }
536    else if (strcmp(env, "wayland_shm") != 0 && strcmp(env, "wayland_egl") != 0)
537      {
538         ERR("ERROR: ECORE_EVAS_ENGINE must be set to either 'wayland_shm' or 'wayland_egl'");
539         goto err;
540      }
541
542    wkb->ee_engine = env;
543    ret = EINA_TRUE;
544
545 err:
546    return ret;
547 }
548
549 static Eina_Bool
550 _wkb_check_ibus_connection(void *data)
551 {
552    static int tries = 0;
553
554    if (tries++ > 5)
555      {
556         CRITICAL("Unable to establish connection to IBus.");
557         return ECORE_CALLBACK_DONE;
558      }
559
560    return !wkb_ibus_is_connected();
561 }
562
563 int
564 main(int argc EINA_UNUSED, char **argv EINA_UNUSED)
565 {
566    struct weekeyboard wkb = {0};
567    int ret = EXIT_FAILURE;
568
569    if (!wkb_log_init("weekeyboard"))
570       return ret;
571
572    if (!ecore_evas_init())
573       goto ee_err;
574
575    if (!edje_init())
576       goto edj_err;
577
578    if (!_wkb_check_evas_engine(&wkb))
579       goto engine_err;
580
581    DBG("Selected engine: '%s'", wkb.ee_engine);
582    wkb.ee = ecore_evas_new(wkb.ee_engine, 0, 0, 1, 1, "frame=0");
583
584    if (!wkb.ee)
585      {
586         ERR("ERROR: Unable to create Ecore_Evas object");
587         goto edj_err;
588      }
589
590    _wkb_setup(&wkb);
591
592    wkb_ibus_init();
593
594    if (!_wkb_ui_setup(&wkb))
595       goto end;
596
597    wkb_ibus_connect();
598    ecore_timer_add(1, _wkb_check_ibus_connection, NULL);
599    ecore_main_loop_begin();
600
601    ret = EXIT_SUCCESS;
602
603 end:
604    _wkb_free(&wkb);
605    ecore_evas_free(wkb.ee);
606
607 engine_err:
608    edje_shutdown();
609
610 edj_err:
611    ecore_evas_shutdown();
612
613 ee_err:
614    wkb_log_shutdown();
615
616    return ret;
617 }