diff options
author | fujiwarat <takao.fujiwara1@gmail.com> | 2016-06-21 18:10:21 +0900 |
---|---|---|
committer | fujiwarat <takao.fujiwara1@gmail.com> | 2016-06-21 18:10:21 +0900 |
commit | 160d3c975af91eea6b8271b757be769b8ceef98d (patch) | |
tree | 8ea7728f7076896e927186eb12390ab7db0f24f4 | |
parent | a598ae29223d1ca25e76bf7d7de9703f63ea337e (diff) | |
download | ibus-160d3c975af91eea6b8271b757be769b8ceef98d.tar.gz |
engine: Implement Emoji typing with XKB engines
Now Ctrl+Shift+e can convert an Emoji annotation to the Emoji characters
likes Ctrl+Shift+u.
The annotations are described as "Keywords" in the Unicode Emoji Data:
http://www.unicode.org/emoji/charts/emoji-list.html
'emoji-parser' compiles 'emoji-list.html' and generates 'emoji.dict'
Review URL: https://codereview.appspot.com/295610043
-rw-r--r-- | configure.ac | 16 | ||||
-rw-r--r-- | src/Makefile.am | 31 | ||||
-rw-r--r-- | src/emoji-parser.c | 217 | ||||
-rw-r--r-- | src/ibusenginesimple.c | 443 | ||||
-rw-r--r-- | src/ibusutil.c | 193 | ||||
-rw-r--r-- | src/ibusutil.h | 31 |
6 files changed, 890 insertions, 41 deletions
diff --git a/configure.ac b/configure.ac index 1e1f5dd3..3128ef98 100644 --- a/configure.ac +++ b/configure.ac @@ -599,6 +599,21 @@ if test x"$enable_libnotify" = x"yes"; then enable_libnotify="yes (enabled, use --disable-libnotify to disable)" fi +# --disable-emoji-dict option. +AC_ARG_ENABLE(emoji-dict, + AS_HELP_STRING([--disable-emoji-dict], + [Do not build Emoji dict files]), + [enable_emoji_dict=$enableval], + [enable_emoji_dict=yes] +) +AM_CONDITIONAL([ENABLE_EMOJI_DICT], [test x"$enable_emoji_dict" = x"yes"]) +if test x"$enable_emoji_dict" = x"yes"; then + PKG_CHECK_MODULES(LIBXML2, [ + libxml-2.0 + ]) + enable_emoji_dict="yes (enabled, use --disable-emoji-dict to disable)" +fi + # Check iso-codes. PKG_CHECK_MODULES(ISOCODES, [ iso-codes @@ -682,6 +697,7 @@ Build options: Panel icon "$IBUS_ICON_KEYBOARD" Enable surrounding-text $enable_surrounding_text Enable libnotify $enable_libnotify + Enable Emoji dict $enable_emoji_dict Run test cases $enable_tests ]) diff --git a/src/Makefile.am b/src/Makefile.am index adaebe98..a33b67dd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -231,7 +231,38 @@ ibusmarshalers.c: ibusmarshalers.h ibusmarshalers.list $(GLIB_GENMARSHAL) --prefix=_ibus_marshal $(srcdir)/ibusmarshalers.list --body --internal) > $@.tmp && \ mv $@.tmp $@ +if ENABLE_EMOJI_DICT +AM_CPPFLAGS += -DENABLE_EMOJI_DICT + +dictdir = $(pkgdatadir)/dicts +dict_DATA = emoji.dict + +noinst_PROGRAMS = emoji-parser + +emoji.dict: emoji-parser emoji-list.html + $(builddir)/emoji-parser emoji-list.html $@ + +emoji_parser_SOURCES = \ + emoji-parser.c \ + $(NULL) +emoji_parser_CFLAGS = \ + $(GLIB2_CFLAGS) \ + $(LIBXML2_CFLAGS) \ + $(NULL) +emoji_parser_LDADD = \ + $(GLIB2_LIBS) \ + $(LIBXML2_LIBS) \ + $(libibus) \ + $(NULL) + +CLEANFILES += \ + $(dict_DATA) \ + $(NULL) +endif + EXTRA_DIST = \ + emoji-list.html \ + emoji-parser.c \ ibusversion.h.in \ ibusmarshalers.list \ ibusenumtypes.h.template \ diff --git a/src/emoji-parser.c b/src/emoji-parser.c new file mode 100644 index 00000000..cf92feea --- /dev/null +++ b/src/emoji-parser.c @@ -0,0 +1,217 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* vim:set et sts=4: */ +/* ibus - The Input Bus + * Copyright (C) 2016 Takao Fujiwara <takao.fujiwara1@gmail.com> + * Copyright (C) 2016 Red Hat, Inc. + * + * 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 2.1 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +/* Convert http://www.unicode.org/emoji/charts/emoji-list.html + * to the dictionary file which look up the Emoji from the annotation. + */ + +#include <glib.h> +#include <libxml/HTMLparser.h> +#include <libgen.h> + +#include "ibusutil.h" + +typedef struct _EmojiData EmojiData; +struct _EmojiData { + gchar *class; + gchar *emoji; + GSList *annotates; + GSList *prev_annotates; + GHashTable *dict; +}; + +const gchar *progname; + +static gboolean parse_node (xmlNode *node, + gboolean is_child, + const gchar *prop_name, + EmojiData *data); + +static void +usage (void) +{ + g_print ("%s emoji-list.html emoji.dict\n", progname); +} + +static void +reset_emoji_element (EmojiData *data) +{ + g_clear_pointer (&data->class, g_free); + g_clear_pointer (&data->emoji, g_free); + if (data->annotates) { + g_slist_free_full (data->prev_annotates, g_free); + data->prev_annotates = data->annotates; + data->annotates = NULL; + } +} + +static void +free_dict_words (gpointer list) +{ + g_slist_free_full (list, g_free); +} + +static gboolean +parse_attr (xmlAttr *attr, + EmojiData *data) +{ + if (g_strcmp0 ((const gchar *) attr->name, "class") == 0 && attr->children) + parse_node (attr->children, TRUE, (const gchar *) attr->name, data); + if (g_strcmp0 ((const gchar *) attr->name, "target") == 0 && attr->children) + parse_node (attr->children, TRUE, (const gchar *) attr->name, data); + if (attr->next) + parse_attr (attr->next, data); + return TRUE; +} + +static gboolean +parse_node (xmlNode *node, + gboolean is_child, + const gchar *prop_name, + EmojiData *data) +{ + if (g_strcmp0 ((const gchar *) node->name, "tr") == 0) { + GSList *annotates = data->annotates; + while (annotates) { + GSList *emojis = g_hash_table_lookup (data->dict, annotates->data); + if (emojis) { + emojis = g_slist_copy_deep (emojis, (GCopyFunc) g_strdup, NULL); + } + emojis = g_slist_append (emojis, g_strdup (data->emoji)); + g_hash_table_replace (data->dict, + g_strdup (annotates->data), + emojis); + annotates = annotates->next; + } + reset_emoji_element (data); + } + /* if node->name is "text" and is_child is FALSE, + * it's '\n' or Space between <td> and <td>. + */ + if (g_strcmp0 ((const gchar *) node->name, "text") == 0 && is_child) { + /* Get "chars" in <td class="chars"> */ + if (g_strcmp0 (prop_name, "class") == 0) { + if (g_strcmp0 (data->class, (const gchar *) node->content) != 0) { + g_clear_pointer (&data->class, g_free); + data->class = g_strdup ((const gchar *) node->content); + } + } + /* Get "annotate" in <td class="name"><a target="annotate"> */ + if (g_strcmp0 (prop_name, "target") == 0 && + g_strcmp0 (data->class, "name") == 0) { + g_clear_pointer (&data->class, g_free); + data->class = g_strdup ((const gchar *) node->content); + } + /* Get "emoji" in <td class="chars">emoji</td> */ + if (g_strcmp0 (prop_name, "td") == 0 && + g_strcmp0 (data->class, "chars") == 0) { + data->emoji = g_strdup ((const gchar *) node->content); + } + /* We ignore "NAME" for <td class="name">NAME</td> but + * takes "ANNOTATE" for + * <td class="name"><a target="annotate">ANNOTATE</a></td> + */ + if (g_strcmp0 (prop_name, "td") == 0 && + g_strcmp0 (data->class, "name") == 0) { + g_slist_free_full (data->annotates, g_free); + data->annotates = NULL; + } + /* Get "ANNOTATE" in + * <td class="name"><a target="annotate">ANNOTATE</a></td> + */ + if (g_strcmp0 (prop_name, "a") == 0 && + g_strcmp0 (data->class, "annotate") == 0) { + data->annotates = + g_slist_append (data->annotates, + g_strdup ((const gchar *) node->content)); + } + } + /* Get "foo" in <td class="foo"> */ + if (g_strcmp0 ((const gchar *) node->name, "td") == 0 && + node->properties != NULL) { + parse_attr (node->properties, data); + } + /* Get "foo" in <a target="foo"> */ + if (g_strcmp0 ((const gchar *) node->name, "a") == 0 && + node->properties != NULL) { + parse_attr (node->properties, data); + } + if (node->children) { + parse_node (node->children, TRUE, (const gchar *) node->name, data); + } else { + /* If annotate is NULL likes <td class="name"></td>, + * the previous emoji cell has the same annotate. + */ + if (g_strcmp0 ((const gchar *) node->name, "td") == 0 && + g_strcmp0 (data->class, "name") == 0) { + data->annotates = g_slist_copy_deep (data->prev_annotates, + (GCopyFunc) g_strdup, + NULL); + } + } + if (node->next) + parse_node (node->next, FALSE, (const gchar *) node->name, data); + + return TRUE; +} + +static GHashTable * +parse_html (const gchar *filename) +{ + xmlDoc *doc = htmlParseFile (filename, "utf-8"); + EmojiData data = { 0, }; + + if (doc == NULL || doc->children == NULL) { + g_warning ("Parse Error in document type: %x", + doc ? doc->type : 0); + return FALSE; + } + + data.dict = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + free_dict_words); + parse_node (doc->children, TRUE, (const gchar *) doc->name, &data); + + reset_emoji_element (&data); + g_slist_free_full (data.prev_annotates, g_free); + + return data.dict; +} + +int +main (int argc, char *argv[]) +{ + GHashTable *dict; + progname = basename (argv[0]); + + if (argc < 3) { + usage (); + return -1; + } + + dict = parse_html (argv[1]); + ibus_emoji_dict_save (argv[2], dict); + g_hash_table_destroy (dict); + + return 0; +} diff --git a/src/ibusenginesimple.c b/src/ibusenginesimple.c index 1b688b09..8efe5a92 100644 --- a/src/ibusenginesimple.c +++ b/src/ibusenginesimple.c @@ -3,7 +3,7 @@ /* ibus - The Input Bus * Copyright (C) 2014 Peng Huang <shawn.p.huang@gmail.com> * Copyright (C) 2015-2016 Takao Fujiwara <takao.fujiwara1@gmail.com> - * Copyright (C) 2014 Red Hat, Inc. + * Copyright (C) 2014-2016 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -31,6 +31,7 @@ #include "ibuskeys.h" #include "ibuskeysyms.h" +#include "ibusutil.h" /* This file contains the table of the compose sequences, * static const guint16 gtk_compose_seqs_compact[] = {} @@ -42,16 +43,27 @@ #include <stdlib.h> #define X11_DATADIR X11_DATA_PREFIX "/share/X11/locale" +#define EMOJI_SOURCE_LEN 100 #define IBUS_ENGINE_SIMPLE_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_ENGINE_SIMPLE, IBusEngineSimplePrivate)) -struct _IBusEngineSimplePrivate { - guint16 compose_buffer[IBUS_MAX_COMPOSE_LEN + 1]; - gunichar tentative_match; - gint tentative_match_len; +typedef struct { + GHashTable *dict; + int max_seq_len; +} IBusEngineDict; - guint in_hex_sequence : 1; - guint modifiers_dropped : 1; +struct _IBusEngineSimplePrivate { + guint16 compose_buffer[EMOJI_SOURCE_LEN]; + gunichar tentative_match; + gchar *tentative_emoji; + gint tentative_match_len; + + guint in_hex_sequence : 1; + guint in_emoji_sequence : 1; + guint modifiers_dropped : 1; + IBusEngineDict *emoji_dict; + IBusLookupTable *lookup_table; + gboolean lookup_table_visible; }; /* From the values below, the value 30 means the number of different first keysyms @@ -97,6 +109,8 @@ static gboolean ibus_engine_simple_process_key_event guint modifiers); static void ibus_engine_simple_commit_char (IBusEngineSimple *simple, gunichar ch); +static void ibus_engine_simple_commit_str (IBusEngineSimple *simple, + const gchar *str); static void ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple); @@ -128,6 +142,18 @@ ibus_engine_simple_init (IBusEngineSimple *simple) static void ibus_engine_simple_destroy (IBusEngineSimple *simple) { + IBusEngineSimplePrivate *priv = simple->priv; + + if (priv->emoji_dict) { + if (priv->emoji_dict->dict) + g_clear_pointer (&priv->emoji_dict->dict, g_hash_table_destroy); + g_slice_free (IBusEngineDict, priv->emoji_dict); + priv->emoji_dict = NULL; + } + + g_clear_pointer (&priv->lookup_table, g_object_unref); + g_clear_pointer (&priv->tentative_emoji, g_free); + IBUS_OBJECT_CLASS(ibus_engine_simple_parent_class)->destroy ( IBUS_OBJECT (simple)); } @@ -146,6 +172,11 @@ ibus_engine_simple_reset (IBusEngine *engine) priv->tentative_match_len = 0; ibus_engine_hide_preedit_text ((IBusEngine *)simple); } + if (priv->tentative_emoji || priv->in_emoji_sequence) { + priv->in_emoji_sequence = FALSE; + g_clear_pointer (&priv->tentative_emoji, g_free); + ibus_engine_hide_preedit_text ((IBusEngine *)simple); + } } static void @@ -162,23 +193,60 @@ ibus_engine_simple_commit_char (IBusEngineSimple *simple, priv->tentative_match_len = 0; ibus_engine_simple_update_preedit_text (simple); } + if (priv->tentative_emoji || priv->in_emoji_sequence) { + priv->in_emoji_sequence = FALSE; + g_clear_pointer (&priv->tentative_emoji, g_free); + ibus_engine_simple_update_preedit_text (simple); + } ibus_engine_commit_text ((IBusEngine *)simple, ibus_text_new_from_unichar (ch)); } static void +ibus_engine_simple_commit_str (IBusEngineSimple *simple, + const gchar *str) +{ + IBusEngineSimplePrivate *priv = simple->priv; + gchar *backup_str; + + g_return_if_fail (str && *str); + + backup_str = g_strdup (str); + + if (priv->tentative_match || priv->in_hex_sequence) { + priv->in_hex_sequence = FALSE; + priv->tentative_match = 0; + priv->tentative_match_len = 0; + ibus_engine_simple_update_preedit_text (simple); + } + if (priv->tentative_emoji || priv->in_emoji_sequence) { + priv->in_emoji_sequence = FALSE; + g_clear_pointer (&priv->tentative_emoji, g_free); + ibus_engine_simple_update_preedit_text (simple); + } + + ibus_engine_commit_text ((IBusEngine *)simple, + ibus_text_new_from_string (backup_str)); + g_free (backup_str); +} + +static void ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple) { IBusEngineSimplePrivate *priv = simple->priv; - gunichar outbuf[IBUS_MAX_COMPOSE_LEN + 2]; + gunichar outbuf[EMOJI_SOURCE_LEN + 1]; int len = 0; - if (priv->in_hex_sequence) { + if (priv->in_hex_sequence || priv->in_emoji_sequence) { int hexchars = 0; - outbuf[0] = L'u'; + if (priv->in_hex_sequence) + outbuf[0] = L'u'; + else + outbuf[0] = L'@'; + len = 1; while (priv->compose_buffer[hexchars] != 0) { @@ -187,10 +255,22 @@ ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple) ++len; ++hexchars; } - g_assert (len <= IBUS_MAX_COMPOSE_LEN + 1); + + if (priv->in_hex_sequence) + g_assert (len <= IBUS_MAX_COMPOSE_LEN + 1); + else + g_assert (len <= EMOJI_SOURCE_LEN + 1); } - else if (priv->tentative_match) + else if (priv->tentative_match) { outbuf[len++] = priv->tentative_match; + } else if (priv->tentative_emoji && *priv->tentative_emoji) { + IBusText *text = ibus_text_new_from_string (priv->tentative_emoji); + len = strlen (priv->tentative_emoji); + ibus_text_append_attribute (text, + IBUS_ATTR_TYPE_UNDERLINE, IBUS_ATTR_UNDERLINE_SINGLE, 0, len); + ibus_engine_update_preedit_text ((IBusEngine *)simple, text, len, TRUE); + return; + } outbuf[len] = L'\0'; if (len == 0) { @@ -277,6 +357,104 @@ check_hex (IBusEngineSimple *simple, return TRUE; } +static IBusEngineDict * +load_emoji_dict () +{ + IBusEngineDict *emoji_dict; + GList *keys; + int max_length = 0; + + emoji_dict = g_slice_new0 (IBusEngineDict); + emoji_dict->dict = ibus_emoji_dict_load (IBUS_DATA_DIR "/dicts/emoji.dict"); + if (!emoji_dict->dict) + return emoji_dict; + + keys = g_hash_table_get_keys (emoji_dict->dict); + for (; keys; keys = keys->next) { + int length = strlen (keys->data); + if (max_length < length) + max_length = length; + } + emoji_dict->max_seq_len = max_length; + + return emoji_dict; +} + +static gboolean +check_emoji_table (IBusEngineSimple *simple, + gint n_compose, + gint index) +{ + IBusEngineSimplePrivate *priv = simple->priv; + IBusEngineDict *emoji_dict = priv->emoji_dict; + GString *str = NULL; + gint i; + gchar buf[7]; + GSList *words = NULL; + + g_assert (IBUS_IS_ENGINE_SIMPLE (simple)); + + if (priv->lookup_table == NULL) { + priv->lookup_table = ibus_lookup_table_new (10, 0, TRUE, TRUE); + g_object_ref_sink (priv->lookup_table); + } + if (emoji_dict == NULL) + emoji_dict = priv->emoji_dict = load_emoji_dict (simple); + + if (emoji_dict == NULL || emoji_dict->dict == NULL) + return FALSE; + + if (n_compose > emoji_dict->max_seq_len) + return FALSE; + + str = g_string_new (NULL); + priv->lookup_table_visible = FALSE; + + i = 0; + while (i < n_compose) { + gunichar ch; + + ch = ibus_keyval_to_unicode (priv->compose_buffer[i]); + + if (ch == 0) + return FALSE; + + if (!g_unichar_isprint (ch)) + return FALSE; + + buf[g_unichar_to_utf8 (ch, buf)] = '\0'; + + g_string_append (str, buf); + + ++i; + } + + if (str->str) { + words = g_hash_table_lookup (emoji_dict->dict, str->str); + } + g_string_free (str, TRUE); + + if (words != NULL) { + int i = 0; + ibus_lookup_table_clear (priv->lookup_table); + priv->lookup_table_visible = TRUE; + + while (words) { + if (i == index) { + g_clear_pointer (&priv->tentative_emoji, g_free); + priv->tentative_emoji = g_strdup (words->data); + } + IBusText *text = ibus_text_new_from_string (words->data); + ibus_lookup_table_append_candidate (priv->lookup_table, text); + words = words->next; + i++; + } + return TRUE; + } + + return FALSE; +} + static int compare_seq_index (const void *key, const void *value) { @@ -626,10 +804,10 @@ ibus_check_algorithmically (const guint16 *compose_buffer, static gboolean no_sequence_matches (IBusEngineSimple *simple, - gint n_compose, - guint keyval, - guint keycode, - guint modifiers) + gint n_compose, + guint keyval, + guint keycode, + guint modifiers) { IBusEngineSimplePrivate *priv = simple->priv; @@ -642,8 +820,7 @@ no_sequence_matches (IBusEngineSimple *simple, gint len = priv->tentative_match_len; int i; - ibus_engine_simple_commit_char (simple, - priv->tentative_match); + ibus_engine_simple_commit_char (simple, priv->tentative_match); priv->compose_buffer[0] = 0; for (i=0; i < n_compose - len - 1; i++) { @@ -655,8 +832,11 @@ no_sequence_matches (IBusEngineSimple *simple, return ibus_engine_simple_process_key_event ( (IBusEngine *)simple, keyval, keycode, modifiers); - } - else { + } else if (priv->tentative_emoji && *priv->tentative_emoji) { + ibus_engine_simple_commit_str (simple, priv->tentative_emoji); + g_clear_pointer (&priv->tentative_emoji, g_free); + priv->compose_buffer[0] = 0; + } else { priv->compose_buffer[0] = 0; if (n_compose > 1) { /* Invalid sequence */ @@ -676,6 +856,7 @@ no_sequence_matches (IBusEngineSimple *simple, else return FALSE; } + return FALSE; } static gboolean @@ -687,6 +868,39 @@ is_hex_keyval (guint keyval) } static gboolean +is_graph_keyval (guint keyval) +{ + gunichar ch = ibus_keyval_to_unicode (keyval); + + return g_unichar_isgraph (ch); +} + +static void +ibus_engine_simple_update_lookup_and_aux_table (IBusEngineSimple *simple) +{ + IBusEngineSimplePrivate *priv; + guint index, candidates; + gchar *aux_label = NULL; + IBusText *text = NULL; + + g_return_if_fail (IBUS_IS_ENGINE_SIMPLE (simple)); + + priv = simple->priv; + index = ibus_lookup_table_get_cursor_pos (priv->lookup_table) + 1; + candidates = ibus_lookup_table_get_number_of_candidates(priv->lookup_table); + aux_label = g_strdup_printf ("(%u / %u)", index, candidates); + text = ibus_text_new_from_string (aux_label); + g_free (aux_label); + + ibus_engine_update_auxiliary_text (IBUS_ENGINE (simple), + text, + priv->lookup_table_visible); + ibus_engine_update_lookup_table (IBUS_ENGINE (simple), + priv->lookup_table, + priv->lookup_table_visible); +} + +static gboolean ibus_engine_simple_process_key_event (IBusEngine *engine, guint keyval, guint keycode, @@ -697,10 +911,13 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, gint n_compose = 0; gboolean have_hex_mods; gboolean is_hex_start; + gboolean is_emoji_start = FALSE; gboolean is_hex_end; + gboolean is_space; gboolean is_backspace; gboolean is_escape; guint hex_keyval; + guint printable_keyval; gint i; gboolean compose_finish; gunichar output_char; @@ -714,17 +931,16 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, keyval == IBUS_KEY_Shift_L || keyval == IBUS_KEY_Shift_R)) { if (priv->tentative_match && g_unichar_validate (priv->tentative_match)) { - ibus_engine_simple_commit_char (simple, - priv->tentative_match); - } - else if (n_compose == 0) { + ibus_engine_simple_commit_char (simple, priv->tentative_match); + } else if (n_compose == 0) { priv->modifiers_dropped = TRUE; - } - else { + } else { /* invalid hex sequence */ /* FIXME beep_window (event->window); */ priv->tentative_match = 0; + g_clear_pointer (&priv->tentative_emoji, g_free); priv->in_hex_sequence = FALSE; + priv->in_emoji_sequence = FALSE; priv->compose_buffer[0] = 0; ibus_engine_simple_update_preedit_text (simple); @@ -732,6 +948,26 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, return TRUE; } + /* Handle Shift + Space */ + else if (priv->in_emoji_sequence && + (keyval == IBUS_KEY_Control_L || keyval == IBUS_KEY_Control_R)) { + if (priv->tentative_emoji && *priv->tentative_emoji) { + ibus_engine_simple_commit_str (simple, priv->tentative_emoji); + g_clear_pointer (&priv->tentative_emoji, g_free); + } else if (n_compose == 0) { + priv->modifiers_dropped = TRUE; + } else { + /* invalid hex sequence */ + /* FIXME beep_window (event->window); */ + priv->tentative_match = 0; + g_clear_pointer (&priv->tentative_emoji, g_free); + priv->in_hex_sequence = FALSE; + priv->in_emoji_sequence = FALSE; + priv->compose_buffer[0] = 0; + + ibus_engine_simple_update_preedit_text (simple); + } + } else return FALSE; } @@ -741,25 +977,33 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, if (keyval == ibus_compose_ignore[i]) return FALSE; - if (priv->in_hex_sequence && priv->modifiers_dropped) + if ((priv->in_hex_sequence || priv->in_emoji_sequence) + && priv->modifiers_dropped) { have_hex_mods = TRUE; - else + } else { have_hex_mods = (modifiers & (HEX_MOD_MASK)) == HEX_MOD_MASK; + } is_hex_start = keyval == IBUS_KEY_U; +#ifdef ENABLE_EMOJI_DICT + is_emoji_start = keyval == IBUS_KEY_E; +#endif is_hex_end = (keyval == IBUS_KEY_space || keyval == IBUS_KEY_KP_Space || keyval == IBUS_KEY_Return || keyval == IBUS_KEY_ISO_Enter || keyval == IBUS_KEY_KP_Enter); + is_space = (keyval == IBUS_KEY_space || keyval == IBUS_KEY_KP_Space); is_backspace = keyval == IBUS_KEY_BackSpace; is_escape = keyval == IBUS_KEY_Escape; hex_keyval = is_hex_keyval (keyval) ? keyval : 0; + printable_keyval = is_graph_keyval (keyval) ? keyval : 0; /* gtkimcontextsimple causes a buffer overflow in priv->compose_buffer. * Add the check code here. */ - if (n_compose >= IBUS_MAX_COMPOSE_LEN) { + if ((n_compose >= IBUS_MAX_COMPOSE_LEN && priv->in_hex_sequence) || + (n_compose >= EMOJI_SOURCE_LEN && priv->in_emoji_sequence)) { if (is_backspace) { priv->compose_buffer[--n_compose] = 0; } @@ -767,7 +1011,9 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, /* invalid hex sequence */ // beep_window (event->window); priv->tentative_match = 0; + g_clear_pointer (&priv->tentative_emoji, g_free); priv->in_hex_sequence = FALSE; + priv->in_emoji_sequence = FALSE; priv->compose_buffer[0] = 0; } else if (is_escape) { @@ -789,12 +1035,16 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, * ISO_Level3_Switch. */ if (!have_hex_mods || - (n_compose > 0 && !priv->in_hex_sequence) || - (n_compose == 0 && !priv->in_hex_sequence && !is_hex_start) || + (n_compose > 0 && !priv->in_hex_sequence && !priv->in_emoji_sequence) || + (n_compose == 0 && !priv->in_hex_sequence && !is_hex_start && + !priv->in_emoji_sequence && !is_emoji_start) || (priv->in_hex_sequence && !hex_keyval && - !is_hex_start && !is_hex_end && !is_escape && !is_backspace)) { + !is_hex_start && !is_hex_end && !is_escape && !is_backspace) || + (priv->in_emoji_sequence && !printable_keyval && + !is_emoji_start && !is_hex_end && !is_escape && !is_backspace)) { if (modifiers & (IBUS_MOD1_MASK | IBUS_CONTROL_MASK) || - (priv->in_hex_sequence && priv->modifiers_dropped && + ((priv->in_hex_sequence || priv->in_emoji_sequence) && + priv->modifiers_dropped && (keyval == IBUS_KEY_Return || keyval == IBUS_KEY_ISO_Enter || keyval == IBUS_KEY_KP_Enter))) { @@ -816,6 +1066,20 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, return TRUE; } + if (priv->in_emoji_sequence && have_hex_mods && is_backspace) { + if (n_compose > 0) { + n_compose--; + priv->compose_buffer[n_compose] = 0; + check_emoji_table (simple, n_compose, -1); + ibus_engine_simple_update_lookup_and_aux_table (simple); + } else { + priv->in_emoji_sequence = FALSE; + } + + ibus_engine_simple_update_preedit_text (simple); + + return TRUE; + } /* Check for hex sequence restart */ if (priv->in_hex_sequence && have_hex_mods && is_hex_start) { @@ -833,13 +1097,41 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, } } } + if (priv->in_emoji_sequence && have_hex_mods && is_emoji_start) { + if (priv->tentative_emoji && *priv->tentative_emoji) { + ibus_engine_simple_commit_str (simple, priv->tentative_emoji); + g_clear_pointer (&priv->tentative_emoji, g_free); + } + else { + if (n_compose > 0) { + g_clear_pointer (&priv->tentative_emoji, g_free); + priv->in_emoji_sequence = FALSE; + priv->compose_buffer[0] = 0; + } + } + } /* Check for hex sequence start */ if (!priv->in_hex_sequence && have_hex_mods && is_hex_start) { priv->compose_buffer[0] = 0; priv->in_hex_sequence = TRUE; + priv->in_emoji_sequence = FALSE; priv->modifiers_dropped = FALSE; priv->tentative_match = 0; + g_clear_pointer (&priv->tentative_emoji, g_free); + + // g_debug ("Start HEX MODE"); + + ibus_engine_simple_update_preedit_text (simple); + + return TRUE; + } else if (!priv->in_emoji_sequence && have_hex_mods && is_emoji_start) { + priv->compose_buffer[0] = 0; + priv->in_hex_sequence = FALSE; + priv->in_emoji_sequence = TRUE; + priv->modifiers_dropped = FALSE; + priv->tentative_match = 0; + g_clear_pointer (&priv->tentative_emoji, g_free); // g_debug ("Start HEX MODE"); @@ -864,9 +1156,20 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, // beep_window (event->window); return TRUE; } - } - else + } else if (priv->in_emoji_sequence) { + if (printable_keyval) { + priv->compose_buffer[n_compose++] = printable_keyval; + } + else if (is_space && (modifiers & IBUS_SHIFT_MASK)) { + priv->compose_buffer[n_compose++] = IBUS_KEY_space; + } + else if (is_escape) { + ibus_engine_simple_reset (engine); + return TRUE; + } + } else { priv->compose_buffer[n_compose++] = keyval; + } priv->compose_buffer[n_compose] = 0; @@ -880,8 +1183,7 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, ibus_engine_simple_commit_char (simple, priv->tentative_match); priv->compose_buffer[0] = 0; - } - else { + } else { // FIXME /* invalid hex sequence */ // beep_window (event->window); @@ -899,6 +1201,73 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, return TRUE; } } + else if (priv->in_emoji_sequence) { + if (have_hex_mods && n_compose > 0) { + gboolean update_lookup_table = FALSE; + + if (priv->lookup_table_visible) { + switch (keyval) { + case IBUS_KEY_space: + case IBUS_KEY_KP_Space: + if ((modifiers & IBUS_SHIFT_MASK) == 0) { + ibus_lookup_table_cursor_down (priv->lookup_table); + update_lookup_table = TRUE; + } + break; + case IBUS_KEY_Down: + ibus_lookup_table_cursor_down (priv->lookup_table); + update_lookup_table = TRUE; + break; + case IBUS_KEY_Up: + ibus_lookup_table_cursor_up (priv->lookup_table); + update_lookup_table = TRUE; + break; + case IBUS_KEY_Page_Down: + ibus_lookup_table_page_down (priv->lookup_table); + update_lookup_table = TRUE; + break; + case IBUS_KEY_Page_Up: + ibus_lookup_table_page_up (priv->lookup_table); + update_lookup_table = TRUE; + break; + default:; + } + } + + if (!update_lookup_table) { + if (is_hex_end && !is_space) { + if (priv->lookup_table) { + int index = (int) ibus_lookup_table_get_cursor_pos ( + priv->lookup_table); + check_emoji_table (simple, n_compose, index); + priv->lookup_table_visible = FALSE; + update_lookup_table = TRUE; + } + } + else if (check_emoji_table (simple, n_compose, -1)) { + update_lookup_table = TRUE; + } + } + + if (update_lookup_table) + ibus_engine_simple_update_lookup_and_aux_table (simple); + if (is_hex_end && !is_space) { + if (priv->tentative_emoji && *priv->tentative_emoji) { + ibus_engine_simple_commit_str (simple, + priv->tentative_emoji); + priv->compose_buffer[0] = 0; + } else { + g_clear_pointer (&priv->tentative_emoji, g_free); + priv->in_emoji_sequence = FALSE; + priv->compose_buffer[0] = 0; + } + } + + ibus_engine_simple_update_preedit_text (simple); + + return TRUE; + } + } else { GSList *list = global_tables; while (list) { diff --git a/src/ibusutil.c b/src/ibusutil.c index b9f3fdde..bfaa4f4b 100644 --- a/src/ibusutil.c +++ b/src/ibusutil.c @@ -2,8 +2,8 @@ /* vim:set et sts=4: */ /* bus - The Input Bus * Copyright (C) 2008-2015 Peng Huang <shawn.p.huang@gmail.com> - * Copyright (C) 2010-2015 Takao Fujiwara <takao.fujiwara1@gmail.com> - * Copyright (C) 2008-2015 Red Hat, Inc. + * Copyright (C) 2010-2016 Takao Fujiwara <takao.fujiwara1@gmail.com> + * Copyright (C) 2008-2016 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -34,6 +34,9 @@ #include <libintl.h> #endif +#define IBUS_DICT_MAGIC "IBusDict" +#define IBUS_DICT_VERSION (1) + /* gettext macro */ #define N_(t) t @@ -125,6 +128,74 @@ _load_lang() ibus_xml_free (node); } +static void +free_dict_words (gpointer list) +{ + g_slist_free_full (list, g_free); +} + +static void +variant_foreach_add_emoji (gchar *annotation, + GSList *emojis, + GVariantBuilder *builder) +{ + int i; + int length = (int) g_slist_length (emojis); + gchar **buff = g_new0 (gchar *, length); + GSList *l = emojis; + + for (i = 0; i < length; i++, l = l->next) + buff[i] = (gchar *) l->data; + + g_variant_builder_add (builder, + "{sv}", + annotation, + g_variant_new_strv ((const gchar * const *) buff, + length)); + g_free (buff); +} + +static GVariant * +ibus_emoji_dict_serialize (GHashTable *dict) +{ + GVariantBuilder builder; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + g_hash_table_foreach (dict, (GHFunc) variant_foreach_add_emoji, &builder); + return g_variant_builder_end (&builder); +} + +static GHashTable * +ibus_emoji_dict_deserialize (GVariant *variant) +{ + GHashTable *dict = NULL; + GVariantIter iter; + gchar *annotate = NULL; + GVariant *emojis_variant = NULL; + + dict = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + free_dict_words); + + g_variant_iter_init (&iter, variant); + while (g_variant_iter_loop (&iter, "{sv}", &annotate, &emojis_variant)) { + gsize i; + gsize length = 0; + const gchar **array = g_variant_get_strv (emojis_variant, &length); + GSList *emojis = NULL; + + for (i = 0; i < length; i++) { + emojis = g_slist_append (emojis, g_strdup (array[i])); + } + g_hash_table_insert (dict, annotate, emojis); + annotate = NULL; + g_clear_pointer (&emojis_variant, g_variant_unref); + } + + return dict; +} + const gchar * ibus_get_untranslated_language_name (const gchar *_locale) { @@ -171,3 +242,121 @@ ibus_g_variant_get_child_string (GVariant *variant, gsize index, char **str) g_free (*str); g_variant_get_child (variant, index, "s", str); } + +void +ibus_emoji_dict_save (const gchar *path, GHashTable *dict) +{ + GVariant *variant; + const gchar *header = IBUS_DICT_MAGIC; + const guint16 version = IBUS_DICT_VERSION; + const gchar *contents; + gsize length; + GError *error = NULL; + + variant = g_variant_new ("(sqv)", + header, + version, + ibus_emoji_dict_serialize (dict)); + + contents = g_variant_get_data (variant); + length = g_variant_get_size (variant); + + if (!g_file_set_contents (path, contents, length, &error)) { + g_warning ("Failed to save emoji dict %s: %s", path, error->message); + g_error_free (error); + } + + g_variant_unref (variant); +} + +GHashTable * +ibus_emoji_dict_load (const gchar *path) +{ + gchar *contents = NULL; + gsize length = 0; + GError *error = NULL; + GVariant *variant_table = NULL; + GVariant *variant = NULL; + const gchar *header = NULL; + guint16 version = 0; + GHashTable *retval = NULL; + + if (!g_file_test (path, G_FILE_TEST_EXISTS)) { + g_warning ("Emoji dict does not exist: %s", path); + goto out_load_cache; + } + + if (!g_file_get_contents (path, &contents, &length, &error)) { + g_warning ("Failed to get dict content %s: %s", path, error->message); + g_error_free (error); + goto out_load_cache; + } + + variant_table = g_variant_new_from_data (G_VARIANT_TYPE ("(sq)"), + contents, + length, + FALSE, + NULL, + NULL); + + if (variant_table == NULL) { + g_warning ("cache table is broken."); + goto out_load_cache; + } + + g_variant_get (variant_table, "(&sq)", &header, &version); + + if (g_strcmp0 (header, IBUS_DICT_MAGIC) != 0) { + g_warning ("cache is not IBusDict."); + goto out_load_cache; + } + + if (version != IBUS_DICT_VERSION) { + g_warning ("cache version is different: %u != %u", + version, IBUS_DICT_VERSION); + goto out_load_cache; + } + + version = 0; + header = NULL; + g_variant_unref (variant_table); + + variant_table = g_variant_new_from_data (G_VARIANT_TYPE ("(sqv)"), + contents, + length, + FALSE, + NULL, + NULL); + + if (variant_table == NULL) { + g_warning ("cache table is broken."); + goto out_load_cache; + } + + g_variant_get (variant_table, "(&sqv)", + NULL, + NULL, + &variant); + + if (variant == NULL) { + g_warning ("cache dict is broken."); + goto out_load_cache; + } + + retval = ibus_emoji_dict_deserialize (variant); + +out_load_cache: + if (variant) + g_variant_unref (variant); + if (variant_table) + g_variant_unref (variant_table); + + return retval; +} + +GSList * +ibus_emoji_dict_lookup (GHashTable *dict, + const gchar *annotation) +{ + return (GSList *) g_hash_table_lookup (dict, annotation); +} diff --git a/src/ibusutil.h b/src/ibusutil.h index 2c1360c7..e619b678 100644 --- a/src/ibusutil.h +++ b/src/ibusutil.h @@ -2,8 +2,8 @@ /* vim:set et sts=4: */ /* bus - The Input Bus * Copyright (C) 2008-2015 Peng Huang <shawn.p.huang@gmail.com> - * Copyright (C) 2010-2015 Takao Fujiwara <takao.fujiwara1@gmail.com> - * Copyright (C) 2008-2015 Red Hat, Inc. + * Copyright (C) 2010-2016 Takao Fujiwara <takao.fujiwara1@gmail.com> + * Copyright (C) 2008-2016 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -53,4 +53,31 @@ const gchar * ibus_get_untranslated_language_name */ const gchar * ibus_get_language_name (const gchar *_locale); +/** + * ibus_emoji_dict_save: + * @path: A path of the saved dictionary file. + * @dict: (element-type utf8 gpointer) (transfer none): An Emoji dictionary + * + * Save the Emoji dictionary to the cache file. + */ +void ibus_emoji_dict_save (const gchar *path, + GHashTable *dict); +/** + * ibus_emoji_dict_load: + * @path: A path of the saved dictionary file. + * + * Returns: (element-type utf8 gpointer) (transfer none): An Emoji dictionary file loaded from the saved cache file. + */ +GHashTable * ibus_emoji_dict_load (const gchar *path); + +/** + * ibus_emoji_dict_lookup: + * @dict: (element-type utf8 gpointer) (transfer none): An Emoji dictionary + * @annotation: Annotation for Emoji characters + * + * Returns: (element-type utf8) (transfer none): List of Emoji characters + * This API is for gobject-introspection. + */ +GSList * ibus_emoji_dict_lookup (GHashTable *dict, + const gchar *annotation); #endif |