public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Takao Fujiwara <tfujiwar@redhat.com>
To: git-commits@fedoraproject.org
Subject: [rpms/ibus] autotool: Make Compose preedit less intrusive
Date: Sun, 31 May 2026 02:07:39 GMT	[thread overview]
Message-ID: <178019325957.1.15102900056209812634.rpms-ibus-fb4b95e6ad20@fedoraproject.org> (raw)

            A new commit has been pushed.

            Repo   : rpms/ibus
            Branch : autotool
            Commit : fb4b95e6ad201b9eaf27905b9030a0d9a1d8749b
            Author : Takao Fujiwara <tfujiwar@redhat.com>
            Date   : 2021-07-26T23:57:28+09:00
            Stats  : +3319/-1 in 2 file(s)
            URL    : https://src.fedoraproject.org/rpms/ibus/c/fb4b95e6ad201b9eaf27905b9030a0d9a1d8749b?branch=autotool

            Log:
            Make Compose preedit less intrusive

- Set Multi_key to 0xB7 in compose preedit
- Search language name in engine list in ibus-setup

---
diff --git a/ibus-HEAD.patch b/ibus-HEAD.patch
index 9e6842e..362ebb1 100644
--- a/ibus-HEAD.patch
+++ b/ibus-HEAD.patch
@@ -2724,3 +2724,3316 @@ index a79e9296..099b9c60 100644
 -- 
 2.28.0
 
+From a4939f67f9de8275219e2cfcd3873e0fbe59058f Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Fri, 16 Jul 2021 13:08:02 +0900
+Subject: [PATCH] setup: Enhance engine search function
+
+ibus-setup can search both the language names and input method names
+in the top language list but when you search a language keyword
+in the language list and move to the input method list after click
+the hit language name, any input methods could not be shown because
+the language didn't hit in any input method names.
+
+In this enhancement, ibus-setup can show the input methods to hit
+the language names.
+---
+ setup/enginedialog.py | 26 ++++++++++++++++++++------
+ 1 file changed, 20 insertions(+), 6 deletions(-)
+
+diff --git a/setup/enginedialog.py b/setup/enginedialog.py
+index e1c322bf..470f801c 100644
+--- a/setup/enginedialog.py
++++ b/setup/enginedialog.py
+@@ -120,12 +120,26 @@ class EngineDialog(Gtk.Dialog):
+             return True
+         if word in row.untrans.lower():
+             return True
+-        if row.lang_info and row.name in self.__engines_for_lang:
+-            for row_e in self.__engines_for_lang[row.name]:
+-                if word in row_e.name.lower():
+-                    return True
+-                if word in row_e.untrans.lower():
+-                    return True
++        # Search engine name in language list
++        if row.lang_info:
++            if row.name in self.__engines_for_lang.keys():
++                for row_l in self.__engines_for_lang[row.name]:
++                    if word in row_l.name.lower():
++                        return True
++                    if word in row_l.untrans.lower():
++                        return True
++        # Search language name in engine list
++        if not row.lang_info:
++            for l in self.__engines_for_lang.keys():
++                if word in l.lower():
++                    for row_l in self.__engines_for_lang[l]:
++                        if row.name == row_l.name:
++                            return True
++            for (trans, untrans) in self.__untrans_for_lang.items():
++                if word in untrans.lower():
++                    for row_l in self.__engines_for_lang[trans]:
++                        if row.name == row_l.name:
++                            return True
+         return False
+ 
+ 
+-- 
+2.28.0
+
+From 7e12d589ee4979fdd0f0b08f1bac9049741ec628 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Mon, 26 Jul 2021 22:52:12 +0900
+Subject: [PATCH 1/6] src/ibuscomposetable: Move ibus_compose_table_check
+
+- Move ibus_compose_table_check and ibus_compose_table_compact_check
+  from ibusenginesimple.c to ibuscomposetable.c
+- Fix src/tests/ibus-compose.c to read compose sequences correctly.
+---
+ src/ibuscomposetable.c        | 519 +++++++++++++++++++++++++++++-
+ src/ibusenginesimple.c        | 583 ++++------------------------------
+ src/ibusenginesimpleprivate.h |  17 +-
+ src/tests/Makefile.am         |   3 +-
+ src/tests/ibus-compose.c      |   7 +-
+ 5 files changed, 601 insertions(+), 528 deletions(-)
+
+diff --git a/src/ibuscomposetable.c b/src/ibuscomposetable.c
+index dd7cbf83..f85177a6 100644
+--- a/src/ibuscomposetable.c
++++ b/src/ibuscomposetable.c
+@@ -33,6 +33,13 @@
+ 
+ #include "ibusenginesimpleprivate.h"
+ 
++/* This file contains the table of the compose sequences,
++ * static const guint16 gtk_compose_seqs_compact[] = {}
++ * It is generated from the compose-parse.py script.
++ */
++#include "gtkimcontextsimpleseqs.h"
++
++
+ #define IBUS_COMPOSE_TABLE_MAGIC "IBusComposeTable"
+ #define IBUS_COMPOSE_TABLE_VERSION (4)
+ #define X11_DATADIR X11_DATA_PREFIX "/share/X11/locale"
+@@ -44,6 +51,10 @@ typedef struct {
+ } IBusComposeData;
+ 
+ 
++extern const IBusComposeTableCompactEx ibus_compose_table_compact;
++extern const IBusComposeTableCompactEx ibus_compose_table_compact_32bit;
++
++
+ static void
+ ibus_compose_data_free (IBusComposeData *compose_data)
+ {
+@@ -527,20 +538,22 @@ ibus_compose_list_check_duplicated (GList *compose_list,
+         is_32bit = (n_outputs > 1) ? TRUE :
+                 (compose_data->values[0] >= 0xFFFF) ? TRUE : FALSE;
+         if (!is_32bit &&
+-            ibus_check_compact_table (&ibus_compose_table_compact,
+-                                      keysyms,
+-                                      n_compose,
+-                                      &compose_finish,
+-                                      &output_chars) && compose_finish) {
++            ibus_compose_table_compact_check (&ibus_compose_table_compact,
++                                              keysyms,
++                                              n_compose,
++                                              &compose_finish,
++                                              &output_chars) &&
++            compose_finish) {
+             if (compose_data->values[0] == *output_chars)
+                 removed_list = g_list_append (removed_list, compose_data);
+             g_free (output_chars);
+         } else if (is_32bit &&
+-                   ibus_check_compact_table (&ibus_compose_table_compact_32bit,
+-                                             keysyms,
+-                                             n_compose,
+-                                             &compose_finish,
+-                                             &output_chars) && compose_finish) {
++                   ibus_compose_table_compact_check (
++                          &ibus_compose_table_compact_32bit,
++                          keysyms,
++                          n_compose,
++                          &compose_finish,
++                          &output_chars) && compose_finish) {
+             
+             if (n_outputs == unichar_length (output_chars)) {
+                 int j = 0;
+@@ -1354,3 +1367,489 @@ ibus_compose_table_list_add_file (GSList      *compose_tables,
+     ibus_compose_table_save_cache (compose_table);
+     return g_slist_prepend (compose_tables, compose_table);
+ }
++
++
++static int
++compare_seq (const void *key, const void *value)
++{
++    int i = 0;
++    const guint16 *keysyms = key;
++    const guint16 *seq = value;
++
++    while (keysyms[i]) {
++        if (keysyms[i] < seq[i])
++            return -1;
++        else if (keysyms[i] > seq[i])
++            return 1;
++
++        i++;
++    }
++
++    return 0;
++}
++
++
++gboolean
++ibus_compose_table_check (const IBusComposeTableEx *table,
++                          guint16                  *compose_buffer,
++                          gint                      n_compose,
++                          gboolean                 *compose_finish,
++                          gboolean                 *compose_match,
++                          GString                  *output,
++                          gboolean                  is_32bit)
++{
++    gint row_stride = table->max_seq_len + 2;
++    guint16 *data_first;
++    int n_seqs;
++    guint16 *seq;
++
++    if (compose_finish)
++        *compose_finish = FALSE;
++    if (compose_match)
++        *compose_match = FALSE;
++    if (output)
++        g_string_set_size (output, 0);
++
++    if (n_compose > table->max_seq_len)
++        return FALSE;
++
++    if (is_32bit) {
++        if (!table->priv)
++            return FALSE;
++        data_first = table->priv->data_first;
++        n_seqs = table->priv->first_n_seqs;
++    } else {
++        data_first = table->data;
++        n_seqs = table->n_seqs;
++    }
++    seq = bsearch (compose_buffer,
++                   data_first, n_seqs,
++                   sizeof (guint16) * row_stride,
++                   compare_seq);
++
++    if (seq == NULL)
++        return FALSE;
++
++    guint16 *prev_seq;
++
++    /* Back up to the first sequence that matches to make sure
++     * we find the exact match if their is one.
++     */
++    while (seq > data_first) {
++        prev_seq = seq - row_stride;
++        if (compare_seq (compose_buffer, prev_seq) != 0)
++            break;
++        seq = prev_seq;
++    }
++
++    /* complete sequence */
++    if (n_compose == table->max_seq_len || seq[n_compose] == 0) {
++        guint16 *next_seq;
++        gunichar value = 0;
++        int num = 0;
++        int index = 0;
++        gchar *output_str = NULL;
++        GError *error = NULL;
++
++        if (is_32bit) {
++            num = seq[table->max_seq_len];
++            index = seq[table->max_seq_len + 1];
++            value =  table->priv->data_second[index];
++        } else {
++            value = seq[table->max_seq_len];
++        }
++
++        if (is_32bit) {
++            output_str = g_ucs4_to_utf8 (table->priv->data_second + index,
++                                         num, NULL, NULL, &error);
++            if (output_str) {
++                if (output)
++                    g_string_append (output, output_str);
++                g_free (output_str);
++                if (compose_match)
++                    *compose_match = TRUE;
++            } else {
++                g_warning ("Failed to output multiple characters: %s",
++                           error->message);
++                g_error_free (error);
++            }
++        } else {
++            if (output)
++                g_string_append_unichar (output, value);
++            if (compose_match)
++                *compose_match = TRUE;
++        }
++
++        /* We found a tentative match. See if there are any longer
++         * sequences containing this subsequence
++         */
++        next_seq = seq + row_stride;
++        if (next_seq < data_first + row_stride * n_seqs) {
++            if (compare_seq (compose_buffer, next_seq) == 0)
++                return TRUE;
++        }
++
++        if (compose_finish)
++            *compose_finish = TRUE;
++        compose_buffer[0] = 0;
++    }
++    return TRUE;
++}
++
++
++static int
++compare_seq_index (const void *key, const void *value)
++{
++    const guint16 *keysyms = key;
++    const guint16 *seq = value;
++
++    if (keysyms[0] < seq[0])
++        return -1;
++    else if (keysyms[0] > seq[0])
++        return 1;
++    return 0;
++}
++
++
++/**
++ * ibus_compose_table_compact_check:
++ * @table: A const `IBusComposeTableCompactEx`
++ * @compose_buffer: Typed compose sequence buffer
++ * @n_compose: The length of `compose_buffer`
++ * @compose_finish: If %TRUE, `output_chars` should be committed
++ * @output_chars: An array of gunichar of output compose characters
++ *
++ * output_chars is better to use gunichar instead of GString because
++ * IBusComposeData->values[] is the gunichar array.
++ */
++gboolean
++ibus_compose_table_compact_check (const IBusComposeTableCompactEx *table,
++                                  guint16
++                                                               *compose_buffer,
++                                  gint                             n_compose,
++                                  gboolean
++                                                               *compose_finish,
++                                  gunichar                       **output_chars)
++{
++    gint row_stride;
++    guint16 *seq_index;
++    guint16 *seq;
++    gint i;
++
++    if (compose_finish)
++        *compose_finish = FALSE;
++    if (output_chars)
++        *output_chars = NULL;
++
++    /* Will never match, if the sequence in the compose buffer is longer
++     * than the sequences in the table.  Further, compare_seq (key, val)
++     * will overrun val if key is longer than val. */
++    if (n_compose > table->max_seq_len)
++        return FALSE;
++
++    seq_index = bsearch (compose_buffer,
++                         table->data,
++                         table->n_index_size,
++                         sizeof (guint16) *  table->n_index_stride,
++                         compare_seq_index);
++
++    if (seq_index == NULL)
++        return FALSE;
++
++    if (n_compose == 1)
++        return TRUE;
++
++    seq = NULL;
++
++    if (table->priv) {
++        for (i = n_compose - 1; i < table->max_seq_len; i++) {
++            row_stride = i + 2;
++
++            if (seq_index[i + 1] - seq_index[i] > 0) {
++                seq = bsearch (compose_buffer + 1,
++                               table->data + seq_index[i],
++                               (seq_index[i + 1] - seq_index[i]) / row_stride,
++                               sizeof (guint16) * row_stride,
++                               compare_seq);
++                if (seq) {
++                    if (i == n_compose - 1)
++                        break;
++                    else
++                        return TRUE;
++                }
++            }
++        }
++        if (!seq) {
++            return FALSE;
++        } else {
++            int index = seq[row_stride - 2];
++            int length = seq[row_stride - 1];
++            int j;
++            if (compose_finish)
++                *compose_finish = TRUE;
++            if (output_chars) {
++                *output_chars = g_new (gunichar, length + 1);
++                for (j = 0; j < length; j++) {
++                    (*output_chars)[j] = table->priv->data2[index + j];
++                }
++                (*output_chars)[length] = 0;
++            }
++
++            return TRUE;
++        }
++    } else {
++        for (i = n_compose - 1; i < table->max_seq_len; i++) {
++            row_stride = i + 1;
++
++            if (seq_index[i + 1] - seq_index[i] > 0) {
++                seq = bsearch (compose_buffer + 1,
++                               table->data + seq_index[i],
++                               (seq_index[i + 1] - seq_index[i]) / row_stride,
++                               sizeof (guint16) * row_stride,
++                               compare_seq);
++
++                if (seq) {
++                    if (i == n_compose - 1)
++                        break;
++                    else
++                        return TRUE;
++                }
++            }
++        }
++        if (!seq) {
++            return FALSE;
++        } else {
++            if (compose_finish)
++                *compose_finish = TRUE;
++            if (output_chars) {
++                *output_chars = g_new (gunichar, 2);
++                (*output_chars)[0] = seq[row_stride - 1];
++                (*output_chars)[1] = 0;
++            }
++
++            return TRUE;
++        }
++    }
++
++    g_assert_not_reached ();
++}
++
++
++/* Checks if a keysym is a dead key. Dead key keysym values are defined in
++ * ../gdk/gdkkeysyms.h and the first is GDK_KEY_dead_grave. As X.Org is updated,
++ * more dead keys are added and we need to update the upper limit.
++ * Currently, the upper limit is GDK_KEY_dead_dasia+1. The +1 has to do with
++ * a temporary issue in the X.Org header files.
++ * In future versions it will be just the keysym (no +1).
++ */
++#define IS_DEAD_KEY(k) \
++      ((k) >= IBUS_KEY_dead_grave && (k) <= IBUS_KEY_dead_greek)
++
++/* This function receives a sequence of Unicode characters and tries to
++ * normalize it (NFC). We check for the case the the resulting string
++ * has length 1 (single character).
++ * NFC normalisation normally rearranges diacritic marks, unless these
++ * belong to the same Canonical Combining Class.
++ * If they belong to the same canonical combining class, we produce all
++ * permutations of the diacritic marks, then attempt to normalize.
++ */
++static gboolean
++check_normalize_nfc (gunichar* combination_buffer, gint n_compose)
++{
++    gunichar combination_buffer_temp[IBUS_MAX_COMPOSE_LEN];
++    gchar *combination_utf8_temp = NULL;
++    gchar *nfc_temp = NULL;
++    gint n_combinations;
++    gunichar temp_swap;
++    gint i;
++
++    n_combinations = 1;
++
++    for (i = 1; i < n_compose; i++ )
++        n_combinations *= i;
++
++    /* Xorg reuses dead_tilde for the perispomeni diacritic mark.
++     * We check if base character belongs to Greek Unicode block,
++     * and if so, we replace tilde with perispomeni. */
++    if (combination_buffer[0] >= 0x390 && combination_buffer[0] <= 0x3FF) {
++        for (i = 1; i < n_compose; i++ )
++            if (combination_buffer[i] == 0x303)
++                combination_buffer[i] = 0x342;
++    }
++
++    memcpy (combination_buffer_temp,
++            combination_buffer,
++            IBUS_MAX_COMPOSE_LEN * sizeof (gunichar) );
++
++    for (i = 0; i < n_combinations; i++ ) {
++        g_unicode_canonical_ordering (combination_buffer_temp, n_compose);
++        combination_utf8_temp = g_ucs4_to_utf8 (combination_buffer_temp, -1,
++                                                NULL, NULL, NULL);
++        nfc_temp = g_utf8_normalize (combination_utf8_temp, -1,
++                                     G_NORMALIZE_NFC);
++
++        if (g_utf8_strlen (nfc_temp, -1) == 1) {
++            memcpy (combination_buffer,
++                    combination_buffer_temp,
++                    IBUS_MAX_COMPOSE_LEN * sizeof (gunichar) );
++
++            g_free (combination_utf8_temp);
++            g_free (nfc_temp);
++
++            return TRUE;
++        }
++
++        g_free (combination_utf8_temp);
++        g_free (nfc_temp);
++
++        if (n_compose > 2) {
++            gint j = i % (n_compose - 1) + 1;
++            gint k = (i+1) % (n_compose - 1) + 1;
++            if (j >= IBUS_MAX_COMPOSE_LEN) {
++                g_warning ("j >= IBUS_MAX_COMPOSE_LEN for " \
++                           "combination_buffer_temp");
++                break;
++            }
++            if (k >= IBUS_MAX_COMPOSE_LEN) {
++                g_warning ("k >= IBUS_MAX_COMPOSE_LEN for " \
++                           "combination_buffer_temp");
++                break;
++            }
++            temp_swap = combination_buffer_temp[j];
++            combination_buffer_temp[j] = combination_buffer_temp[k];
++            combination_buffer_temp[k] = temp_swap;
++        } else {
++            break;
++        }
++    }
++
++    return FALSE;
++}
++
++
++gboolean
++ibus_check_algorithmically (const guint16 *compose_buffer,
++                            gint           n_compose,
++                            gunichar      *output_char)
++
++{
++    gint i;
++    gunichar combination_buffer[IBUS_MAX_COMPOSE_LEN];
++    gchar *combination_utf8, *nfc;
++
++    if (output_char)
++        *output_char = 0;
++
++    if (n_compose >= IBUS_MAX_COMPOSE_LEN)
++        return FALSE;
++
++    for (i = 0; i < n_compose && IS_DEAD_KEY (compose_buffer[i]); i++)
++        ;
++    if (i == n_compose)
++        return TRUE;
++
++    if (i > 0 && i == n_compose - 1) {
++        combination_buffer[0] = ibus_keyval_to_unicode (compose_buffer[i]);
++        combination_buffer[n_compose] = 0;
++        i--;
++        while (i >= 0) {
++            combination_buffer[i+1] = ibus_keysym_to_unicode (compose_buffer[i],
++                                                              TRUE);
++            if (!combination_buffer[i+1]) {
++                combination_buffer[i+1] =
++                        ibus_keyval_to_unicode (compose_buffer[i]);
++            }
++            i--;
++        }
++
++        /* If the buffer normalizes to a single character,
++         * then modify the order of combination_buffer accordingly, if
++         * necessary, and return TRUE.
++         */
++        if (check_normalize_nfc (combination_buffer, n_compose)) {
++            combination_utf8 = g_ucs4_to_utf8 (combination_buffer, -1,
++                                               NULL, NULL, NULL);
++            nfc = g_utf8_normalize (combination_utf8, -1, G_NORMALIZE_NFC);
++
++            if (output_char)
++                *output_char = g_utf8_get_char (nfc);
++
++            g_free (combination_utf8);
++            g_free (nfc);
++
++            return TRUE;
++        }
++    }
++
++    return FALSE;
++}
++
++
++gunichar
++ibus_keysym_to_unicode (guint16  keysym,
++                        gboolean combining) {
++#define CASE(keysym_suffix, unicode)                                    \
++        case IBUS_KEY_dead_##keysym_suffix: return unicode
++#define CASE_COMBINE(keysym_suffix, combined_unicode, isolated_unicode) \
++        case IBUS_KEY_dead_##keysym_suffix:                             \
++            if (combining)                                              \
++                return combined_unicode;                                \
++            else                                                        \
++                return isolated_unicode
++    switch (keysym) {
++    CASE (a, 0x03041);
++    CASE (A, 0x03042);
++    CASE (i, 0x03043);
++    CASE (I, 0x03044);
++    CASE (u, 0x03045);
++    CASE (U, 0x03046);
++    CASE (e, 0x03047);
++    CASE (E, 0x03048);
++    CASE (o, 0x03049);
++    CASE (O, 0x0304A);
++    CASE         (abovecomma,                   0x0313);
++    CASE_COMBINE (abovedot,                     0x0307, 0x02D9);
++    CASE         (abovereversedcomma,           0x0314);
++    CASE_COMBINE (abovering,                    0x030A, 0x02DA);
++    CASE_COMBINE (acute,                        0x0301, 0x00B4);
++    CASE         (belowbreve,                   0x032E);
++    CASE_COMBINE (belowcircumflex,              0x032D, 0xA788);
++    CASE_COMBINE (belowcomma,                   0x0326, 0x002C);
++    CASE         (belowdiaeresis,               0x0324);
++    CASE_COMBINE (belowdot,                     0x0323, 0x002E);
++    CASE_COMBINE (belowmacron,                  0x0331, 0x02CD);
++    CASE_COMBINE (belowring,                    0x030A, 0x02F3);
++    CASE_COMBINE (belowtilde,                   0x0330, 0x02F7);
++    CASE_COMBINE (breve,                        0x0306, 0x02D8);
++    CASE_COMBINE (capital_schwa,                0x018F, 0x04D8);
++    CASE_COMBINE (caron,                        0x030C, 0x02C7);
++    CASE_COMBINE (cedilla,                      0x0327, 0x00B8);
++    CASE_COMBINE (circumflex,                   0x0302, 0x005E);
++    CASE         (currency,                     0x00A4);
++    // IBUS_KEY_dead_dasia == IBUS_KEY_dead_abovereversedcomma
++    CASE_COMBINE (diaeresis,                    0x0308, 0x00A8);
++    CASE_COMBINE (doubleacute,                  0x030B, 0x02DD);
++    CASE_COMBINE (doublegrave,                  0x030F, 0x02F5);
++    CASE_COMBINE (grave,                        0x0300, 0x0060);
++    CASE         (greek,                        0x03BC);
++    CASE         (hook,                         0x0309);
++    CASE         (horn,                         0x031B);
++    CASE         (invertedbreve,                0x032F);
++    CASE_COMBINE (iota,                         0x0345, 0x037A);
++    CASE_COMBINE (macron,                       0x0304, 0x00AF);
++    CASE_COMBINE (ogonek,                       0x0328, 0x02DB);
++    // IBUS_KEY_dead_perispomeni == IBUS_KEY_dead_tilde
++    // IBUS_KEY_dead_psili == IBUS_KEY_dead_abovecomma
++    CASE_COMBINE (semivoiced_sound,             0x309A, 0x309C);
++    CASE_COMBINE (small_schwa,                  0x1D4A, 0x04D9);
++    CASE         (stroke,                       0x002F);
++    CASE_COMBINE (tilde,                        0x0303, 0x007E);
++    CASE_COMBINE (voiced_sound,                 0x3099, 0x309B);
++    case IBUS_KEY_Multi_key:
++        return 0x2384;
++    default:;
++    }
++    return 0x0;
++#undef CASE
++#undef CASE_COMBINE
++}
+diff --git a/src/ibusenginesimple.c b/src/ibusenginesimple.c
+index 6dbc39c7..4644620b 100644
+--- a/src/ibusenginesimple.c
++++ b/src/ibusenginesimple.c
+@@ -91,11 +91,6 @@ struct _IBusEngineSimplePrivate {
+     gboolean            lookup_table_visible;
+ };
+ 
+-struct _IBusComposeTableCompactPrivate
+-{
+-    const guint32 *data2;
+-};
+-
+ /* From the values below, the value 30 means the number of different first keysyms
+  * that exist in the Compose file (from Xorg). When running compose-parse.py without
+  * parameters, you get the count that you can put here. Needed when updating the
+@@ -124,6 +119,7 @@ const IBusComposeTableCompactEx ibus_compose_table_compact_32bit = {
+ };
+ 
+ guint COMPOSE_BUFFER_SIZE = 20;
++G_LOCK_DEFINE_STATIC (global_tables);
+ static GSList *global_tables;
+ 
+ /* functions prototype */
+@@ -261,74 +257,6 @@ ibus_engine_simple_commit_char (IBusEngineSimple *simple,
+             ibus_text_new_from_unichar (ch));
+ }
+ 
+-static gunichar
+-ibus_keysym_to_unicode (guint16  keysym,
+-                        gboolean combining) {
+-#define CASE(keysym_suffix, unicode)                                    \
+-        case IBUS_KEY_dead_##keysym_suffix: return unicode
+-#define CASE_COMBINE(keysym_suffix, combined_unicode, isolated_unicode) \
+-        case IBUS_KEY_dead_##keysym_suffix:                             \
+-            if (combining)                                              \
+-                return combined_unicode;                                \
+-            else                                                        \
+-                return isolated_unicode
+-    switch (keysym) {
+-    CASE (a, 0x03041);
+-    CASE (A, 0x03042);
+-    CASE (i, 0x03043);
+-    CASE (I, 0x03044);
+-    CASE (u, 0x03045);
+-    CASE (U, 0x03046);
+-    CASE (e, 0x03047);
+-    CASE (E, 0x03048);
+-    CASE (o, 0x03049);
+-    CASE (O, 0x0304A);
+-    CASE         (abovecomma,                   0x0313);
+-    CASE_COMBINE (abovedot,                     0x0307, 0x02D9);
+-    CASE         (abovereversedcomma,           0x0314);
+-    CASE_COMBINE (abovering,                    0x030A, 0x02DA);
+-    CASE_COMBINE (acute,                        0x0301, 0x00B4);
+-    CASE         (belowbreve,                   0x032E);
+-    CASE_COMBINE (belowcircumflex,              0x032D, 0xA788);
+-    CASE_COMBINE (belowcomma,                   0x0326, 0x002C);
+-    CASE         (belowdiaeresis,               0x0324);
+-    CASE_COMBINE (belowdot,                     0x0323, 0x002E);
+-    CASE_COMBINE (belowmacron,                  0x0331, 0x02CD);
+-    CASE_COMBINE (belowring,                    0x030A, 0x02F3);
+-    CASE_COMBINE (belowtilde,                   0x0330, 0x02F7);
+-    CASE_COMBINE (breve,                        0x0306, 0x02D8);
+-    CASE_COMBINE (capital_schwa,                0x018F, 0x04D8);
+-    CASE_COMBINE (caron,                        0x030C, 0x02C7);
+-    CASE_COMBINE (cedilla,                      0x0327, 0x00B8);
+-    CASE_COMBINE (circumflex,                   0x0302, 0x005E);
+-    CASE         (currency,                     0x00A4);
+-    // IBUS_KEY_dead_dasia == IBUS_KEY_dead_abovereversedcomma
+-    CASE_COMBINE (diaeresis,                    0x0308, 0x00A8);
+-    CASE_COMBINE (doubleacute,                  0x030B, 0x02DD);
+-    CASE_COMBINE (doublegrave,                  0x030F, 0x02F5);
+-    CASE_COMBINE (grave,                        0x0300, 0x0060);
+-    CASE         (greek,                        0x03BC);
+-    CASE         (hook,                         0x0309);
+-    CASE         (horn,                         0x031B);
+-    CASE         (invertedbreve,                0x032F);
+-    CASE_COMBINE (iota,                         0x0345, 0x037A);
+-    CASE_COMBINE (macron,                       0x0304, 0x00AF);
+-    CASE_COMBINE (ogonek,                       0x0328, 0x02DB);
+-    // IBUS_KEY_dead_perispomeni == IBUS_KEY_dead_tilde
+-    // IBUS_KEY_dead_psili == IBUS_KEY_dead_abovecomma
+-    CASE_COMBINE (semivoiced_sound,             0x309A, 0x309C);
+-    CASE_COMBINE (small_schwa,                  0x1D4A, 0x04D9);
+-    CASE         (stroke,                       0x002F);
+-    CASE_COMBINE (tilde,                        0x0303, 0x007E);
+-    CASE_COMBINE (voiced_sound,                 0x3099, 0x309B);
+-    case IBUS_KEY_Multi_key:
+-        return 0x2384;
+-    default:;
+-    }
+-    return 0x0;
+-#undef CASE
+-#undef CASE_COMBINE
+-}
+ 
+ static void
+ ibus_engine_simple_commit_str (IBusEngineSimple *simple,
+@@ -607,415 +535,6 @@ check_emoji_table (IBusEngineSimple       *simple,
+     return FALSE;
+ }
+ 
+-static int
+-compare_seq_index (const void *key, const void *value)
+-{
+-    const guint16 *keysyms = key;
+-    const guint16 *seq = value;
+-
+-    if (keysyms[0] < seq[0])
+-        return -1;
+-    else if (keysyms[0] > seq[0])
+-        return 1;
+-    return 0;
+-}
+-
+-static int
+-compare_seq (const void *key, const void *value)
+-{
+-    int i = 0;
+-    const guint16 *keysyms = key;
+-    const guint16 *seq = value;
+-
+-    while (keysyms[i]) {
+-        if (keysyms[i] < seq[i])
+-            return -1;
+-        else if (keysyms[i] > seq[i])
+-            return 1;
+-
+-        i++;
+-    }
+-
+-    return 0;
+-}
+-
+-
+-static gboolean
+-check_table (IBusEngineSimple         *simple,
+-             const IBusComposeTableEx *table,
+-             gint                      n_compose,
+-             gboolean                  is_32bit)
+-{
+-    IBusEngineSimplePrivate *priv = simple->priv;
+-    gint row_stride = table->max_seq_len + 2;
+-    guint16 *data_first;
+-    int n_seqs;
+-    guint16 *seq;
+-
+-    g_assert (IBUS_IS_ENGINE_SIMPLE (simple));
+-    CHECK_COMPOSE_BUFFER_LENGTH (n_compose);
+-
+-    if (n_compose > table->max_seq_len)
+-        return FALSE;
+-
+-    if (is_32bit) {
+-        if (!table->priv)
+-            return FALSE;
+-        data_first = table->priv->data_first;
+-        n_seqs = table->priv->first_n_seqs;
+-    } else {
+-        data_first = table->data;
+-        n_seqs = table->n_seqs;
+-    }
+-    seq = bsearch (priv->compose_buffer,
+-                   data_first, n_seqs,
+-                   sizeof (guint16) * row_stride,
+-                   compare_seq);
+-
+-    if (seq == NULL)
+-        return FALSE;
+-
+-    guint16 *prev_seq;
+-
+-    priv->tentative_match = 0;
+-    priv->tentative_match_len = 0;
+-    /* Back up to the first sequence that matches to make sure
+-     * we find the exact match if their is one.
+-     */
+-    while (seq > data_first) {
+-        prev_seq = seq - row_stride;
+-        if (compare_seq (priv->compose_buffer, prev_seq) != 0) {
+-            break;
+-        }
+-        seq = prev_seq;
+-    }
+-
+-    /* complete sequence */
+-    if (n_compose == table->max_seq_len || seq[n_compose] == 0) {
+-        guint16 *next_seq;
+-        gunichar value = 0;
+-        int num = 0;
+-        int index = 0;
+-        gchar *output_str = NULL;
+-        GError *error = NULL;
+-
+-        if (is_32bit) {
+-            num = seq[table->max_seq_len];
+-            index = seq[table->max_seq_len + 1];
+-            value =  table->priv->data_second[index];
+-        } else {
+-            value = seq[table->max_seq_len];
+-        }
+-
+-        /* We found a tentative match. See if there are any longer
+-         * sequences containing this subsequence
+-         */
+-        next_seq = seq + row_stride;
+-        if (next_seq < data_first + row_stride * n_seqs) {
+-            if (compare_seq (priv->compose_buffer, next_seq) == 0) {
+-                priv->tentative_match = value;
+-                priv->tentative_match_len = n_compose;
+-
+-                ibus_engine_simple_update_preedit_text (simple);
+-
+-                return TRUE;
+-            }
+-        }
+-
+-        if (is_32bit) {
+-            output_str = g_ucs4_to_utf8 (table->priv->data_second + index,
+-                                         num, NULL, NULL, &error);
+-            if (output_str) {
+-                ibus_engine_simple_commit_str(simple, output_str);
+-                g_free (output_str);
+-            } else {
+-                g_warning ("Failed to output multiple characters: %s",
+-                           error->message);
+-                g_error_free (error);
+-            }
+-        } else {
+-            ibus_engine_simple_commit_char (simple, value);
+-        }
+-        priv->compose_buffer[0] = 0;
+-    }
+-    ibus_engine_simple_update_preedit_text (simple);
+-    return TRUE;
+-}
+-
+-gboolean
+-ibus_check_compact_table (const IBusComposeTableCompactEx *table,
+-                          guint16                         *compose_buffer,
+-                          gint                             n_compose,
+-                          gboolean                        *compose_finish,
+-                          gunichar                       **output_chars)
+-{
+-    gint row_stride;
+-    guint16 *seq_index;
+-    guint16 *seq;
+-    gint i;
+-
+-    if (compose_finish)
+-        *compose_finish = FALSE;
+-    if (output_chars)
+-        *output_chars = NULL;
+-
+-    CHECK_COMPOSE_BUFFER_LENGTH (n_compose);
+-
+-    /* Will never match, if the sequence in the compose buffer is longer
+-     * than the sequences in the table.  Further, compare_seq (key, val)
+-     * will overrun val if key is longer than val. */
+-    if (n_compose > table->max_seq_len)
+-        return FALSE;
+-
+-    // g_debug ("check_compact_table(n_compose=%d) [%04x, %04x, %04x, %04x]",
+-    //          n_compose,
+-    //          compose_buffer[0],
+-    //          compose_buffer[1],
+-    //          compose_buffer[2],
+-    //          compose_buffer[3]);
+-
+-    seq_index = bsearch (compose_buffer,
+-                         table->data,
+-                         table->n_index_size,
+-                         sizeof (guint16) *  table->n_index_stride,
+-                         compare_seq_index);
+-
+-    if (seq_index == NULL) {
+-        // g_debug ("compact: no\n");
+-        return FALSE;
+-    }
+-
+-    if (n_compose == 1) {
+-        // g_debug ("compact: yes\n");
+-        return TRUE;
+-    }
+-
+-    // g_debug ("compact: %04x ", *seq_index);
+-    seq = NULL;
+-
+-    if (table->priv) {
+-        for (i = n_compose - 1; i < table->max_seq_len; i++) {
+-            row_stride = i + 2;
+-
+-            if (seq_index[i + 1] - seq_index[i] > 0) {
+-                seq = bsearch (compose_buffer + 1,
+-                               table->data + seq_index[i],
+-                               (seq_index[i + 1] - seq_index[i]) / row_stride,
+-                               sizeof (guint16) * row_stride,
+-                               compare_seq);
+-                if (seq) {
+-                    if (i == n_compose - 1)
+-                        break;
+-                    else
+-                        return TRUE;
+-                }
+-            }
+-        }
+-        if (!seq) {
+-            return FALSE;
+-        } else {
+-            int index = seq[row_stride - 2];
+-            int length = seq[row_stride - 1];
+-            int j;
+-            if (compose_finish)
+-                *compose_finish = TRUE;
+-            if (output_chars) {
+-                *output_chars = g_new (gunichar, length + 1);
+-                for (j = 0; j < length; j++)
+-                    (*output_chars)[j] = table->priv->data2[index + j];
+-                (*output_chars)[length] = 0;
+-            }
+-
+-            // g_debug ("U+%04X\n", value);
+-            return TRUE;
+-        }
+-    } else {
+-        for (i = n_compose - 1; i < table->max_seq_len; i++) {
+-            row_stride = i + 1;
+-
+-            if (seq_index[i + 1] - seq_index[i] > 0) {
+-                seq = bsearch (compose_buffer + 1,
+-                               table->data + seq_index[i],
+-                               (seq_index[i + 1] - seq_index[i]) / row_stride,
+-                               sizeof (guint16) * row_stride,
+-                               compare_seq);
+-
+-                if (seq) {
+-                    if (i == n_compose - 1)
+-                        break;
+-                    else
+-                        return TRUE;
+-                }
+-            }
+-        }
+-        if (!seq) {
+-            return FALSE;
+-        } else {
+-            if (compose_finish)
+-                *compose_finish = TRUE;
+-            if (output_chars) {
+-                *output_chars = g_new (gunichar, 2);
+-                (*output_chars)[0] = seq[row_stride - 1];
+-                (*output_chars)[1] = 0;
+-            }
+-
+-            // g_debug ("U+%04X\n", value);
+-            return TRUE;
+-        }
+-    }
+-
+-    g_assert_not_reached ();
+-}
+-
+-
+-/* Checks if a keysym is a dead key. Dead key keysym values are defined in
+- * ../gdk/gdkkeysyms.h and the first is GDK_KEY_dead_grave. As X.Org is updated,
+- * more dead keys are added and we need to update the upper limit.
+- * Currently, the upper limit is GDK_KEY_dead_dasia+1. The +1 has to do with
+- * a temporary issue in the X.Org header files.
+- * In future versions it will be just the keysym (no +1).
+- */
+-#define IS_DEAD_KEY(k) \
+-      ((k) >= IBUS_KEY_dead_grave && (k) <= (IBUS_KEY_dead_dasia + 1))
+-
+-/* This function receives a sequence of Unicode characters and tries to
+- * normalize it (NFC). We check for the case the the resulting string
+- * has length 1 (single character).
+- * NFC normalisation normally rearranges diacritic marks, unless these
+- * belong to the same Canonical Combining Class.
+- * If they belong to the same canonical combining class, we produce all
+- * permutations of the diacritic marks, then attempt to normalize.
+- */
+-static gboolean
+-check_normalize_nfc (gunichar* combination_buffer, gint n_compose)
+-{
+-    gunichar combination_buffer_temp[IBUS_MAX_COMPOSE_LEN];
+-    gchar *combination_utf8_temp = NULL;
+-    gchar *nfc_temp = NULL;
+-    gint n_combinations;
+-    gunichar temp_swap;
+-    gint i;
+-
+-    n_combinations = 1;
+-
+-    CHECK_COMPOSE_BUFFER_LENGTH (n_compose);
+-
+-    for (i = 1; i < n_compose; i++ )
+-        n_combinations *= i;
+-
+-    /* Xorg reuses dead_tilde for the perispomeni diacritic mark.
+-     * We check if base character belongs to Greek Unicode block,
+-     * and if so, we replace tilde with perispomeni. */
+-    if (combination_buffer[0] >= 0x390 && combination_buffer[0] <= 0x3FF) {
+-        for (i = 1; i < n_compose; i++ )
+-            if (combination_buffer[i] == 0x303)
+-                combination_buffer[i] = 0x342;
+-    }
+-
+-    memcpy (combination_buffer_temp,
+-            combination_buffer,
+-            IBUS_MAX_COMPOSE_LEN * sizeof (gunichar) );
+-
+-    for (i = 0; i < n_combinations; i++ ) {
+-        g_unicode_canonical_ordering (combination_buffer_temp, n_compose);
+-        combination_utf8_temp = g_ucs4_to_utf8 (combination_buffer_temp, -1, NULL, NULL, NULL);
+-        nfc_temp = g_utf8_normalize (combination_utf8_temp, -1, G_NORMALIZE_NFC);
+-
+-        if (g_utf8_strlen (nfc_temp, -1) == 1) {
+-            memcpy (combination_buffer,
+-                    combination_buffer_temp,
+-                    IBUS_MAX_COMPOSE_LEN * sizeof (gunichar) );
+-
+-            g_free (combination_utf8_temp);
+-            g_free (nfc_temp);
+-
+-            return TRUE;
+-        }
+-
+-        g_free (combination_utf8_temp);
+-        g_free (nfc_temp);
+-
+-        if (n_compose > 2) {
+-            gint j = i % (n_compose - 1) + 1;
+-            gint k = (i+1) % (n_compose - 1) + 1;
+-            if (j >= IBUS_MAX_COMPOSE_LEN) {
+-                g_warning ("j >= IBUS_MAX_COMPOSE_LEN for " \
+-                           "combination_buffer_temp");
+-                break;
+-            }
+-            if (k >= IBUS_MAX_COMPOSE_LEN) {
+-                g_warning ("k >= IBUS_MAX_COMPOSE_LEN for " \
+-                           "combination_buffer_temp");
+-                break;
+-            }
+-            temp_swap = combination_buffer_temp[j];
+-            combination_buffer_temp[j] = combination_buffer_temp[k];
+-            combination_buffer_temp[k] = temp_swap;
+-        }
+-        else
+-            break;
+-    }
+-
+-    return FALSE;
+-}
+-
+-gboolean
+-ibus_check_algorithmically (const guint16 *compose_buffer,
+-                            gint           n_compose,
+-                            gunichar      *output_char)
+-
+-{
+-    gint i;
+-    gunichar combination_buffer[IBUS_MAX_COMPOSE_LEN];
+-    gchar *combination_utf8, *nfc;
+-
+-    if (output_char)
+-        *output_char = 0;
+-
+-    CHECK_COMPOSE_BUFFER_LENGTH (n_compose);
+-
+-    if (n_compose >= IBUS_MAX_COMPOSE_LEN)
+-        return FALSE;
+-
+-    for (i = 0; i < n_compose && IS_DEAD_KEY (compose_buffer[i]); i++)
+-        ;
+-    if (i == n_compose)
+-        return TRUE;
+-
+-    if (i > 0 && i == n_compose - 1) {
+-        combination_buffer[0] = ibus_keyval_to_unicode (compose_buffer[i]);
+-        combination_buffer[n_compose] = 0;
+-        i--;
+-        while (i >= 0) {
+-            combination_buffer[i+1] = ibus_keysym_to_unicode (compose_buffer[i],
+-                                                              TRUE);
+-            if (!combination_buffer[i+1]) {
+-                combination_buffer[i+1] =
+-                        ibus_keyval_to_unicode (compose_buffer[i]);
+-            }
+-            i--;
+-        }
+-
+-        /* If the buffer normalizes to a single character,
+-         * then modify the order of combination_buffer accordingly, if necessary,
+-         * and return TRUE.
+-         */
+-        if (check_normalize_nfc (combination_buffer, n_compose)) {
+-            combination_utf8 = g_ucs4_to_utf8 (combination_buffer, -1, NULL, NULL, NULL);
+-            nfc = g_utf8_normalize (combination_utf8, -1, G_NORMALIZE_NFC);
+-
+-            if (output_char)
+-                *output_char = g_utf8_get_char (nfc);
+-
+-            g_free (combination_utf8);
+-            g_free (nfc);
+-
+-            return TRUE;
+-        }
+-    }
+-
+-    return FALSE;
+-}
+ 
+ static gboolean
+ no_sequence_matches (IBusEngineSimple *simple,
+@@ -1184,34 +703,71 @@ ibus_engine_simple_check_all_compose_table (IBusEngineSimple *simple,
+                                             gint              n_compose)
+ {
+     IBusEngineSimplePrivate *priv = simple->priv;
+-    gboolean compose_finish;
+-    gunichar output_char;
++    gboolean compose_finish = FALSE;
++    gboolean compose_match = FALSE;
++    GString *output = g_string_new ("");
++    gboolean does_hit = FALSE;
++    gboolean is_32bit = FALSE;
++    GSList *tmp_list;
+     gunichar *output_chars = NULL;
+-    gchar *output_str = NULL;
+-    GError *error = NULL;
+-    GSList *list = global_tables;
++    gunichar output_char;
+ 
+-    while (list) {
+-        if (check_table (simple,
+-            (IBusComposeTableEx *)list->data,
++    G_LOCK (global_tables);
++    tmp_list = global_tables;
++    while (tmp_list) {
++        is_32bit = FALSE;
++        if (ibus_compose_table_check (
++            (IBusComposeTableEx *)tmp_list->data,
++            priv->compose_buffer,
+             n_compose,
+-            FALSE)) {
+-            return TRUE;
++            &compose_finish,
++            &compose_match,
++            output,
++            is_32bit)) {
++            does_hit = TRUE;
++            break;
+         }
+-        if (check_table (simple,
+-            (IBusComposeTableEx *)list->data,
++        is_32bit = TRUE;
++        if (ibus_compose_table_check (
++            (IBusComposeTableEx *)tmp_list->data,
++            priv->compose_buffer,
+             n_compose,
+-            TRUE)) {
+-            return TRUE;
++            &compose_finish,
++            &compose_match,
++            output,
++            is_32bit)) {
++            does_hit = TRUE;
++            break;
+         }
+-        list = list->next;
++        tmp_list = tmp_list->next;
+     }
++    G_UNLOCK (global_tables);
+ 
+-    if (ibus_check_compact_table (&ibus_compose_table_compact,
+-                                  priv->compose_buffer,
+-                                  n_compose,
+-                                  &compose_finish,
+-                                  &output_chars)) {
++    if (does_hit) {
++        if (compose_finish) {
++            if (compose_match) {
++                if (is_32bit) {
++                    ibus_engine_simple_commit_str (simple, output->str);
++                } else {
++                    ibus_engine_simple_commit_char (simple,
++                                                    g_utf8_get_char (output->str));
++                }
++            }
++        } else if (compose_match) {
++            priv->tentative_match = g_utf8_get_char (output->str);
++            priv->tentative_match_len = n_compose;
++        }
++        ibus_engine_simple_update_preedit_text (simple);
++        g_string_free (output, TRUE);
++        return TRUE;
++    }
++    g_string_free (output, TRUE);
++
++    if (ibus_compose_table_compact_check (&ibus_compose_table_compact,
++                                          priv->compose_buffer,
++                                          n_compose,
++                                          &compose_finish,
++                                          &output_chars)) {
+         if (compose_finish) {
+             ibus_engine_simple_commit_char (simple, *output_chars);
+             g_free (output_chars);
+@@ -1220,28 +776,29 @@ ibus_engine_simple_check_all_compose_table (IBusEngineSimple *simple,
+         ibus_engine_simple_update_preedit_text (simple);
+         return TRUE;
+     }
+-    if (ibus_check_compact_table (&ibus_compose_table_compact_32bit,
+-                                  priv->compose_buffer,
+-                                  n_compose,
+-                                  &compose_finish,
+-                                  &output_chars)) {
++    if (ibus_compose_table_compact_check (&ibus_compose_table_compact_32bit,
++                                          priv->compose_buffer,
++                                          n_compose,
++                                          &compose_finish,
++                                          &output_chars)) {
+         if (compose_finish) {
+-            output_str = g_ucs4_to_utf8 (output_chars, -1, NULL, NULL, &error);
+-            if (output_str) {
+-                ibus_engine_simple_commit_str (simple, output_str);
+-                g_free (output_str);
++            GError *error = NULL;
++            char *str = g_ucs4_to_utf8 (output_chars, -1, NULL, NULL, &error);
++            if (str) {
++                ibus_engine_simple_commit_str (simple, str);
++                g_free (str);
+             } else {
+                 g_warning ("Failed to output multiple characters: %s",
+                            error->message);
+                 g_error_free (error);
+             }
+-            g_free (output_chars);
+             priv->compose_buffer[0] = 0;
+         }
+-        
++        g_free (output_chars);
+         ibus_engine_simple_update_preedit_text (simple);
+         return TRUE;
+     }
++    g_assert (!output_chars);
+     if (ibus_check_algorithmically (priv->compose_buffer,
+                                     n_compose,
+                                     &output_char)) {
+diff --git a/src/ibusenginesimpleprivate.h b/src/ibusenginesimpleprivate.h
+index 508d9344..47f2a3d4 100644
+--- a/src/ibusenginesimpleprivate.h
++++ b/src/ibusenginesimpleprivate.h
+@@ -37,15 +37,30 @@ struct _IBusComposeTablePrivate
+     gsize second_size;
+ };
+ 
++struct _IBusComposeTableCompactPrivate
++{
++    const guint32 *data2;
++};
++
+ gboolean ibus_check_algorithmically (const guint16              *compose_buffer,
+                                      gint                        n_compose,
+                                      gunichar                   *output);
+-gboolean ibus_check_compact_table   (const IBusComposeTableCompactEx
++gboolean ibus_compose_table_check   (const IBusComposeTableEx   *table,
++                                     guint16                    *compose_buffer,
++                                     gint                        n_compose,
++                                     gboolean                   *compose_finish,
++                                     gboolean                   *compose_match,
++                                     GString                    *output,
++                                     gboolean                    is_32bit);
++gboolean ibus_compose_table_compact_check
++                                    (const IBusComposeTableCompactEx
+                                                                 *table,
+                                      guint16                    *compose_buffer,
+                                      gint                        n_compose,
+                                      gboolean                   *compose_finish,
+                                      gunichar                  **output_chars);
++gunichar ibus_keysym_to_unicode     (guint16                     keysym,
++                                     gboolean                    combining);
+ 
+ G_END_DECLS
+ 
+diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am
+index 0a2e523c..13c06eb4 100644
+--- a/src/tests/Makefile.am
++++ b/src/tests/Makefile.am
+@@ -3,7 +3,7 @@
+ # ibus - The Input Bus
+ #
+ # Copyright (c) 2007-2015 Peng Huang <shawn.p.huang@gmail.com>
+-# Copyright (c) 2015-2019 Takao Fujiwara <takao.fujiwara1@gmail.com>
++# Copyright (c) 2015-2021 Takao Fujiwara <takao.fujiwara1@gmail.com>
+ # Copyright (c) 2007-2018 Red Hat, Inc.
+ #
+ # This library is free software; you can redistribute it and/or
+@@ -30,6 +30,7 @@ AM_CPPFLAGS = \
+     @GLIB2_CFLAGS@                          \
+     @GIO2_CFLAGS@                           \
+     -DIBUS_DISABLE_DEPRECATION_WARNINGS     \
++    -DX11_DATA_PREFIX=\"$(X11_PREFIX)\"     \
+     -I$(top_srcdir)/src                     \
+     -I$(top_builddir)/src                   \
+     $(NULL)
+diff --git a/src/tests/ibus-compose.c b/src/tests/ibus-compose.c
+index 81bfc69b..0be01d27 100644
+--- a/src/tests/ibus-compose.c
++++ b/src/tests/ibus-compose.c
+@@ -6,6 +6,7 @@
+ #define GREEN "\033[0;32m"
+ #define RED   "\033[0;31m"
+ #define NC    "\033[0m"
++#define X11_DATADIR X11_DATA_PREFIX "/share/X11/locale"
+ 
+ IBusBus *m_bus;
+ gchar *m_compose_file;
+@@ -35,7 +36,7 @@ get_compose_path ()
+             break;
+         if (g_strcmp0 (*l, "C") == 0)
+             break;
+-        compose_path = g_build_filename ("/usr/share/X11/locale",
++        compose_path = g_build_filename (X11_DATADIR,
+                                          *l,
+                                          "Compose",
+                                          NULL);
+@@ -155,7 +156,7 @@ set_engine_cb (GObject *object, GAsyncResult *res, gpointer data)
+     for (i = 0;
+          i < (m_compose_table->n_seqs * index_stride);
+          i += index_stride) {
+-        for (j = i; j < i + (index_stride - 1); j++) {
++        for (j = i; j < i + (index_stride - 2); j++) {
+             guint keyval = m_compose_table->data[j];
+             guint keycode = 0;
+             guint modifiers = 0;
+@@ -175,7 +176,7 @@ set_engine_cb (GObject *object, GAsyncResult *res, gpointer data)
+         for (i = 0;
+              i < (priv->first_n_seqs * index_stride);
+              i += index_stride) {
+-            for (j = i; j < i + (index_stride - 1); j++) {
++            for (j = i; j < i + (index_stride - 2); j++) {
+                 guint keyval = priv->data_first[j];
+                 guint keycode = 0;
+                 guint modifiers = 0;
+-- 
+2.28.0
+
+From 2fc1a028aa697f1320d85a76548cde12894bf9ab Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Mon, 26 Jul 2021 22:52:18 +0900
+Subject: [PATCH 2/6] src/ibusenginesimple: Multi_key to 0xB7
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Use · instead of ⎄ to display Multi_key in pre-edit.
+
+BUG=https://gitlab.gnome.org/GNOME/gtk/-/issues/3669
+BUG=https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/3220
+---
+ src/ibuscomposetable.c |  8 ++++++-
+ src/ibusenginesimple.c | 49 ++++++++++++++++++++----------------------
+ 2 files changed, 30 insertions(+), 27 deletions(-)
+
+diff --git a/src/ibuscomposetable.c b/src/ibuscomposetable.c
+index f85177a6..4ebf119b 100644
+--- a/src/ibuscomposetable.c
++++ b/src/ibuscomposetable.c
+@@ -1846,7 +1846,13 @@ ibus_keysym_to_unicode (guint16  keysym,
+     CASE_COMBINE (tilde,                        0x0303, 0x007E);
+     CASE_COMBINE (voiced_sound,                 0x3099, 0x309B);
+     case IBUS_KEY_Multi_key:
+-        return 0x2384;
++        /* We only show the Compose key visibly when it is the
++         * only glyph in the preedit, or when it occurs in the
++         * middle of the sequence. Sadly, the official character,
++         * U+2384, COMPOSITION SYMBOL, is bit too distracting, so
++         * we use U+00B7, MIDDLE DOT.
++         */
++        return 0x00B7;
+     default:;
+     }
+     return 0x0;
+diff --git a/src/ibusenginesimple.c b/src/ibusenginesimple.c
+index 4644620b..57ca10c4 100644
+--- a/src/ibusenginesimple.c
++++ b/src/ibusenginesimple.c
+@@ -289,32 +289,27 @@ ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple)
+ {
+     IBusEngineSimplePrivate *priv = simple->priv;
+ 
+-    gunichar outbuf[COMPOSE_BUFFER_SIZE + 1];
+-    int len = 0;
++    GString *s = g_string_new ("");
+ 
+     if (priv->in_hex_sequence || priv->in_emoji_sequence) {
+         int hexchars = 0;
+ 
+         if (priv->in_hex_sequence)
+-            outbuf[0] = L'u';
++            g_string_append_c (s, 'u');
+         else
+-            outbuf[0] = L'@';
+-
+-        len = 1;
++            g_string_append_c (s, '@');
+ 
+         while (priv->compose_buffer[hexchars] != 0) {
+-            outbuf[len] = ibus_keyval_to_unicode (
+-                priv->compose_buffer[hexchars]);
+-            ++len;
+-            ++hexchars;
++            g_string_append_unichar(
++                    s,
++                    ibus_keyval_to_unicode (priv->compose_buffer[hexchars++])
++            );
+         }
+-
+-        g_assert (len <= COMPOSE_BUFFER_SIZE);
+     } else if (priv->tentative_match) {
+-        outbuf[len++] = priv->tentative_match;
++        g_string_append_unichar(s, 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);
++        int 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);
+@@ -324,31 +319,33 @@ ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple)
+         while (priv->compose_buffer[hexchars] != 0) {
+             guint16 keysym = priv->compose_buffer[hexchars];
+             gunichar unichar = ibus_keysym_to_unicode (keysym, FALSE);
+-            if (unichar > 0)
+-                outbuf[len] = unichar;
+-            else
+-                outbuf[len] = ibus_keyval_to_unicode (keysym);
+-            if (!outbuf[len]) {
++            if (unichar > 0) {
++                g_string_append_unichar(s, unichar);
++            } else {
++                unichar = ibus_keyval_to_unicode (keysym);
++                g_string_append_unichar(s, unichar);
++            }
++            if (!unichar) {
+                 g_warning (
+                         "Not found alternative character of compose key 0x%X",
+                         priv->compose_buffer[hexchars]);
+             }
+-            ++len;
+             ++hexchars;
+         }
+-        g_assert (len <= IBUS_MAX_COMPOSE_LEN);
+     }
+ 
+-    outbuf[len] = L'\0';
+-    if (len == 0) {
++    if (s->len == 0) {
+         ibus_engine_hide_preedit_text ((IBusEngine *)simple);
+-    }
+-    else {
+-        IBusText *text = ibus_text_new_from_ucs4 (outbuf);
++    } else if (s->len >= G_MAXINT) {
++        g_warning ("%s is too long compose length: %lu", s->str, s->len);
++    } else {
++        int len = (int)s->len;
++        IBusText *text = ibus_text_new_from_string (s->str);
+         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);
+     }
++    g_string_free (s, TRUE);
+ }
+ 
+ 
+-- 
+2.28.0
+
+From 3e2609e68c9107ce7c65e2d5876bfdc9f0f8c854 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Mon, 26 Jul 2021 22:52:21 +0900
+Subject: [PATCH 3/6] src: Update ibuskeysyms.h for latest dead keys
+
+---
+ src/ibuskeysyms.h | 186 +++++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 185 insertions(+), 1 deletion(-)
+
+diff --git a/src/ibuskeysyms.h b/src/ibuskeysyms.h
+index d35eb4a7..2fb2ea19 100644
+--- a/src/ibuskeysyms.h
++++ b/src/ibuskeysyms.h
+@@ -274,6 +274,10 @@
+ #define IBUS_KEY_dead_invertedbreve 0xfe6d
+ #define IBUS_KEY_dead_belowcomma 0xfe6e
+ #define IBUS_KEY_dead_currency 0xfe6f
++#define IBUS_KEY_dead_lowline 0xfe90
++#define IBUS_KEY_dead_aboveverticalline 0xfe91
++#define IBUS_KEY_dead_belowverticalline 0xfe92
++#define IBUS_KEY_dead_longsolidusoverlay 0xfe93
+ #define IBUS_KEY_dead_a 0xfe80
+ #define IBUS_KEY_dead_A 0xfe81
+ #define IBUS_KEY_dead_e 0xfe82
+@@ -1201,6 +1205,7 @@
+ #define IBUS_KEY_leftdoublequotemark 0xad2
+ #define IBUS_KEY_rightdoublequotemark 0xad3
+ #define IBUS_KEY_prescription 0xad4
++#define IBUS_KEY_permille 0xad5
+ #define IBUS_KEY_minutes 0xad6
+ #define IBUS_KEY_seconds 0xad7
+ #define IBUS_KEY_latincross 0xad9
+@@ -1633,8 +1638,8 @@
+ #define IBUS_KEY_ocaron 0x10001d2
+ #define IBUS_KEY_obarred 0x1000275
+ #define IBUS_KEY_SCHWA 0x100018f
+-#define IBUS_KEY_EZH 0x10001b7
+ #define IBUS_KEY_schwa 0x1000259
++#define IBUS_KEY_EZH 0x10001b7
+ #define IBUS_KEY_ezh 0x1000292
+ #define IBUS_KEY_Lbelowdot 0x1001e36
+ #define IBUS_KEY_lbelowdot 0x1001e37
+@@ -2121,5 +2126,184 @@
+ #define IBUS_KEY_Sinh_ruu2 0x1000df2
+ #define IBUS_KEY_Sinh_luu2 0x1000df3
+ #define IBUS_KEY_Sinh_kunddaliya 0x1000df4
++#define IBUS_KEY_ModeLock 0x1008ff01
++#define IBUS_KEY_MonBrightnessUp 0x1008ff02
++#define IBUS_KEY_MonBrightnessDown 0x1008ff03
++#define IBUS_KEY_KbdLightOnOff 0x1008ff04
++#define IBUS_KEY_KbdBrightnessUp 0x1008ff05
++#define IBUS_KEY_KbdBrightnessDown 0x1008ff06
++#define IBUS_KEY_Standby 0x1008ff10
++#define IBUS_KEY_AudioLowerVolume 0x1008ff11
++#define IBUS_KEY_AudioMute 0x1008ff12
++#define IBUS_KEY_AudioRaiseVolume 0x1008ff13
++#define IBUS_KEY_AudioPlay 0x1008ff14
++#define IBUS_KEY_AudioStop 0x1008ff15
++#define IBUS_KEY_AudioPrev 0x1008ff16
++#define IBUS_KEY_AudioNext 0x1008ff17
++#define IBUS_KEY_HomePage 0x1008ff18
++#define IBUS_KEY_Mail 0x1008ff19
++#define IBUS_KEY_Start 0x1008ff1a
++#define IBUS_KEY_Search 0x1008ff1b
++#define IBUS_KEY_AudioRecord 0x1008ff1c
++#define IBUS_KEY_Calculator 0x1008ff1d
++#define IBUS_KEY_Memo 0x1008ff1e
++#define IBUS_KEY_ToDoList 0x1008ff1f
++#define IBUS_KEY_Calendar 0x1008ff20
++#define IBUS_KEY_PowerDown 0x1008ff21
++#define IBUS_KEY_ContrastAdjust 0x1008ff22
++#define IBUS_KEY_RockerUp 0x1008ff23
++#define IBUS_KEY_RockerDown 0x1008ff24
++#define IBUS_KEY_RockerEnter 0x1008ff25
++#define IBUS_KEY_Back 0x1008ff26
++#define IBUS_KEY_Forward 0x1008ff27
++#define IBUS_KEY_Stop 0x1008ff28
++#define IBUS_KEY_Refresh 0x1008ff29
++#define IBUS_KEY_PowerOff 0x1008ff2a
++#define IBUS_KEY_WakeUp 0x1008ff2b
++#define IBUS_KEY_Eject 0x1008ff2c
++#define IBUS_KEY_ScreenSaver 0x1008ff2d
++#define IBUS_KEY_WWW 0x1008ff2e
++#define IBUS_KEY_Sleep 0x1008ff2f
++#define IBUS_KEY_Favorites 0x1008ff30
++#define IBUS_KEY_AudioPause 0x1008ff31
++#define IBUS_KEY_AudioMedia 0x1008ff32
++#define IBUS_KEY_MyComputer 0x1008ff33
++#define IBUS_KEY_VendorHome 0x1008ff34
++#define IBUS_KEY_LightBulb 0x1008ff35
++#define IBUS_KEY_Shop 0x1008ff36
++#define IBUS_KEY_History 0x1008ff37
++#define IBUS_KEY_OpenURL 0x1008ff38
++#define IBUS_KEY_AddFavorite 0x1008ff39
++#define IBUS_KEY_HotLinks 0x1008ff3a
++#define IBUS_KEY_BrightnessAdjust 0x1008ff3b
++#define IBUS_KEY_Finance 0x1008ff3c
++#define IBUS_KEY_Community 0x1008ff3d
++#define IBUS_KEY_AudioRewind 0x1008ff3e
++#define IBUS_KEY_BackForward 0x1008ff3f
++#define IBUS_KEY_Launch0 0x1008ff40
++#define IBUS_KEY_Launch1 0x1008ff41
++#define IBUS_KEY_Launch2 0x1008ff42
++#define IBUS_KEY_Launch3 0x1008ff43
++#define IBUS_KEY_Launch4 0x1008ff44
++#define IBUS_KEY_Launch5 0x1008ff45
++#define IBUS_KEY_Launch6 0x1008ff46
++#define IBUS_KEY_Launch7 0x1008ff47
++#define IBUS_KEY_Launch8 0x1008ff48
++#define IBUS_KEY_Launch9 0x1008ff49
++#define IBUS_KEY_LaunchA 0x1008ff4a
++#define IBUS_KEY_LaunchB 0x1008ff4b
++#define IBUS_KEY_LaunchC 0x1008ff4c
++#define IBUS_KEY_LaunchD 0x1008ff4d
++#define IBUS_KEY_LaunchE 0x1008ff4e
++#define IBUS_KEY_LaunchF 0x1008ff4f
++#define IBUS_KEY_ApplicationLeft 0x1008ff50
++#define IBUS_KEY_ApplicationRight 0x1008ff51
++#define IBUS_KEY_Book 0x1008ff52
++#define IBUS_KEY_CD 0x1008ff53
++#define IBUS_KEY_WindowClear 0x1008ff55
++#define IBUS_KEY_Close 0x1008ff56
++#define IBUS_KEY_Copy 0x1008ff57
++#define IBUS_KEY_Cut 0x1008ff58
++#define IBUS_KEY_Display 0x1008ff59
++#define IBUS_KEY_DOS 0x1008ff5a
++#define IBUS_KEY_Documents 0x1008ff5b
++#define IBUS_KEY_Excel 0x1008ff5c
++#define IBUS_KEY_Explorer 0x1008ff5d
++#define IBUS_KEY_Game 0x1008ff5e
++#define IBUS_KEY_Go 0x1008ff5f
++#define IBUS_KEY_iTouch 0x1008ff60
++#define IBUS_KEY_LogOff 0x1008ff61
++#define IBUS_KEY_Market 0x1008ff62
++#define IBUS_KEY_Meeting 0x1008ff63
++#define IBUS_KEY_MenuKB 0x1008ff65
++#define IBUS_KEY_MenuPB 0x1008ff66
++#define IBUS_KEY_MySites 0x1008ff67
++#define IBUS_KEY_New 0x1008ff68
++#define IBUS_KEY_News 0x1008ff69
++#define IBUS_KEY_OfficeHome 0x1008ff6a
++#define IBUS_KEY_Open 0x1008ff6b
++#define IBUS_KEY_Option 0x1008ff6c
++#define IBUS_KEY_Paste 0x1008ff6d
++#define IBUS_KEY_Phone 0x1008ff6e
++#define IBUS_KEY_Reply 0x1008ff72
++#define IBUS_KEY_Reload 0x1008ff73
++#define IBUS_KEY_RotateWindows 0x1008ff74
++#define IBUS_KEY_RotationPB 0x1008ff75
++#define IBUS_KEY_RotationKB 0x1008ff76
++#define IBUS_KEY_Save 0x1008ff77
++#define IBUS_KEY_ScrollUp 0x1008ff78
++#define IBUS_KEY_ScrollDown 0x1008ff79
++#define IBUS_KEY_ScrollClick 0x1008ff7a
++#define IBUS_KEY_Send 0x1008ff7b
++#define IBUS_KEY_Spell 0x1008ff7c
++#define IBUS_KEY_SplitScreen 0x1008ff7d
++#define IBUS_KEY_Support 0x1008ff7e
++#define IBUS_KEY_TaskPane 0x1008ff7f
++#define IBUS_KEY_Terminal 0x1008ff80
++#define IBUS_KEY_Tools 0x1008ff81
++#define IBUS_KEY_Travel 0x1008ff82
++#define IBUS_KEY_UserPB 0x1008ff84
++#define IBUS_KEY_User1KB 0x1008ff85
++#define IBUS_KEY_User2KB 0x1008ff86
++#define IBUS_KEY_Video 0x1008ff87
++#define IBUS_KEY_WheelButton 0x1008ff88
++#define IBUS_KEY_Word 0x1008ff89
++#define IBUS_KEY_Xfer 0x1008ff8a
++#define IBUS_KEY_ZoomIn 0x1008ff8b
++#define IBUS_KEY_ZoomOut 0x1008ff8c
++#define IBUS_KEY_Away 0x1008ff8d
++#define IBUS_KEY_Messenger 0x1008ff8e
++#define IBUS_KEY_WebCam 0x1008ff8f
++#define IBUS_KEY_MailForward 0x1008ff90
++#define IBUS_KEY_Pictures 0x1008ff91
++#define IBUS_KEY_Music 0x1008ff92
++#define IBUS_KEY_Battery 0x1008ff93
++#define IBUS_KEY_Bluetooth 0x1008ff94
++#define IBUS_KEY_WLAN 0x1008ff95
++#define IBUS_KEY_UWB 0x1008ff96
++#define IBUS_KEY_AudioForward 0x1008ff97
++#define IBUS_KEY_AudioRepeat 0x1008ff98
++#define IBUS_KEY_AudioRandomPlay 0x1008ff99
++#define IBUS_KEY_Subtitle 0x1008ff9a
++#define IBUS_KEY_AudioCycleTrack 0x1008ff9b
++#define IBUS_KEY_CycleAngle 0x1008ff9c
++#define IBUS_KEY_FrameBack 0x1008ff9d
++#define IBUS_KEY_FrameForward 0x1008ff9e
++#define IBUS_KEY_Time 0x1008ff9f
++#define IBUS_KEY_SelectButton 0x1008ffa0
++#define IBUS_KEY_View 0x1008ffa1
++#define IBUS_KEY_TopMenu 0x1008ffa2
++#define IBUS_KEY_Red 0x1008ffa3
++#define IBUS_KEY_Green 0x1008ffa4
++#define IBUS_KEY_Yellow 0x1008ffa5
++#define IBUS_KEY_Blue 0x1008ffa6
++#define IBUS_KEY_Suspend 0x1008ffa7
++#define IBUS_KEY_Hibernate 0x1008ffa8
++#define IBUS_KEY_TouchpadToggle 0x1008ffa9
++#define IBUS_KEY_TouchpadOn 0x1008ffb0
++#define IBUS_KEY_TouchpadOff 0x1008ffb1
++#define IBUS_KEY_AudioMicMute 0x1008ffb2
++#define IBUS_KEY_Keyboard 0x1008ffb3
++#define IBUS_KEY_WWAN 0x1008ffb4
++#define IBUS_KEY_RFKill 0x1008ffb5
++#define IBUS_KEY_AudioPreset 0x1008ffb6
++#define IBUS_KEY_Switch_VT_1 0x1008fe01
++#define IBUS_KEY_Switch_VT_2 0x1008fe02
++#define IBUS_KEY_Switch_VT_3 0x1008fe03
++#define IBUS_KEY_Switch_VT_4 0x1008fe04
++#define IBUS_KEY_Switch_VT_5 0x1008fe05
++#define IBUS_KEY_Switch_VT_6 0x1008fe06
++#define IBUS_KEY_Switch_VT_7 0x1008fe07
++#define IBUS_KEY_Switch_VT_8 0x1008fe08
++#define IBUS_KEY_Switch_VT_9 0x1008fe09
++#define IBUS_KEY_Switch_VT_10 0x1008fe0a
++#define IBUS_KEY_Switch_VT_11 0x1008fe0b
++#define IBUS_KEY_Switch_VT_12 0x1008fe0c
++#define IBUS_KEY_Ungrab 0x1008fe20
++#define IBUS_KEY_ClearGrab 0x1008fe21
++#define IBUS_KEY_Next_VMode 0x1008fe22
++#define IBUS_KEY_Prev_VMode 0x1008fe23
++#define IBUS_KEY_LogWindowTree 0x1008fe24
++#define IBUS_KEY_LogGrabInfo 0x1008fe25
+ 
+ #endif /* __IBUS_KEYSYMS_H__ */
+-- 
+2.28.0
+
+From df495660a6da492fe0cac8f7fe748f25a1c3217c Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Mon, 26 Jul 2021 22:52:23 +0900
+Subject: [PATCH 4/6] src/ibusenginesimple: Change gint to int
+
+---
+ src/ibuscomposetable.c        | 128 +++++++++++++++++-----------------
+ src/ibusenginesimple.c        |  58 +++++++--------
+ src/ibusenginesimpleprivate.h |   6 +-
+ 3 files changed, 96 insertions(+), 96 deletions(-)
+
+diff --git a/src/ibuscomposetable.c b/src/ibuscomposetable.c
+index 4ebf119b..aa58f136 100644
+--- a/src/ibuscomposetable.c
++++ b/src/ibuscomposetable.c
+@@ -47,7 +47,7 @@
+ typedef struct {
+   gunichar     *sequence;
+   gunichar     *values;
+-  gchar        *comment;
++  char         *comment;
+ } IBusComposeData;
+ 
+ 
+@@ -85,7 +85,7 @@ unichar_length (gunichar *uni_array)
+ 
+ 
+ static gboolean
+-is_codepoint (const gchar *str)
++is_codepoint (const char *str)
+ {
+     int i;
+ 
+@@ -104,11 +104,11 @@ is_codepoint (const gchar *str)
+ 
+ static gboolean
+ parse_compose_value (IBusComposeData  *compose_data,
+-                     const gchar      *val,
+-                     const gchar      *line)
++                     const char       *val,
++                     const char       *line)
+ {
+-    gchar *head, *end, *p;
+-    gchar *ustr = NULL;
++    char *head, *end, *p;
++    char *ustr = NULL;
+     gunichar *uchars = NULL, *up;
+     GError *error = NULL;
+     int n_uchars = 0;
+@@ -184,10 +184,10 @@ fail:
+ 
+ static int
+ parse_compose_sequence (IBusComposeData *compose_data,
+-                        const gchar     *seq,
+-                        const gchar     *line)
++                        const char      *seq,
++                        const char      *line)
+ {
+-    gchar **words = g_strsplit (seq, "<", -1);
++    char **words = g_strsplit (seq, "<", -1);
+     int i;
+     int n = 0;
+ 
+@@ -198,9 +198,9 @@ parse_compose_sequence (IBusComposeData *compose_data,
+     }
+ 
+     for (i = 1; words[i] != NULL; i++) {
+-        gchar *start = words[i];
+-        gchar *end = index (words[i], '>');
+-        gchar *match;
++        char *start = words[i];
++        char *end = index (words[i], '>');
++        char *match;
+         gunichar codepoint;
+ 
+         if (words[i][0] == '\0')
+@@ -256,18 +256,18 @@ fail:
+ }
+ 
+ 
+-static gchar *
+-expand_include_path (const gchar *include_path) {
+-    gchar *out = strdup ("");
+-    const gchar *head, *i;
+-    gchar *former, *o;
++static char *
++expand_include_path (const char *include_path) {
++    char *out = strdup ("");
++    const char *head, *i;
++    char *former, *o;
+ 
+     for (head = i = include_path; *i; ++i) {
+         /* expand sequence */
+         if (*i == '%') {
+             switch (*(i + 1)) {
+             case 'H': { /* $HOME */
+-                const gchar *home = getenv ("HOME");
++                const char *home = getenv ("HOME");
+                 if (!home) {
+                     g_warning ("while parsing XCompose include target %s, "
+                                "%%H replacement failed because HOME is not "
+@@ -325,11 +325,11 @@ fail:
+ 
+ static void
+ parse_compose_line (GList       **compose_list,
+-                    const gchar  *line,
++                    const char   *line,
+                     int          *compose_len,
+-                    gchar        **include)
++                    char        **include)
+ {
+-    gchar **components = NULL;
++    char **components = NULL;
+     IBusComposeData *compose_data = NULL;
+     int l;
+ 
+@@ -395,13 +395,13 @@ fail:
+ }
+ 
+ 
+-static gchar *
++static char *
+ get_en_compose_file (void)
+ {
+-    gchar * const sys_langs[] = { "en_US.UTF-8", "en_US", "en.UTF-8",
+-                                  "en", NULL };
+-    gchar * const *sys_lang = NULL;
+-    gchar *path = NULL;
++    char * const sys_langs[] = { "en_US.UTF-8", "en_US", "en.UTF-8",
++                                 "en", NULL };
++    char * const *sys_lang = NULL;
++    char *path = NULL;
+     for (sys_lang = sys_langs; *sys_lang; sys_lang++) {
+         path = g_build_filename (X11_DATADIR, *sys_lang, "Compose", NULL);
+         if (g_file_test (path, G_FILE_TEST_EXISTS))
+@@ -413,11 +413,11 @@ get_en_compose_file (void)
+ 
+ 
+ static GList *
+-ibus_compose_list_parse_file (const gchar *compose_file,
+-                              int         *max_compose_len)
++ibus_compose_list_parse_file (const char *compose_file,
++                              int        *max_compose_len)
+ {
+-    gchar *contents = NULL;
+-    gchar **lines = NULL;
++    char *contents = NULL;
++    char **lines = NULL;
+     gsize length = 0;
+     GError *error = NULL;
+     GList *compose_list = NULL;
+@@ -435,14 +435,14 @@ ibus_compose_list_parse_file (const gchar *compose_file,
+     g_free (contents);
+     for (i = 0; lines[i] != NULL; i++) {
+         int compose_len = 0;
+-        gchar *include = NULL;
++        char *include = NULL;
+         parse_compose_line (&compose_list, lines[i], &compose_len, &include);
+         if (*max_compose_len < compose_len)
+             *max_compose_len = compose_len;
+         if (include && *include) {
+             GStatBuf buf_include = { 0, };
+             GStatBuf buf_parent = { 0, };
+-            gchar *en_compose;
++            char *en_compose;
+             errno = 0;
+             if (g_stat (include,  &buf_include)) {
+                 g_warning ("Cannot access %s: %s",
+@@ -588,7 +588,7 @@ ibus_compose_list_check_duplicated (GList *compose_list,
+ }
+ 
+ 
+-static gint
++static int
+ ibus_compose_data_compare (gpointer a,
+                            gpointer b,
+                            gpointer data)
+@@ -625,7 +625,7 @@ ibus_compose_list_print (GList *compose_list,
+     int i, j;
+     IBusComposeData *compose_data;
+     int total_size = 0;
+-    const gchar *keyval;
++    const char *keyval;
+ 
+     for (list = compose_list; list != NULL; list = list->next) {
+         compose_data = list->data;
+@@ -682,13 +682,13 @@ ibus_compose_table_data_hash (gconstpointer v,
+ }
+ 
+ 
+-static gchar *
++static char *
+ ibus_compose_hash_get_cache_path (guint32 hash)
+ {
+-    gchar *basename = NULL;
+-    const gchar *cache_dir;
+-    gchar *dir = NULL;
+-    gchar *path = NULL;
++    char *basename = NULL;
++    const char *cache_dir;
++    char *dir = NULL;
++    char *path = NULL;
+ 
+     basename = g_strdup_printf ("%08x.cache", hash);
+ 
+@@ -715,7 +715,7 @@ ibus_compose_hash_get_cache_path (guint32 hash)
+ static GVariant *
+ ibus_compose_table_serialize (IBusComposeTableEx *compose_table)
+ {
+-    const gchar *header = IBUS_COMPOSE_TABLE_MAGIC;
++    const char *header = IBUS_COMPOSE_TABLE_MAGIC;
+     const guint16 version = IBUS_COMPOSE_TABLE_VERSION;
+     guint16 max_seq_len;
+     guint16 index_stride;
+@@ -825,7 +825,7 @@ out_serialize:
+ }
+ 
+ 
+-static gint
++static int
+ ibus_compose_table_find (gconstpointer data1,
+                          gconstpointer data2)
+ {
+@@ -837,8 +837,8 @@ ibus_compose_table_find (gconstpointer data1,
+ 
+ 
+ static IBusComposeTableEx *
+-ibus_compose_table_deserialize (const gchar *contents,
+-                                gsize        length)
++ibus_compose_table_deserialize (const char *contents,
++                                gsize       length)
+ {
+     IBusComposeTableEx *retval = NULL;
+     GVariantType *type;
+@@ -846,7 +846,7 @@ ibus_compose_table_deserialize (const gchar *contents,
+     GVariant *variant_data_32bit_first = NULL;
+     GVariant *variant_data_32bit_second = NULL;
+     GVariant *variant_table = NULL;
+-    const gchar *header = NULL;
++    const char *header = NULL;
+     guint16 version = 0;
+     guint16 max_seq_len = 0;
+     guint16 n_seqs = 0;
+@@ -1020,8 +1020,8 @@ ibus_compose_table_load_cache (const gchar *compose_file)
+ {
+     IBusComposeTableEx *retval = NULL;
+     guint32 hash;
+-    gchar *path = NULL;
+-    gchar *contents = NULL;
++    char *path = NULL;
++    char *contents = NULL;
+     GStatBuf original_buf;
+     GStatBuf cache_buf;
+     gsize length = 0;
+@@ -1063,9 +1063,9 @@ ibus_compose_table_load_cache (const gchar *compose_file)
+ void
+ ibus_compose_table_save_cache (IBusComposeTableEx *compose_table)
+ {
+-    gchar *path = NULL;
++    char *path = NULL;
+     GVariant *variant_table = NULL;
+-    const gchar *contents = NULL;
++    const char *contents = NULL;
+     GError *error = NULL;
+     gsize length = 0;
+ 
+@@ -1392,13 +1392,13 @@ compare_seq (const void *key, const void *value)
+ gboolean
+ ibus_compose_table_check (const IBusComposeTableEx *table,
+                           guint16                  *compose_buffer,
+-                          gint                      n_compose,
++                          int                       n_compose,
+                           gboolean                 *compose_finish,
+                           gboolean                 *compose_match,
+                           GString                  *output,
+                           gboolean                  is_32bit)
+ {
+-    gint row_stride = table->max_seq_len + 2;
++    int row_stride = table->max_seq_len + 2;
+     guint16 *data_first;
+     int n_seqs;
+     guint16 *seq;
+@@ -1448,7 +1448,7 @@ ibus_compose_table_check (const IBusComposeTableEx *table,
+         gunichar value = 0;
+         int num = 0;
+         int index = 0;
+-        gchar *output_str = NULL;
++        char *output_str = NULL;
+         GError *error = NULL;
+ 
+         if (is_32bit) {
+@@ -1526,15 +1526,15 @@ gboolean
+ ibus_compose_table_compact_check (const IBusComposeTableCompactEx *table,
+                                   guint16
+                                                                *compose_buffer,
+-                                  gint                             n_compose,
++                                  int                              n_compose,
+                                   gboolean
+                                                                *compose_finish,
+                                   gunichar                       **output_chars)
+ {
+-    gint row_stride;
++    int row_stride;
+     guint16 *seq_index;
+     guint16 *seq;
+-    gint i;
++    int i;
+ 
+     if (compose_finish)
+         *compose_finish = FALSE;
+@@ -1654,14 +1654,14 @@ ibus_compose_table_compact_check (const IBusComposeTableCompactEx *table,
+  * permutations of the diacritic marks, then attempt to normalize.
+  */
+ static gboolean
+-check_normalize_nfc (gunichar* combination_buffer, gint n_compose)
++check_normalize_nfc (gunichar* combination_buffer, int n_compose)
+ {
+     gunichar combination_buffer_temp[IBUS_MAX_COMPOSE_LEN];
+-    gchar *combination_utf8_temp = NULL;
+-    gchar *nfc_temp = NULL;
+-    gint n_combinations;
++    char *combination_utf8_temp = NULL;
++    char *nfc_temp = NULL;
++    int n_combinations;
+     gunichar temp_swap;
+-    gint i;
++    int i;
+ 
+     n_combinations = 1;
+ 
+@@ -1703,8 +1703,8 @@ check_normalize_nfc (gunichar* combination_buffer, gint n_compose)
+         g_free (nfc_temp);
+ 
+         if (n_compose > 2) {
+-            gint j = i % (n_compose - 1) + 1;
+-            gint k = (i+1) % (n_compose - 1) + 1;
++            int j = i % (n_compose - 1) + 1;
++            int k = (i+1) % (n_compose - 1) + 1;
+             if (j >= IBUS_MAX_COMPOSE_LEN) {
+                 g_warning ("j >= IBUS_MAX_COMPOSE_LEN for " \
+                            "combination_buffer_temp");
+@@ -1729,13 +1729,13 @@ check_normalize_nfc (gunichar* combination_buffer, gint n_compose)
+ 
+ gboolean
+ ibus_check_algorithmically (const guint16 *compose_buffer,
+-                            gint           n_compose,
++                            int            n_compose,
+                             gunichar      *output_char)
+ 
+ {
+-    gint i;
++    int i;
+     gunichar combination_buffer[IBUS_MAX_COMPOSE_LEN];
+-    gchar *combination_utf8, *nfc;
++    char *combination_utf8, *nfc;
+ 
+     if (output_char)
+         *output_char = 0;
+diff --git a/src/ibusenginesimple.c b/src/ibusenginesimple.c
+index 57ca10c4..699cf0d4 100644
+--- a/src/ibusenginesimple.c
++++ b/src/ibusenginesimple.c
+@@ -79,8 +79,8 @@ typedef struct {
+ struct _IBusEngineSimplePrivate {
+     guint16            *compose_buffer;
+     gunichar            tentative_match;
+-    gchar              *tentative_emoji;
+-    gint                tentative_match_len;
++    char               *tentative_emoji;
++    int                 tentative_match_len;
+ 
+     guint               hex_mode_enabled : 1;
+     guint               in_hex_sequence : 1;
+@@ -142,7 +142,7 @@ static void     ibus_engine_simple_candidate_clicked
+ static void     ibus_engine_simple_commit_char (IBusEngineSimple    *simple,
+                                                 gunichar             ch);
+ static void     ibus_engine_simple_commit_str  (IBusEngineSimple    *simple,
+-                                                const gchar         *str);
++                                                const char          *str);
+ static void     ibus_engine_simple_update_preedit_text
+                                                (IBusEngineSimple    *simple);
+ 
+@@ -260,10 +260,10 @@ ibus_engine_simple_commit_char (IBusEngineSimple *simple,
+ 
+ static void
+ ibus_engine_simple_commit_str (IBusEngineSimple *simple,
+-                               const gchar      *str)
++                               const char       *str)
+ {
+     IBusEngineSimplePrivate *priv = simple->priv;
+-    gchar *backup_str;
++    char *backup_str;
+ 
+     g_return_if_fail (str && *str);
+ 
+@@ -368,15 +368,15 @@ ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple)
+ 
+ static gboolean
+ check_hex (IBusEngineSimple *simple,
+-           gint              n_compose)
++           int               n_compose)
+ {
+     IBusEngineSimplePrivate *priv = simple->priv;
+ 
+-    gint i;
++    int i;
+     GString *str;
+     gulong n;
+-    gchar *nptr = NULL;
+-    gchar buf[7];
++    char *nptr = NULL;
++    char buf[7];
+ 
+     CHECK_COMPOSE_BUFFER_LENGTH (n_compose);
+ 
+@@ -453,14 +453,14 @@ load_emoji_dict ()
+ 
+ static gboolean
+ check_emoji_table (IBusEngineSimple       *simple,
+-                   gint                    n_compose,
+-                   gint                    index)
++                   int                     n_compose,
++                   int                     index)
+ {
+     IBusEngineSimplePrivate *priv = simple->priv;
+     IBusEngineDict *emoji_dict = priv->emoji_dict;
+     GString *str = NULL;
+-    gint i;
+-    gchar buf[7];
++    int i;
++    char buf[7];
+     GSList *words = NULL;
+ 
+     g_assert (IBUS_IS_ENGINE_SIMPLE (simple));
+@@ -535,7 +535,7 @@ check_emoji_table (IBusEngineSimple       *simple,
+ 
+ static gboolean
+ no_sequence_matches (IBusEngineSimple *simple,
+-                     gint              n_compose,
++                     int               n_compose,
+                      guint             keyval,
+                      guint             keycode,
+                      guint             modifiers)
+@@ -550,7 +550,7 @@ no_sequence_matches (IBusEngineSimple *simple,
+      * match pending.
+      */
+     if (priv->tentative_match) {
+-        gint len = priv->tentative_match_len;
++        int len = priv->tentative_match_len;
+         int i;
+ 
+         ibus_engine_simple_commit_char (simple, priv->tentative_match);
+@@ -615,7 +615,7 @@ ibus_engine_simple_update_lookup_and_aux_table (IBusEngineSimple *simple)
+ {
+     IBusEngineSimplePrivate *priv;
+     guint index, candidates;
+-    gchar *aux_label = NULL;
++    char *aux_label = NULL;
+     IBusText *text = NULL;
+ 
+     g_return_if_fail (IBUS_IS_ENGINE_SIMPLE (simple));
+@@ -697,7 +697,7 @@ ibus_engine_simple_set_number_on_lookup_table (IBusEngineSimple *simple,
+ 
+ static gboolean
+ ibus_engine_simple_check_all_compose_table (IBusEngineSimple *simple,
+-                                            gint              n_compose)
++                                            int               n_compose)
+ {
+     IBusEngineSimplePrivate *priv = simple->priv;
+     gboolean compose_finish = FALSE;
+@@ -818,7 +818,7 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
+ {
+     IBusEngineSimple *simple = (IBusEngineSimple *)engine;
+     IBusEngineSimplePrivate *priv = simple->priv;
+-    gint n_compose = 0;
++    int n_compose = 0;
+     gboolean have_hex_mods;
+     gboolean is_hex_start = FALSE;
+     gboolean is_emoji_start = FALSE;
+@@ -828,7 +828,7 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
+     gboolean is_escape;
+     guint hex_keyval;
+     guint printable_keyval;
+-    gint i;
++    int i;
+ 
+     while (n_compose <= COMPOSE_BUFFER_SIZE && priv->compose_buffer[n_compose] != 0)
+         n_compose++;
+@@ -1274,7 +1274,7 @@ ibus_engine_simple_candidate_clicked (IBusEngine *engine,
+     IBusEngineSimple *simple = (IBusEngineSimple *)engine;
+     IBusEngineSimplePrivate *priv = simple->priv;
+     guint keyval;
+-    gint n_compose = 0;
++    int n_compose = 0;
+ 
+     if (priv->lookup_table == NULL || !priv->lookup_table_visible)
+         return;
+@@ -1308,18 +1308,18 @@ ibus_engine_simple_add_table_by_locale (IBusEngineSimple *simple,
+ {
+     /* Now ibus_engine_simple_add_compose_file() always returns TRUE. */
+     gboolean retval = TRUE;
+-    gchar *path = NULL;
+-    const gchar *home;
++    char *path = NULL;
++    const char *home;
+ #if GLIB_CHECK_VERSION (2, 58, 0)
+-    const gchar * const *langs;
+-    const gchar * const *lang = NULL;
++    const char * const *langs;
++    const char * const *lang = NULL;
+ #else
+-    const gchar *_locale;
+-    gchar **langs = NULL;
+-    gchar **lang = NULL;
++    const char *_locale;
++    char **langs = NULL;
++    char **lang = NULL;
+ #endif
+-    gchar * const sys_langs[] = { "el_gr", "fi_fi", "pt_br", NULL };
+-    gchar * const *sys_lang = NULL;
++    char * const sys_langs[] = { "el_gr", "fi_fi", "pt_br", NULL };
++    char * const *sys_lang = NULL;
+ 
+     if (locale == NULL) {
+         path = g_build_filename (g_get_user_config_dir (),
+diff --git a/src/ibusenginesimpleprivate.h b/src/ibusenginesimpleprivate.h
+index 47f2a3d4..5479e553 100644
+--- a/src/ibusenginesimpleprivate.h
++++ b/src/ibusenginesimpleprivate.h
+@@ -43,11 +43,11 @@ struct _IBusComposeTableCompactPrivate
+ };
+ 
+ gboolean ibus_check_algorithmically (const guint16              *compose_buffer,
+-                                     gint                        n_compose,
++                                     int                         n_compose,
+                                      gunichar                   *output);
+ gboolean ibus_compose_table_check   (const IBusComposeTableEx   *table,
+                                      guint16                    *compose_buffer,
+-                                     gint                        n_compose,
++                                     int                         n_compose,
+                                      gboolean                   *compose_finish,
+                                      gboolean                   *compose_match,
+                                      GString                    *output,
+@@ -56,7 +56,7 @@ gboolean ibus_compose_table_compact_check
+                                     (const IBusComposeTableCompactEx
+                                                                 *table,
+                                      guint16                    *compose_buffer,
+-                                     gint                        n_compose,
++                                     int                         n_compose,
+                                      gboolean                   *compose_finish,
+                                      gunichar                  **output_chars);
+ gunichar ibus_keysym_to_unicode     (guint16                     keysym,
+-- 
+2.28.0
+
+From 4259f16324d578aa2505cf50f2f23923d8d87e76 Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Mon, 26 Jul 2021 22:53:19 +0900
+Subject: [PATCH 5/6] src/ibusenginesimple: Make Compose preedit less intrusive
+
+Tweak the preedit display for Compose sequences to
+be not so distracting. We only show the Compose key
+when it occurs in the middle of the sequence or is
+the only key so far.
+
+BUG=https://gitlab.gnome.org/GNOME/gtk/-/issues/3669
+BUG=https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/3220
+BUG=https://blog.gtk.org/2021/03/24/input-revisited/
+---
+ src/ibuscomposetable.c        | 175 +++++++++++---------
+ src/ibusenginesimple.c        | 301 +++++++++++++++++++++++++---------
+ src/ibusenginesimpleprivate.h |   9 +-
+ 3 files changed, 325 insertions(+), 160 deletions(-)
+
+diff --git a/src/ibuscomposetable.c b/src/ibuscomposetable.c
+index aa58f136..2ad21551 100644
+--- a/src/ibuscomposetable.c
++++ b/src/ibuscomposetable.c
+@@ -1523,23 +1523,22 @@ compare_seq_index (const void *key, const void *value)
+  * IBusComposeData->values[] is the gunichar array.
+  */
+ gboolean
+-ibus_compose_table_compact_check (const IBusComposeTableCompactEx *table,
+-                                  guint16
+-                                                               *compose_buffer,
+-                                  int                              n_compose,
+-                                  gboolean
+-                                                               *compose_finish,
+-                                  gunichar                       **output_chars)
++ibus_compose_table_compact_check (const IBusComposeTableCompactEx
++                                                                *table,
++                                  guint16                       *compose_buffer,
++                                  int                            n_compose,
++                                  gboolean                      *compose_finish,
++                                  gunichar                     **output_chars)
+ {
+     int row_stride;
+     guint16 *seq_index;
+     guint16 *seq;
+     int i;
+ 
+-    if (compose_finish)
+-        *compose_finish = FALSE;
+-    if (output_chars)
+-        *output_chars = NULL;
++    /* compose_finish and output_chars should not be initialized because
++     * ibus_compose_table_check() is called at first and
++     * engine->priv->tentative_match will be preedit after this is called.
++     */
+ 
+     /* Will never match, if the sequence in the compose buffer is longer
+      * than the sequences in the table.  Further, compare_seq (key, val)
+@@ -1588,7 +1587,8 @@ ibus_compose_table_compact_check (const IBusComposeTableCompactEx *table,
+             if (compose_finish)
+                 *compose_finish = TRUE;
+             if (output_chars) {
+-                *output_chars = g_new (gunichar, length + 1);
++                if (!(*output_chars))
++                    *output_chars = g_new (gunichar, length + 1);
+                 for (j = 0; j < length; j++) {
+                     (*output_chars)[j] = table->priv->data2[index + j];
+                 }
+@@ -1622,7 +1622,8 @@ ibus_compose_table_compact_check (const IBusComposeTableCompactEx *table,
+             if (compose_finish)
+                 *compose_finish = TRUE;
+             if (output_chars) {
+-                *output_chars = g_new (gunichar, 2);
++                if (!(*output_chars))
++                    *output_chars = g_new (gunichar, 2);
+                 (*output_chars)[0] = seq[row_stride - 1];
+                 (*output_chars)[1] = 0;
+             }
+@@ -1635,16 +1636,6 @@ ibus_compose_table_compact_check (const IBusComposeTableCompactEx *table,
+ }
+ 
+ 
+-/* Checks if a keysym is a dead key. Dead key keysym values are defined in
+- * ../gdk/gdkkeysyms.h and the first is GDK_KEY_dead_grave. As X.Org is updated,
+- * more dead keys are added and we need to update the upper limit.
+- * Currently, the upper limit is GDK_KEY_dead_dasia+1. The +1 has to do with
+- * a temporary issue in the X.Org header files.
+- * In future versions it will be just the keysym (no +1).
+- */
+-#define IS_DEAD_KEY(k) \
+-      ((k) >= IBUS_KEY_dead_grave && (k) <= IBUS_KEY_dead_greek)
+-
+ /* This function receives a sequence of Unicode characters and tries to
+  * normalize it (NFC). We check for the case the the resulting string
+  * has length 1 (single character).
+@@ -1754,7 +1745,8 @@ ibus_check_algorithmically (const guint16 *compose_buffer,
+         i--;
+         while (i >= 0) {
+             combination_buffer[i+1] = ibus_keysym_to_unicode (compose_buffer[i],
+-                                                              TRUE);
++                                                              TRUE,
++                                                              NULL);
+             if (!combination_buffer[i+1]) {
+                 combination_buffer[i+1] =
+                         ibus_keyval_to_unicode (compose_buffer[i]);
+@@ -1786,66 +1778,91 @@ ibus_check_algorithmically (const guint16 *compose_buffer,
+ 
+ 
+ gunichar
+-ibus_keysym_to_unicode (guint16  keysym,
+-                        gboolean combining) {
+-#define CASE(keysym_suffix, unicode)                                    \
+-        case IBUS_KEY_dead_##keysym_suffix: return unicode
+-#define CASE_COMBINE(keysym_suffix, combined_unicode, isolated_unicode) \
+-        case IBUS_KEY_dead_##keysym_suffix:                             \
+-            if (combining)                                              \
+-                return combined_unicode;                                \
+-            else                                                        \
++ibus_keysym_to_unicode (guint16   keysym,
++                        gboolean  combining,
++                        gboolean *need_space) {
++#define CASE(keysym_suffix, unicode, sp)                                      \
++        case IBUS_KEY_dead_##keysym_suffix:                                   \
++            if (need_space)                                                   \
++                *need_space = sp;                                             \
++            return unicode
++#define CASE_COMBINE(keysym_suffix, combined_unicode, isolated_unicode, sp)   \
++        case IBUS_KEY_dead_##keysym_suffix:                                   \
++            if (need_space)                                                   \
++                *need_space = sp;                                             \
++            if (combining)                                                    \
++                return combined_unicode;                                      \
++            else                                                              \
+                 return isolated_unicode
+     switch (keysym) {
+-    CASE (a, 0x03041);
+-    CASE (A, 0x03042);
+-    CASE (i, 0x03043);
+-    CASE (I, 0x03044);
+-    CASE (u, 0x03045);
+-    CASE (U, 0x03046);
+-    CASE (e, 0x03047);
+-    CASE (E, 0x03048);
+-    CASE (o, 0x03049);
+-    CASE (O, 0x0304A);
+-    CASE         (abovecomma,                   0x0313);
+-    CASE_COMBINE (abovedot,                     0x0307, 0x02D9);
+-    CASE         (abovereversedcomma,           0x0314);
+-    CASE_COMBINE (abovering,                    0x030A, 0x02DA);
+-    CASE_COMBINE (acute,                        0x0301, 0x00B4);
+-    CASE         (belowbreve,                   0x032E);
+-    CASE_COMBINE (belowcircumflex,              0x032D, 0xA788);
+-    CASE_COMBINE (belowcomma,                   0x0326, 0x002C);
+-    CASE         (belowdiaeresis,               0x0324);
+-    CASE_COMBINE (belowdot,                     0x0323, 0x002E);
+-    CASE_COMBINE (belowmacron,                  0x0331, 0x02CD);
+-    CASE_COMBINE (belowring,                    0x030A, 0x02F3);
+-    CASE_COMBINE (belowtilde,                   0x0330, 0x02F7);
+-    CASE_COMBINE (breve,                        0x0306, 0x02D8);
+-    CASE_COMBINE (capital_schwa,                0x018F, 0x04D8);
+-    CASE_COMBINE (caron,                        0x030C, 0x02C7);
+-    CASE_COMBINE (cedilla,                      0x0327, 0x00B8);
+-    CASE_COMBINE (circumflex,                   0x0302, 0x005E);
+-    CASE         (currency,                     0x00A4);
++#ifdef IBUS_ENGLISH_DEAD_KEY
++    CASE (a, 0x0363, 1);
++    CASE (A, 0x0363, 1);
++    CASE (i, 0x0365, 1);
++    CASE (I, 0x0365, 1);
++    CASE (u, 0x0367, 1);
++    CASE (U, 0x0367, 1);
++    CASE (e, 0x0364, 1);
++    CASE (E, 0x0364, 1);
++    CASE (o, 0x0366, 1);
++    CASE (O, 0x0366, 1);
++#else
++    CASE (a, 0x3041, 0);
++    CASE (A, 0x3042, 0);
++    CASE (i, 0x3043, 0);
++    CASE (I, 0x3044, 0);
++    CASE (u, 0x3045, 0);
++    CASE (U, 0x3046, 0);
++    CASE (e, 0x3047, 0);
++    CASE (E, 0x3048, 0);
++    CASE (o, 0x3049, 0);
++    CASE (O, 0x304A, 0);
++#endif
++    CASE_COMBINE (abovecomma,                   0x0313, 0x02BC, 0);
++    CASE_COMBINE (abovedot,                     0x0307, 0x02D9, 0);
++    CASE_COMBINE (abovereversedcomma,           0x0314, 0x02BD, 0);
++    CASE_COMBINE (abovering,                    0x030A, 0x02DA, 0);
++    CASE_COMBINE (aboveverticalline,            0x030D, 0x02C8, 0);
++    CASE_COMBINE (acute,                        0x0301, 0x00B4, 0);
++    CASE         (belowbreve,                   0x032E, 1);
++    CASE_COMBINE (belowcircumflex,              0x032D, 0xA788, 0);
++    CASE_COMBINE (belowcomma,                   0x0326, 0x002C, 0);
++    CASE         (belowdiaeresis,               0x0324, 1);
++    CASE_COMBINE (belowdot,                     0x0323, 0x002E, 0);
++    CASE_COMBINE (belowmacron,                  0x0331, 0x02CD, 0);
++    CASE_COMBINE (belowring,                    0x030A, 0x02F3, 0);
++    CASE_COMBINE (belowtilde,                   0x0330, 0x02F7, 0);
++    CASE_COMBINE (belowverticalline,            0x0329, 0x02CC, 0);
++    CASE_COMBINE (breve,                        0x0306, 0x02D8, 0);
++    CASE_COMBINE (capital_schwa,                0x1DEA, 0x1D4A, 0);
++    CASE_COMBINE (caron,                        0x030C, 0x02C7, 0);
++    CASE_COMBINE (cedilla,                      0x0327, 0x00B8, 0);
++    CASE_COMBINE (circumflex,                   0x0302, 0x005E, 0);
++    CASE         (currency,                     0x00A4, 0);
+     // IBUS_KEY_dead_dasia == IBUS_KEY_dead_abovereversedcomma
+-    CASE_COMBINE (diaeresis,                    0x0308, 0x00A8);
+-    CASE_COMBINE (doubleacute,                  0x030B, 0x02DD);
+-    CASE_COMBINE (doublegrave,                  0x030F, 0x02F5);
+-    CASE_COMBINE (grave,                        0x0300, 0x0060);
+-    CASE         (greek,                        0x03BC);
+-    CASE         (hook,                         0x0309);
+-    CASE         (horn,                         0x031B);
+-    CASE         (invertedbreve,                0x032F);
+-    CASE_COMBINE (iota,                         0x0345, 0x037A);
+-    CASE_COMBINE (macron,                       0x0304, 0x00AF);
+-    CASE_COMBINE (ogonek,                       0x0328, 0x02DB);
++    CASE_COMBINE (diaeresis,                    0x0308, 0x00A8, 0);
++    CASE_COMBINE (doubleacute,                  0x030B, 0x02DD, 0);
++    CASE_COMBINE (doublegrave,                  0x030F, 0x02F5, 0);
++    CASE_COMBINE (grave,                        0x0300, 0x0060, 0);
++    CASE         (greek,                        0x03BC, 0);
++    CASE_COMBINE (hook,                         0x0309, 0x02C0, 0);
++    CASE         (horn,                         0x031B, 1);
++    CASE         (invertedbreve,                0x032F, 1);
++    CASE_COMBINE (iota,                         0x0345, 0x037A, 0);
++    CASE         (longsolidusoverlay,           0x0338, 1);
++    CASE_COMBINE (lowline,                      0x0332, 0x005F, 0);
++    CASE_COMBINE (macron,                       0x0304, 0x00AF, 0);
++    CASE_COMBINE (ogonek,                       0x0328, 0x02DB, 0);
+     // IBUS_KEY_dead_perispomeni == IBUS_KEY_dead_tilde
+     // IBUS_KEY_dead_psili == IBUS_KEY_dead_abovecomma
+-    CASE_COMBINE (semivoiced_sound,             0x309A, 0x309C);
+-    CASE_COMBINE (small_schwa,                  0x1D4A, 0x04D9);
+-    CASE         (stroke,                       0x002F);
+-    CASE_COMBINE (tilde,                        0x0303, 0x007E);
+-    CASE_COMBINE (voiced_sound,                 0x3099, 0x309B);
++    CASE_COMBINE (semivoiced_sound,             0x309A, 0x309C, 0);
++    CASE_COMBINE (small_schwa,                  0x1DEA, 0x1D4A, 0);
++    CASE         (stroke,                       0x0335, 1);
++    CASE_COMBINE (tilde,                        0x0303, 0x007E, 0);
++    CASE_COMBINE (voiced_sound,                 0x3099, 0x309B, 0);
+     case IBUS_KEY_Multi_key:
++        if (need_space)
++            *need_space = FALSE;
+         /* We only show the Compose key visibly when it is the
+          * only glyph in the preedit, or when it occurs in the
+          * middle of the sequence. Sadly, the official character,
+@@ -1854,6 +1871,8 @@ ibus_keysym_to_unicode (guint16  keysym,
+          */
+         return 0x00B7;
+     default:;
++        if (need_space)
++            *need_space = FALSE;
+     }
+     return 0x0;
+ #undef CASE
+diff --git a/src/ibusenginesimple.c b/src/ibusenginesimple.c
+index 699cf0d4..acfc0264 100644
+--- a/src/ibusenginesimple.c
++++ b/src/ibusenginesimple.c
+@@ -78,13 +78,14 @@ typedef struct {
+ 
+ struct _IBusEngineSimplePrivate {
+     guint16            *compose_buffer;
+-    gunichar            tentative_match;
+-    char               *tentative_emoji;
++    GString            *tentative_match;
+     int                 tentative_match_len;
++    char               *tentative_emoji;
+ 
+     guint               hex_mode_enabled : 1;
+     guint               in_hex_sequence : 1;
+     guint               in_emoji_sequence : 1;
++    guint               in_compose_sequence : 1;
+     guint               modifiers_dropped : 1;
+     IBusEngineDict     *emoji_dict;
+     IBusLookupTable    *lookup_table;
+@@ -178,6 +179,8 @@ ibus_engine_simple_init (IBusEngineSimple *simple)
+     simple->priv->hex_mode_enabled =
+         g_getenv("IBUS_ENABLE_CTRL_SHIFT_U") != NULL ||
+         g_getenv("IBUS_ENABLE_CONTROL_SHIFT_U") != NULL;
++    simple->priv->tentative_match = g_string_new ("");
++    simple->priv->tentative_match_len = 0;
+ }
+ 
+ 
+@@ -196,6 +199,9 @@ ibus_engine_simple_destroy (IBusEngineSimple *simple)
+     g_clear_object (&priv->lookup_table);
+     g_clear_pointer (&priv->compose_buffer, g_free);
+     g_clear_pointer (&priv->tentative_emoji, g_free);
++    g_string_free (priv->tentative_match, TRUE);
++    priv->tentative_match = NULL;
++    priv->tentative_match_len = 0;
+ 
+     IBUS_OBJECT_CLASS(ibus_engine_simple_parent_class)->destroy (
+         IBUS_OBJECT (simple));
+@@ -222,15 +228,15 @@ ibus_engine_simple_reset (IBusEngine *engine)
+ 
+     priv->compose_buffer[0] = 0;
+ 
+-    if (priv->tentative_match || priv->in_hex_sequence) {
++    if (priv->tentative_match->len > 0 || priv->in_hex_sequence) {
+         priv->in_hex_sequence = FALSE;
+-        priv->tentative_match = 0;
++        g_string_set_size (priv->tentative_match, 0);
+         priv->tentative_match_len = 0;
+     } else if (priv->tentative_emoji || priv->in_emoji_sequence) {
+         priv->in_emoji_sequence = FALSE;
+         g_clear_pointer (&priv->tentative_emoji, g_free);
+     } else if (!priv->in_hex_sequence && !priv->in_emoji_sequence) {
+-        priv->tentative_match = 0;
++        g_string_set_size (priv->tentative_match, 0);
+         priv->tentative_match_len = 0;
+     }
+     ibus_engine_hide_preedit_text ((IBusEngine *)simple);
+@@ -244,10 +250,17 @@ ibus_engine_simple_commit_char (IBusEngineSimple *simple,
+ 
+     IBusEngineSimplePrivate *priv = simple->priv;
+ 
+-    if (priv->tentative_match || priv->in_hex_sequence) {
+-        priv->in_hex_sequence = FALSE;
+-        priv->tentative_match = 0;
++    if (priv->in_hex_sequence ||
++        priv->tentative_match_len > 0 ||
++        priv->compose_buffer[0] != 0) {
++        g_string_set_size (priv->tentative_match, 0);
+         priv->tentative_match_len = 0;
++        priv->in_hex_sequence = FALSE;
++        priv->in_compose_sequence = FALSE;
++        priv->compose_buffer[0] = 0;
++        /* Don't call ibus_engine_simple_update_preedit_text() inside
++         * not to call it as duplilcated.
++         */
+     }
+     if (priv->tentative_emoji || priv->in_emoji_sequence) {
+         priv->in_emoji_sequence = FALSE;
+@@ -269,10 +282,17 @@ ibus_engine_simple_commit_str (IBusEngineSimple *simple,
+ 
+     backup_str = g_strdup (str);
+ 
+-    if (priv->tentative_match || priv->in_hex_sequence) {
+-        priv->in_hex_sequence = FALSE;
+-        priv->tentative_match = 0;
++    if (priv->in_hex_sequence ||
++        priv->tentative_match_len > 0 ||
++        priv->compose_buffer[0] != 0) {
++        g_string_set_size (priv->tentative_match, 0);
+         priv->tentative_match_len = 0;
++        priv->in_hex_sequence = FALSE;
++        priv->in_compose_sequence = FALSE;
++        priv->compose_buffer[0] = 0;
++        /* Don't call ibus_engine_simple_update_preedit_text() inside
++         * not to call it as duplilcated.
++         */
+     }
+     if (priv->tentative_emoji || priv->in_emoji_sequence) {
+         priv->in_emoji_sequence = FALSE;
+@@ -288,8 +308,8 @@ static void
+ ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple)
+ {
+     IBusEngineSimplePrivate *priv = simple->priv;
+-
+     GString *s = g_string_new ("");
++    int i, j;
+ 
+     if (priv->in_hex_sequence || priv->in_emoji_sequence) {
+         int hexchars = 0;
+@@ -305,32 +325,59 @@ ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple)
+                     ibus_keyval_to_unicode (priv->compose_buffer[hexchars++])
+             );
+         }
+-    } else if (priv->tentative_match) {
+-        g_string_append_unichar(s, priv->tentative_match);
+     } else if (priv->tentative_emoji && *priv->tentative_emoji) {
+         IBusText *text = ibus_text_new_from_string (priv->tentative_emoji);
+         int 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);
++        g_string_free (s, TRUE);
+         return;
+-    } else {
+-        int hexchars = 0;
+-        while (priv->compose_buffer[hexchars] != 0) {
+-            guint16 keysym = priv->compose_buffer[hexchars];
+-            gunichar unichar = ibus_keysym_to_unicode (keysym, FALSE);
+-            if (unichar > 0) {
+-                g_string_append_unichar(s, unichar);
+-            } else {
+-                unichar = ibus_keyval_to_unicode (keysym);
+-                g_string_append_unichar(s, unichar);
+-            }
+-            if (!unichar) {
+-                g_warning (
++    } else if (priv->in_compose_sequence) {
++        if (priv->tentative_match->len > 0 && priv->compose_buffer[0] != 0) {
++            g_string_append (s, priv->tentative_match->str);
++        } else {
++            for (i = 0; priv->compose_buffer[i]; ++i) {
++                guint16 keysym = priv->compose_buffer[i];
++                gboolean show_keysym = TRUE;
++                gboolean need_space = FALSE;
++                gunichar ch;
++
++                if (keysym == IBUS_KEY_Multi_key) {
++                    /* We only show the Compose key visibly when it is the
++                     * only glyph in the preedit, or when it occurs in the
++                     * middle of the sequence. Sadly, the official character,
++                     * U+2384, COMPOSITION SYMBOL, is bit too distracting, so
++                     * we use U+00B7, MIDDLE DOT.
++                     */
++                    for (j = i + 1; priv->compose_buffer[j]; j++) {
++                        if (priv->compose_buffer[j] != IBUS_KEY_Multi_key) {
++                            show_keysym = FALSE;
++                            break;
++                        }
++                    }
++                    if (!show_keysym)
++                        continue;
++                    ch = ibus_keysym_to_unicode (keysym, FALSE, NULL);
++                    g_string_append_unichar (s, ch);
++                } else if (IS_DEAD_KEY (keysym)) {
++                    ch = ibus_keysym_to_unicode (keysym, FALSE, &need_space);
++                    if (ch) {
++                        if (need_space)
++                            g_string_append_c (s, ' ');
++                        g_string_append_unichar (s, ch);
++                    }
++                } else {
++                    ch = ibus_keyval_to_unicode (keysym);
++                    if (ch)
++                        g_string_append_unichar(s, ch);
++                }
++                if (!ch) {
++                    g_warning (
+                         "Not found alternative character of compose key 0x%X",
+-                        priv->compose_buffer[hexchars]);
++                        priv->compose_buffer[i]);
++                }
+             }
+-            ++hexchars;
+         }
+     }
+ 
+@@ -380,7 +427,7 @@ check_hex (IBusEngineSimple *simple,
+ 
+     CHECK_COMPOSE_BUFFER_LENGTH (n_compose);
+ 
+-    priv->tentative_match = 0;
++    g_string_set_size (priv->tentative_match, 0);
+     priv->tentative_match_len = 0;
+ 
+     str = g_string_new (NULL);
+@@ -421,7 +468,8 @@ check_hex (IBusEngineSimple *simple,
+     }
+ 
+     if (g_unichar_validate (n)) {
+-        priv->tentative_match = n;
++        g_string_set_size (priv->tentative_match, 0);
++        g_string_append_unichar (priv->tentative_match, n);
+         priv->tentative_match_len = n_compose;
+     }
+ 
+@@ -546,21 +594,31 @@ no_sequence_matches (IBusEngineSimple *simple,
+ 
+     CHECK_COMPOSE_BUFFER_LENGTH (n_compose);
+ 
++    priv->in_compose_sequence = FALSE;
++
+     /* No compose sequences found, check first if we have a partial
+      * match pending.
+      */
+-    if (priv->tentative_match) {
++    if (priv->tentative_match_len > 0) {
++        guint16 *compose_buffer;
+         int len = priv->tentative_match_len;
+         int i;
++        char *str;
+ 
+-        ibus_engine_simple_commit_char (simple, priv->tentative_match);
+-        priv->compose_buffer[0] = 0;
++        compose_buffer = alloca (sizeof (guint16) * COMPOSE_BUFFER_SIZE);
++        memcpy (compose_buffer,
++                priv->compose_buffer,
++                sizeof (guint16) * COMPOSE_BUFFER_SIZE);
++
++        str = g_strdup (priv->tentative_match->str);
++        ibus_engine_simple_commit_str (simple, str);
++        g_free (str);
+         ibus_engine_simple_update_preedit_text (simple);
+ 
+         for (i=0; i < n_compose - len - 1; i++) {
+             ibus_engine_simple_process_key_event (
+                     (IBusEngine *)simple,
+-                    priv->compose_buffer[len + i],
++                    compose_buffer[len + i],
+                     0, 0);
+         }
+ 
+@@ -571,6 +629,26 @@ no_sequence_matches (IBusEngineSimple *simple,
+         priv->compose_buffer[0] = 0;
+         ibus_engine_simple_update_preedit_text (simple);
+     } else {
++        if (n_compose == 2 && IS_DEAD_KEY (priv->compose_buffer[0])) {
++            gboolean need_space = FALSE;
++            GString *s = g_string_new ("");
++            /* dead keys are never *really* dead */
++            ch = ibus_keysym_to_unicode (priv->compose_buffer[0],
++                                         FALSE, &need_space);
++            if (ch) {
++                if (need_space)
++                    g_string_append_c (s, ' ');
++                g_string_append_unichar (s, ch);
++            }
++            ch = ibus_keyval_to_unicode (priv->compose_buffer[1]);
++            if (ch != 0 && !g_unichar_iscntrl (ch))
++                g_string_append_unichar (s, ch);
++            ibus_engine_simple_commit_str (simple, s->str);
++            g_string_free (s, TRUE);
++            ibus_engine_simple_update_preedit_text (simple);
++            return TRUE;
++        }
++
+         priv->compose_buffer[0] = 0;
+         if (n_compose > 1) {
+             /* Invalid sequence */
+@@ -695,19 +773,46 @@ ibus_engine_simple_set_number_on_lookup_table (IBusEngineSimple *simple,
+     ibus_engine_simple_update_preedit_text (simple);
+ }
+ 
++
+ static gboolean
+ ibus_engine_simple_check_all_compose_table (IBusEngineSimple *simple,
+                                             int               n_compose)
+ {
+     IBusEngineSimplePrivate *priv = simple->priv;
++    GSList *tmp_list;
+     gboolean compose_finish = FALSE;
+     gboolean compose_match = FALSE;
+     GString *output = g_string_new ("");
+-    gboolean does_hit = FALSE;
++    gboolean success = FALSE;
+     gboolean is_32bit = FALSE;
+-    GSList *tmp_list;
+     gunichar *output_chars = NULL;
+-    gunichar output_char;
++    gunichar output_char = '\0';
++
++    if (n_compose == 2) {
++        /* Special-case deadkey-deadkey sequences.
++         * We are not doing chained deadkeys, so we
++         * want to commit the first key, and contine
++         * preediting with second.
++         */
++        if (IS_DEAD_KEY (priv->compose_buffer[0]) &&
++            IS_DEAD_KEY (priv->compose_buffer[1])) {
++            gboolean need_space = FALSE;
++            gunichar ch = ibus_keysym_to_unicode (priv->compose_buffer[0],
++                                                  FALSE, &need_space);
++            guint16 next = priv->compose_buffer[1];
++            if (ch) {
++                if (need_space)
++                    g_string_append_c (output, ' ');
++                g_string_append_unichar (output, ch);
++                ibus_engine_simple_commit_str (simple, output->str);
++                g_string_set_size (output, 0);
++
++                priv->compose_buffer[0] = next;
++                priv->compose_buffer[1] = 0;
++                n_compose = 1;
++            }
++        }
++    }
+ 
+     G_LOCK (global_tables);
+     tmp_list = global_tables;
+@@ -721,7 +826,7 @@ ibus_engine_simple_check_all_compose_table (IBusEngineSimple *simple,
+             &compose_match,
+             output,
+             is_32bit)) {
+-            does_hit = TRUE;
++            success = TRUE;
+             break;
+         }
+         is_32bit = TRUE;
+@@ -733,14 +838,15 @@ ibus_engine_simple_check_all_compose_table (IBusEngineSimple *simple,
+             &compose_match,
+             output,
+             is_32bit)) {
+-            does_hit = TRUE;
++            success = TRUE;
+             break;
+         }
+         tmp_list = tmp_list->next;
+     }
+     G_UNLOCK (global_tables);
+ 
+-    if (does_hit) {
++    if (success) {
++        priv->in_compose_sequence = TRUE;
+         if (compose_finish) {
+             if (compose_match) {
+                 if (is_32bit) {
+@@ -750,59 +856,89 @@ ibus_engine_simple_check_all_compose_table (IBusEngineSimple *simple,
+                                                     g_utf8_get_char (output->str));
+                 }
+             }
++            ibus_engine_simple_update_preedit_text (simple);
++            g_string_free (output, TRUE);
++            return TRUE;
+         } else if (compose_match) {
+-            priv->tentative_match = g_utf8_get_char (output->str);
++            g_string_assign (priv->tentative_match, output->str);
+             priv->tentative_match_len = n_compose;
++            ibus_engine_simple_update_preedit_text (simple);
++            g_string_free (output, TRUE);
++            return TRUE;
+         }
+-        ibus_engine_simple_update_preedit_text (simple);
+-        g_string_free (output, TRUE);
+-        return TRUE;
+     }
+     g_string_free (output, TRUE);
++    output = NULL;
+ 
+     if (ibus_compose_table_compact_check (&ibus_compose_table_compact,
+                                           priv->compose_buffer,
+                                           n_compose,
+                                           &compose_finish,
+                                           &output_chars)) {
++        priv->in_compose_sequence = TRUE;
+         if (compose_finish) {
+-            ibus_engine_simple_commit_char (simple, *output_chars);
++            if (success) {
++                g_string_append_unichar (priv->tentative_match, *output_chars);
++                priv->tentative_match_len = n_compose;
++            } else {
++                ibus_engine_simple_commit_char (simple, *output_chars);
++                priv->compose_buffer[0] = 0;
++            }
+             g_free (output_chars);
+-            priv->compose_buffer[0] = 0;
++            ibus_engine_simple_update_preedit_text (simple);
++            return TRUE;
+         }
+-        ibus_engine_simple_update_preedit_text (simple);
+-        return TRUE;
++        success = TRUE;
+     }
++    g_free (output_chars);
++    output_chars = NULL;
+     if (ibus_compose_table_compact_check (&ibus_compose_table_compact_32bit,
+                                           priv->compose_buffer,
+                                           n_compose,
+                                           &compose_finish,
+                                           &output_chars)) {
++        priv->in_compose_sequence = TRUE;
+         if (compose_finish) {
+             GError *error = NULL;
+             char *str = g_ucs4_to_utf8 (output_chars, -1, NULL, NULL, &error);
+-            if (str) {
+-                ibus_engine_simple_commit_str (simple, str);
+-                g_free (str);
+-            } else {
++            if (!str) {
+                 g_warning ("Failed to output multiple characters: %s",
+                            error->message);
+                 g_error_free (error);
++            } else if (success) {
++                g_string_append (priv->tentative_match, str);
++                priv->tentative_match_len = n_compose;
++            } else {
++                ibus_engine_simple_commit_str (simple, str);
++                priv->compose_buffer[0] = 0;
+             }
+-            priv->compose_buffer[0] = 0;
++            g_free (str);
++            g_free (output_chars);
++            ibus_engine_simple_update_preedit_text (simple);
++            return TRUE;
+         }
+-        g_free (output_chars);
+-        ibus_engine_simple_update_preedit_text (simple);
+-        return TRUE;
++        success = TRUE;
+     }
+-    g_assert (!output_chars);
++    g_free (output_chars);
++    output_chars = NULL;
+     if (ibus_check_algorithmically (priv->compose_buffer,
+                                     n_compose,
+                                     &output_char)) {
++        priv->in_compose_sequence = TRUE;
+         if (output_char) {
+-            ibus_engine_simple_commit_char (simple, output_char);
+-            priv->compose_buffer[0] = 0;
++            if (success) {
++                g_string_append_unichar (priv->tentative_match, output_char);
++                priv->tentative_match_len = n_compose;
++            } else  {
++                ibus_engine_simple_commit_char (simple, output_char);
++                priv->compose_buffer[0] = 0;
++            }
++            ibus_engine_simple_update_preedit_text (simple);
++            return TRUE;
+         }
++        success = TRUE;
++    }
++    if (success) {
+         ibus_engine_simple_update_preedit_text (simple);
+         return TRUE;
+     }
+@@ -810,6 +946,7 @@ ibus_engine_simple_check_all_compose_table (IBusEngineSimple *simple,
+     return FALSE;
+ }
+ 
++
+ static gboolean
+ ibus_engine_simple_process_key_event (IBusEngine *engine,
+                                       guint       keyval,
+@@ -841,16 +978,17 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
+         if (priv->in_hex_sequence &&
+             (keyval == IBUS_KEY_Control_L || keyval == IBUS_KEY_Control_R ||
+              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);
++            if (priv->tentative_match->len > 0) {
++                char *str = g_strdup (priv->tentative_match->str);
++                ibus_engine_simple_commit_str (simple, str);
++                g_free (str);
+                 ibus_engine_simple_update_preedit_text (simple);
+             } else if (n_compose == 0) {
+                 priv->modifiers_dropped = TRUE;
+             } else {
+                 /* invalid hex sequence */
+                 /* FIXME beep_window (event->window); */
+-                priv->tentative_match = 0;
++                g_string_set_size (priv->tentative_match, 0);
+                 g_clear_pointer (&priv->tentative_emoji, g_free);
+                 priv->in_hex_sequence = FALSE;
+                 priv->in_emoji_sequence = FALSE;
+@@ -873,7 +1011,7 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
+             } else {
+                 /* invalid hex sequence */
+                 /* FIXME beep_window (event->window); */
+-                priv->tentative_match = 0;
++                g_string_set_size (priv->tentative_match, 0);
+                 g_clear_pointer (&priv->tentative_emoji, g_free);
+                 priv->in_hex_sequence = FALSE;
+                 priv->in_emoji_sequence = FALSE;
+@@ -921,7 +1059,7 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
+         else if (is_hex_end) {
+             /* invalid hex sequence */
+             // beep_window (event->window);
+-            priv->tentative_match = 0;
++            g_string_set_size (priv->tentative_match, 0);
+             g_clear_pointer (&priv->tentative_emoji, g_free);
+             priv->in_hex_sequence = FALSE;
+             priv->in_emoji_sequence = FALSE;
+@@ -999,7 +1137,8 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
+         if (n_compose > 0) {
+             n_compose--;
+             priv->compose_buffer[n_compose] = 0;
+-            priv->tentative_match = 0;
++            g_string_set_size (priv->tentative_match, 0);
++            priv->tentative_match_len = 0;
+             ibus_engine_simple_check_all_compose_table (simple, n_compose);
+             return TRUE;
+         }
+@@ -1007,16 +1146,17 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
+ 
+     /* Check for hex sequence restart */
+     if (priv->in_hex_sequence && have_hex_mods && is_hex_start) {
+-        if (priv->tentative_match &&
+-            g_unichar_validate (priv->tentative_match)) {
+-            ibus_engine_simple_commit_char (simple, priv->tentative_match);
++        if (priv->tentative_match->len > 0 ) {
++            char *str = g_strdup (priv->tentative_match->str);
++            ibus_engine_simple_commit_str (simple, str);
++            g_free (str);
+             ibus_engine_simple_update_preedit_text (simple);
+         }
+         else {
+             /* invalid hex sequence */
+             if (n_compose > 0) {
+                 // FIXME beep_window (event->window);
+-                priv->tentative_match = 0;
++                g_string_set_size (priv->tentative_match, 0);
+                 priv->in_hex_sequence = FALSE;
+                 priv->compose_buffer[0] = 0;
+             }
+@@ -1044,7 +1184,7 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
+         priv->in_hex_sequence = TRUE;
+         priv->in_emoji_sequence = FALSE;
+         priv->modifiers_dropped = FALSE;
+-        priv->tentative_match = 0;
++        g_string_set_size (priv->tentative_match, 0);
+         g_clear_pointer (&priv->tentative_emoji, g_free);
+ 
+         // g_debug ("Start HEX MODE");
+@@ -1058,7 +1198,7 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
+         priv->in_hex_sequence = FALSE;
+         priv->in_emoji_sequence = TRUE;
+         priv->modifiers_dropped = FALSE;
+-        priv->tentative_match = 0;
++        g_string_set_size (priv->tentative_match, 0);
+         g_clear_pointer (&priv->tentative_emoji, g_free);
+ 
+         // g_debug ("Start HEX MODE");
+@@ -1068,7 +1208,6 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
+         return TRUE;
+     }
+ 
+-    /* Then, check for compose sequences */
+     if (priv->in_hex_sequence) {
+         if (hex_keyval) {
+             SET_COMPOSE_BUFFER_ELEMENT_NEXT (priv->compose_buffer,
+@@ -1133,17 +1272,17 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
+         if (have_hex_mods) {
+             /* space or return ends the sequence, and we eat the key */
+             if (n_compose > 0 && is_hex_end) {
+-                if (priv->tentative_match &&
+-                    g_unichar_validate (priv->tentative_match)) {
+-                    ibus_engine_simple_commit_char (simple,
+-                            priv->tentative_match);
+-                    priv->compose_buffer[0] = 0;
++                if (priv->tentative_match->len > 0) {
++                    char *str = g_strdup (priv->tentative_match->str);
++                    ibus_engine_simple_commit_str (simple, str);
++                    g_free (str);
+                     ibus_engine_simple_update_preedit_text (simple);
++                    return TRUE;
+                 } else {
+                     // FIXME
+                     /* invalid hex sequence */
+                     // beep_window (event->window);
+-                    priv->tentative_match = 0;
++                    g_string_set_size (priv->tentative_match, 0);
+                     priv->in_hex_sequence = FALSE;
+                     priv->compose_buffer[0] = 0;
+                 }
+@@ -1234,7 +1373,7 @@ ibus_engine_simple_process_key_event (IBusEngine *engine,
+ 
+             return TRUE;
+         }
+-    } else {
++    } else { /* Then, check for compose sequences */
+         if (ibus_engine_simple_check_all_compose_table (simple, n_compose))
+             return TRUE;
+     }
+diff --git a/src/ibusenginesimpleprivate.h b/src/ibusenginesimpleprivate.h
+index 5479e553..ad93d2d2 100644
+--- a/src/ibusenginesimpleprivate.h
++++ b/src/ibusenginesimpleprivate.h
+@@ -26,6 +26,12 @@
+ 
+ G_BEGIN_DECLS
+ 
++/* Checks if a keysym is a dead key. Dead key keysym values are defined in
++ * ibuskeysyms.h and the first is GDK_KEY_dead_grave.
++ */
++#define IS_DEAD_KEY(k) \
++      ((k) >= IBUS_KEY_dead_grave && (k) <= IBUS_KEY_dead_greek)
++
+ extern const IBusComposeTableCompactEx ibus_compose_table_compact;
+ extern const IBusComposeTableCompactEx ibus_compose_table_compact_32bit;
+ 
+@@ -60,7 +66,8 @@ gboolean ibus_compose_table_compact_check
+                                      gboolean                   *compose_finish,
+                                      gunichar                  **output_chars);
+ gunichar ibus_keysym_to_unicode     (guint16                     keysym,
+-                                     gboolean                    combining);
++                                     gboolean                    combining,
++                                     gboolean                   *need_space);
+ 
+ G_END_DECLS
+ 
+-- 
+2.28.0
+
+From 17ae266cade41e795534532acefc85716bb309ef Mon Sep 17 00:00:00 2001
+From: fujiwarat <takao.fujiwara1@gmail.com>
+Date: Mon, 26 Jul 2021 22:53:34 +0900
+Subject: [PATCH 6/6] Code reviews
+
+- Use fstat() and fchmod() but not stat() and chmod() to
+  fix race conditions
+- Avoid to use after free
+- Fix dereference of IBusComposeTable->priv
+- Fix to divide by zero
+---
+ src/ibusbus.c                            | 23 ++++++++++++++++++-----
+ src/ibuscomposetable.c                   |  8 ++++++--
+ src/tests/ibus-desktop-testing-runner.in |  6 +++---
+ 3 files changed, 27 insertions(+), 10 deletions(-)
+
+diff --git a/src/ibusbus.c b/src/ibusbus.c
+index e9b0bcbb..47400cb8 100644
+--- a/src/ibusbus.c
++++ b/src/ibusbus.c
+@@ -21,13 +21,14 @@
+  * USA
+  */
+ 
+-#include "ibusbus.h"
+ #include <errno.h>
++#include <fcntl.h>
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <unistd.h>
+ #include <glib/gstdio.h>
+ #include <gio/gio.h>
++#include "ibusbus.h"
+ #include "ibusmarshalers.h"
+ #include "ibusinternal.h"
+ #include "ibusshare.h"
+@@ -537,7 +538,8 @@ static void
+ ibus_bus_init (IBusBus *bus)
+ {
+     struct stat buf;
+-    gchar *path;
++    char *path;
++    int fd;
+ 
+     bus->priv = IBUS_BUS_GET_PRIVATE (bus);
+ 
+@@ -562,20 +564,31 @@ ibus_bus_init (IBusBus *bus)
+         return;
+     }
+ 
+-    if (stat (path, &buf) == 0) {
++    errno = 0;
++    if ((fd = open (path, O_RDONLY | O_DIRECTORY, S_IRWXU)) == -1) {
++        g_warning ("open %s failed: %s", path, g_strerror (errno));
++        g_free (path);
++        return;
++    }
++    /* TOCTOU: Use fstat() and fchmod() but not stat() and chmod().
++     * because it can cause a time-of-check, time-of-use race condition.
++     */
++    if (fstat (fd, &buf) == 0) {
+         if (buf.st_uid != getuid ()) {
+             g_warning ("The owner of %s is not %s!",
+                        path, ibus_get_user_name ());
++            close (fd);
+             g_free (path);
+             return;
+         }
+         if (buf.st_mode != (S_IFDIR | S_IRWXU)) {
+             errno = 0;
+-            if (g_chmod (path, 0700))
+-                g_warning ("chmod failed: %s", errno ? g_strerror (errno) : "");
++            if (fchmod (fd, S_IRWXU))
++                g_warning ("chmod failed: %s", g_strerror (errno));
+         }
+     }
+ 
++    close (fd);
+     g_free (path);
+ }
+ 
+diff --git a/src/ibuscomposetable.c b/src/ibuscomposetable.c
+index 2ad21551..a0b0befc 100644
+--- a/src/ibuscomposetable.c
++++ b/src/ibuscomposetable.c
+@@ -406,7 +406,7 @@ get_en_compose_file (void)
+         path = g_build_filename (X11_DATADIR, *sys_lang, "Compose", NULL);
+         if (g_file_test (path, G_FILE_TEST_EXISTS))
+             break;
+-        g_free (path);
++        g_clear_pointer (&path, g_free);
+     }
+     return path;
+ }
+@@ -975,7 +975,10 @@ ibus_compose_table_deserialize (const char *contents,
+         goto out_load_cache;
+     }
+     if (data_length) {
+-        retval->priv = g_new0 (IBusComposeTablePrivate, 1);
++        if (!(retval->priv = g_new0 (IBusComposeTablePrivate, 1))) {
++            g_warning ("Failed g_new");
++            goto out_load_cache;
++        }
+         retval->priv->data_first = g_new (guint16, data_length);
+         memcpy (retval->priv->data_first,
+                 data_32bit_first, data_length * sizeof (guint16));
+@@ -1565,6 +1568,7 @@ ibus_compose_table_compact_check (const IBusComposeTableCompactEx
+             row_stride = i + 2;
+ 
+             if (seq_index[i + 1] - seq_index[i] > 0) {
++                g_assert (row_stride);
+                 seq = bsearch (compose_buffer + 1,
+                                table->data + seq_index[i],
+                                (seq_index[i + 1] - seq_index[i]) / row_stride,
+diff --git a/src/tests/ibus-desktop-testing-runner.in b/src/tests/ibus-desktop-testing-runner.in
+index 0ef72c03..c1016703 100755
+--- a/src/tests/ibus-desktop-testing-runner.in
++++ b/src/tests/ibus-desktop-testing-runner.in
+@@ -220,7 +220,7 @@ init_gnome()
+     # G_MESSAGES_DEBUG=all or G_MESSAGES_DEBUG=GLib-GIO-DEBUG would append
+     # debug messages to gsettings output and could not get the result correctly.
+     backup_G_MESSAGES_DEBUG="$G_MESSAGES_DEBUG"
+-    export -n G_MESSAGES_DEBUG=''
++    unset G_MESSAGES_DEBUG
+     # Disable Tour dialog to get focus
+     V=`gsettings get org.gnome.shell welcome-dialog-last-shown-version`
+     if [ x"$V" = x"''" ] ; then
+@@ -231,8 +231,8 @@ init_gnome()
+     NO_SYS_DIR=/usr/share/gnome-shell/extensions/no-overview@fthx
+     NO_USER_DIR=$HOME/.local/share/gnome-shell/extensions/no-overview@fthx
+     if [ ! -d $NO_SYS_DIR ] && [ ! -d $NO_USER_DIR ] ; then
+-        mkdir -p `dirname $NO_USER_DIR`
+-        cp -R no-overview@fthx `dirname $NO_USER_DIR`
++        mkdir -p "`dirname $NO_USER_DIR`"
++        cp -R "no-overview@fthx" "`dirname $NO_USER_DIR`"
+     fi
+     V=`gsettings get org.gnome.shell disable-user-extensions`
+     if [ x"$V" = x"true" ] ; then
+-- 
+2.28.0
+

diff --git a/ibus.spec b/ibus.spec
index 41f4cb5..4ba4a52 100644
--- a/ibus.spec
+++ b/ibus.spec
@@ -38,7 +38,7 @@
 
 Name:           ibus
 Version:        1.5.24
-Release:        11%{?dist}
+Release:        12%{?dist}
 Summary:        Intelligent Input Bus for Linux OS
 License:        LGPLv2+
 URL:            https://github.com/ibus/%name/wiki
@@ -510,6 +510,11 @@ dconf update || :
 %{_datadir}/installed-tests/ibus
 
 %changelog
+* Mon Jul 26 2021 Takao Fujiwara <tfujiwar@redhat.com> - 1.5.24-12
+- Search language name in engine list in ibus-setup
+- Set Multi_key to 0xB7 in compose preedit
+- Make Compose preedit less intrusive
+
 * Thu Jul 22 2021 Fedora Release Engineering <releng@fedoraproject.org> - 1.5.24-11
 - Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild
 

                 reply	other threads:[~2026-05-31  2:07 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178019325957.1.15102900056209812634.rpms-ibus-fb4b95e6ad20@fedoraproject.org \
    --to=tfujiwar@redhat.com \
    --cc=git-commits@fedoraproject.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox