Merge branch 'input-tweaks' into 'master'

imcontext: Tweak Compose sequence preedit

Closes #10, #4127, and #4124

See merge request GNOME/gtk!3799
diff --git a/gtk/compose/chars b/gtk/compose/chars
index 9f9b153..f389723 100644
--- a/gtk/compose/chars
+++ b/gtk/compose/chars
Binary files differ
diff --git a/gtk/compose/gtkcomposedata.h b/gtk/compose/gtkcomposedata.h
index 057347a..4426962 100644
--- a/gtk/compose/gtkcomposedata.h
+++ b/gtk/compose/gtkcomposedata.h
@@ -3,7 +3,7 @@
 
 #define MAX_SEQ_LEN 5
 #define N_INDEX_SIZE 30
-#define DATA_SIZE 16521
-#define N_CHARS 1572
+#define DATA_SIZE 16447
+#define N_CHARS 1241
 
 #endif
diff --git a/gtk/compose/sequences b/gtk/compose/sequences
index 1db7a6c..39984d4 100644
--- a/gtk/compose/sequences
+++ b/gtk/compose/sequences
Binary files differ
diff --git a/gtk/gtkcomposetable.c b/gtk/gtkcomposetable.c
index c95b153..464bf18 100644
--- a/gtk/gtkcomposetable.c
+++ b/gtk/gtkcomposetable.c
@@ -486,6 +486,9 @@
   GHashTableIter iter;
   gunichar *sequence;
   char *value;
+  GString *output;
+
+  output = g_string_new ("");
 
   g_hash_table_iter_init (&iter, parser->sequences);
   while (g_hash_table_iter_next (&iter, (gpointer *)&sequence, (gpointer *)&value))
@@ -493,8 +496,6 @@
       static guint16 keysyms[MAX_COMPOSE_LEN + 1];
       int i;
       int n_compose = 0;
-      gunichar output_char;
-      char buf[8] = { 0, };
       gboolean remove_sequence = FALSE;
 
       if (value[0] == '\0')
@@ -529,10 +530,9 @@
           n_compose++;
         }
 
-      if (gtk_check_algorithmically (keysyms, n_compose, &output_char))
+      if (gtk_check_algorithmically (keysyms, n_compose, output))
         {
-          g_unichar_to_utf8 (output_char, buf);
-          if (strcmp (value, buf) == 0)
+          if (strcmp (value, output->str) == 0)
             remove_sequence = TRUE;
         }
 
@@ -540,6 +540,8 @@
       if (remove_sequence)
         g_hash_table_iter_remove (&iter);
     }
+
+  g_string_free (output, TRUE);
 }
 
 static void
@@ -1069,22 +1071,22 @@
   return compose_table;
 }
 
-static const char *prefix =
-  "# GTK has rewritten this file to add the line:\n"
-  "\n"
-  "include \"%L\"\n"
-  "\n"
-  "# This is necessary to add your own Compose sequences\n"
-  "# in addition to the builtin sequences of GTK. If this\n"
-  "# is not what you want, just remove that line.\n"
-  "#\n"
-  "# A backup of the previous file contents has been made.\n"
-  "\n"
-  "\n";
-
 static gboolean
 rewrite_compose_file (const char *compose_file)
 {
+  static const char *prefix =
+    "# GTK has rewritten this file to add the line:\n"
+    "\n"
+    "include \"%L\"\n"
+    "\n"
+    "# This is necessary to add your own Compose sequences\n"
+    "# in addition to the builtin sequences of GTK. If this\n"
+    "# is not what you want, just remove that line.\n"
+    "#\n"
+    "# A backup of the previous file contents has been made.\n"
+    "\n"
+    "\n";
+
   char *path = NULL;
   char *content = NULL;
   gsize content_len;
@@ -1353,6 +1355,47 @@
 }
 
 void
+gtk_compose_table_get_prefix (const GtkComposeTable *table,
+                              const guint16         *compose_buffer,
+                              int                    n_compose,
+                              int                   *prefix)
+{
+  int index_stride = table->max_seq_len + 1;
+  int p = 0;
+
+  for (int idx = 0; idx < table->n_index_size; idx++)
+    {
+      const guint16 *seq_index = table->data + (idx * index_stride);
+
+      if (seq_index[0] == compose_buffer[0])
+        {
+          p = 1;
+
+          for (int i = 1; i < table->max_seq_len; i++)
+            {
+              int len = i + 1;
+
+              for (int j = seq_index[i]; j < seq_index[i + 1]; j += len)
+                {
+                  int k;
+
+                  for (k = 0; k < MIN (len, n_compose) - 1; k++)
+                    {
+                      if (compose_buffer[k + 1] != (gunichar) table->data[j + k])
+                        break;
+                    }
+                  p = MAX (p, k + 1);
+                }
+            }
+
+          break;
+        }
+    }
+
+  *prefix = p;
+}
+
+void
 gtk_compose_table_foreach (const GtkComposeTable      *table,
                            GtkComposeSequenceCallback  callback,
                            gpointer                    data)
@@ -1419,119 +1462,56 @@
 #define IS_DEAD_KEY(k) \
     ((k) >= GDK_KEY_dead_grave && (k) <= GDK_KEY_dead_greek)
 
-/* This function receives a sequence of Unicode characters and tries to
- * normalize it (NFC). We check for the case where 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,
-                     int       n_compose)
-{
-  gunichar *combination_buffer_temp;
-  char *combination_utf8_temp = NULL;
-  char *nfc_temp = NULL;
-  int n_combinations;
-  gunichar temp_swap;
-  int i;
-
-  combination_buffer_temp = g_alloca (n_compose * sizeof (gunichar));
-
-  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, n_compose * 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, n_compose, 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, n_compose * 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)
-        {
-          temp_swap = combination_buffer_temp[i % (n_compose - 1) + 1];
-          combination_buffer_temp[i % (n_compose - 1) + 1] = combination_buffer_temp[(i+1) % (n_compose - 1) + 1];
-          combination_buffer_temp[(i+1) % (n_compose - 1) + 1] = temp_swap;
-        }
-      else
-        break;
-    }
-
-  return FALSE;
-}
-
 gboolean
 gtk_check_algorithmically (const guint16 *compose_buffer,
                            int            n_compose,
-                           gunichar      *output_char)
+                           GString       *output)
 
 {
   int i;
-  gunichar *combination_buffer;
-  char *combination_utf8, *nfc;
 
-  combination_buffer = alloca (sizeof (gunichar) * (n_compose + 1));
-
-  if (output_char)
-    *output_char = 0;
+  g_string_set_size (output, 0);
 
   for (i = 0; i < n_compose && IS_DEAD_KEY (compose_buffer[i]); i++)
     ;
 
-  /* Allow at most 2 dead keys */
-  if (i > 2)
-    return FALSE;
-
-  /* Can't combine if there's no base character */
+  /* Can't combine if there's no base character: incomplete sequence */
   if (i == n_compose)
     return TRUE;
 
   if (i > 0 && i == n_compose - 1)
     {
-      combination_buffer[0] = gdk_keyval_to_unicode (compose_buffer[i]);
-      combination_buffer[n_compose] = 0;
+      GString *input;
+      char *nfc;
+      gunichar ch;
+
+      ch = gdk_keyval_to_unicode (compose_buffer[i]);
+
+      /* We don't allow combining with non-letters */
+      if (!g_unichar_isalpha (ch))
+        return FALSE;
+
+      input = g_string_sized_new (4 * n_compose);
+
+      g_string_append_unichar (input, ch);
+
       i--;
       while (i >= 0)
         {
           switch (compose_buffer[i])
             {
 #define CASE(keysym, unicode) \
-            case GDK_KEY_dead_##keysym: combination_buffer[i+1] = unicode; break
+            case GDK_KEY_dead_##keysym: g_string_append_unichar (input, unicode); break
 
             CASE (grave, 0x0300);
             CASE (acute, 0x0301);
             CASE (circumflex, 0x0302);
-            CASE (tilde, 0x0303);       /* Also used with perispomeni, 0x342. */
+            case GDK_KEY_dead_tilde:
+              if (g_unichar_get_script (ch) == G_UNICODE_SCRIPT_GREEK)
+                g_string_append_unichar (input, 0x342); /* combining perispomeni */
+              else
+                g_string_append_unichar (input, 0x303); /* combining tilde */
+              break;
             CASE (macron, 0x0304);
             CASE (breve, 0x0306);
             CASE (abovedot, 0x0307);
@@ -1549,7 +1529,7 @@
             CASE (horn, 0x031B);        /* Legacy use for psili, 0x313 (or 0x343). */
             CASE (stroke, 0x335);
             CASE (abovecomma, 0x0313);  /* Equivalent to psili */
-            CASE (abovereversedcomma, 0x0314); /* Equivalent to dasia */
+            CASE (abovereversedcomma, 0x0314);   /* Equivalent to dasia */
             CASE (doublegrave, 0x30F);
             CASE (belowring, 0x325);
             CASE (belowmacron, 0x331);
@@ -1577,27 +1557,20 @@
             CASE (capital_schwa, 0x1DEA);
 #undef CASE
             default:
-              combination_buffer[i+1] = gdk_keyval_to_unicode (compose_buffer[i]);
+              g_string_append_unichar (input, gdk_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);
+      nfc = g_utf8_normalize (input->str, input->len, G_NORMALIZE_NFC);
 
-          if (output_char)
-            *output_char = g_utf8_get_char (nfc);
+      g_string_assign (output, nfc);
 
-          g_free (combination_utf8);
-          g_free (nfc);
+      g_free (nfc);
 
-          return TRUE;
-        }
+      g_string_free (input, TRUE);
+
+      return TRUE;
     }
 
   return FALSE;
diff --git a/gtk/gtkcomposetable.h b/gtk/gtkcomposetable.h
index 1398559..584849f 100644
--- a/gtk/gtkcomposetable.h
+++ b/gtk/gtkcomposetable.h
@@ -80,9 +80,14 @@
                                              gboolean              *compose_match,
                                              GString               *output);
 
+void              gtk_compose_table_get_prefix (const GtkComposeTable *table,
+                                                const guint16         *compose_buffer,
+                                                int                    n_compose,
+                                                int                   *prefix);
+
 gboolean          gtk_check_algorithmically (const guint16         *compose_buffer,
                                              int                    n_compose,
-                                             gunichar              *output);
+                                             GString               *output);
 
 guint32           gtk_compose_table_data_hash (const guint16 *data,
                                                int            max_seq_len,
diff --git a/gtk/gtkimcontextsimple.c b/gtk/gtkimcontextsimple.c
index 7183205..cdba288 100644
--- a/gtk/gtkimcontextsimple.c
+++ b/gtk/gtkimcontextsimple.c
@@ -544,18 +544,22 @@
   return GDK_KEY_dead_grave <= keysym && keysym <= GDK_KEY_dead_greek;
 }
 
-static gunichar
-dead_key_to_unicode (guint     keysym,
-                     gboolean *need_space)
+static void
+append_dead_key (GString *string,
+                 guint    keysym)
 {
   /* Sadly, not all the dead keysyms have spacing mark equivalents
-   * in Unicode. For those that don't, we use space + the non-spacing
-   * mark as an approximation
+   * in Unicode. For those that don't, we use NBSP + the non-spacing
+   * mark as an approximation.
    */
   switch (keysym)
     {
-#define CASE(keysym, unicode, sp) \
-    case GDK_KEY_dead_##keysym: *need_space = sp; return unicode;
+#define CASE(keysym, unicode, sp)                \
+    case GDK_KEY_dead_##keysym:                  \
+      if (sp)                                    \
+        g_string_append_unichar (string, 0xA0);  \
+      g_string_append_unichar (string, unicode); \
+      break;
 
     CASE (grave, 0x60, 0);
     CASE (acute, 0xb4, 0);
@@ -606,8 +610,7 @@
     CASE (capital_schwa, 0x1dea, 1);
 #undef CASE
     default:
-      *need_space = FALSE;
-      return gdk_keyval_to_unicode (keysym);
+      g_string_append_unichar (string, gdk_keyval_to_unicode (keysym));
     }
 }
 
@@ -622,7 +625,7 @@
   guint keyval;
 
   context = GTK_IM_CONTEXT (context_simple);
-  
+
   priv->in_compose_sequence = FALSE;
 
   /* No compose sequences found, check first if we have a partial
@@ -675,7 +678,6 @@
 
       if (n_compose > 1 && i >= n_compose - 1)
         {
-          gboolean need_space;
           GString *s;
 
           s = g_string_new ("");
@@ -684,15 +686,7 @@
             {
               /* dead keys are never *really* dead */
               for (int j = 0; j < i; j++)
-                {
-                  ch = dead_key_to_unicode (priv->compose_buffer[j], &need_space);
-                  if (ch)
-                    {
-                      if (need_space)
-                        g_string_append_c (s, ' ');
-                      g_string_append_unichar (s, ch);
-                    }
-                }
+                append_dead_key (s, priv->compose_buffer[j]);
 
               ch = gdk_keyval_to_unicode (priv->compose_buffer[i]);
               if (ch != 0 && ch != ' ' && !g_unichar_iscntrl (ch))
@@ -702,14 +696,7 @@
             }
           else
             {
-              ch = dead_key_to_unicode (priv->compose_buffer[0], &need_space);
-              if (ch)
-                {
-                  if (need_space)
-                    g_string_append_c (s, ' ');
-                  g_string_append_unichar (s, ch);
-                }
-
+              append_dead_key (s, priv->compose_buffer[0]);
               gtk_im_context_simple_commit_string (context_simple, s->str);
 
               for (i = 1; i < n_compose; i++)
@@ -821,7 +808,6 @@
   int i;
   gboolean compose_finish;
   gboolean compose_match;
-  gunichar output_char;
   guint keyval, state;
 
   while (priv->compose_buffer[n_compose] != 0 && n_compose < priv->compose_buffer_len)
@@ -1001,16 +987,22 @@
   
       return TRUE;
     }
-  
+
+  if (is_escape)
+    {
+      if (priv->in_hex_sequence || priv->in_compose_sequence)
+        {
+	  gtk_im_context_simple_reset (context);
+	  return TRUE;
+        }
+
+      return FALSE;
+    }
+
   if (priv->in_hex_sequence)
     {
       if (hex_keyval && n_compose < 6)
 	priv->compose_buffer[n_compose++] = hex_keyval;
-      else if (is_escape)
-	{
-	  gtk_im_context_simple_reset (context);
-	  return TRUE;
-	}
       else if (!is_hex_end)
 	{
 	  /* non-hex character in hex sequence, or sequence too long */
@@ -1071,6 +1063,7 @@
   else /* Then, check for compose sequences */
     {
       gboolean success = FALSE;
+      int prefix = 0;
       GString *output;
 
       output = g_string_new ("");
@@ -1109,18 +1102,29 @@
               success = TRUE;
               break;
             }
+          else
+            {
+              int table_prefix;
+
+              gtk_compose_table_get_prefix ((GtkComposeTable *)tmp_list->data,
+                                            priv->compose_buffer, n_compose,
+                                            &table_prefix);
+
+              prefix = MAX (prefix, table_prefix);
+            }
 
           tmp_list = tmp_list->next;
         }
 
       G_UNLOCK (global_tables);
 
-      g_string_free (output, TRUE);
-
       if (success)
-        return TRUE;
+        {
+          g_string_free (output, TRUE);
+          return TRUE;
+        }
 
-      if (gtk_check_algorithmically (priv->compose_buffer, n_compose, &output_char))
+      if (gtk_check_algorithmically (priv->compose_buffer, n_compose, output))
         {
           if (!priv->in_compose_sequence)
             {
@@ -1128,11 +1132,30 @@
               g_signal_emit_by_name (context_simple, "preedit-start");
             }
 
-          if (output_char)
-            gtk_im_context_simple_commit_char (context_simple, output_char);
+          if (output->len > 0)
+            gtk_im_context_simple_commit_string (context_simple, output->str);
           else
             g_signal_emit_by_name (context_simple, "preedit-changed");
 
+          g_string_free (output, TRUE);
+
+          return TRUE;
+        }
+
+      g_string_free (output, TRUE);
+
+      /* If we get here, no Compose sequence matched.
+       * Only beep if we were in a sequence before.
+       */
+      if (prefix > 0)
+        {
+          for (i = prefix; i < n_compose; i++)
+            priv->compose_buffer[i] = 0;
+
+          beep_surface (gdk_event_get_surface (event));
+
+          g_signal_emit_by_name (context_simple, "preedit-changed");
+
           return TRUE;
         }
     }
@@ -1195,28 +1218,23 @@
               if (priv->compose_buffer[i] == GDK_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
+                   * only glyph in the preedit, or when the sequence contains
+                   * multiple Compose keys, 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.
                    */
-                  if (priv->compose_buffer[1] == 0 || i > 0)
+                  if (priv->compose_buffer[1] == 0 || i > 0 ||
+                      priv->compose_buffer[i + 1] == GDK_KEY_Multi_key)
                     g_string_append (s, "·");
                 }
               else
                 {
                   gunichar ch;
-                  gboolean need_space;
 
                   if (is_dead_key (priv->compose_buffer[i]))
                     {
-                      ch = dead_key_to_unicode (priv->compose_buffer[i], &need_space);
-                      if (ch)
-                        {
-                          if (need_space)
-                            g_string_append_c (s, ' ');
-                          g_string_append_unichar (s, ch);
-                        }
+                      append_dead_key (s, priv->compose_buffer[i]);
                     }
                   else
                     {
diff --git a/testsuite/gtk/compose/system.expected b/testsuite/gtk/compose/system.expected
index aac5ac8..c807827 100644
--- a/testsuite/gtk/compose/system.expected
+++ b/testsuite/gtk/compose/system.expected
@@ -1,14 +1,12 @@
-# n_sequences: 4909
+# n_sequences: 4874
 # max_seq_len: 5
 # n_index_size: 30
-# data_size: 16521
-# n_chars: 1572
+# data_size: 16447
+# n_chars: 1241
 <U7ae> <U7e9> : "ΐ" # U390
 <U7ae> <U7f5> : "ΰ" # U3b0
 <Ufe50> <U20> : "`" # U60
-<Ufe50> <U4d> : "M̀"
 <Ufe50> <U56> : "Ǜ" # U1db
-<Ufe50> <U6d> : "m̀"
 <Ufe50> <U76> : "ǜ" # U1dc
 <Ufe50> <Ua0> : "̀" # U300
 <Ufe50> <U186> : "Ɔ̀"
@@ -23,16 +21,6 @@
 <Ufe50> <U269> : "ɩ̀"
 <Ufe50> <U28a> : "ʊ̀"
 <Ufe50> <U28b> : "ʋ̀"
-<Ufe50> <U3bd> : "Ŋ̀"
-<Ufe50> <U3bf> : "ŋ̀"
-<Ufe50> <U6c1> : "а̀"
-<Ufe50> <U6cf> : "о̀"
-<Ufe50> <U6d2> : "р̀"
-<Ufe50> <U6d5> : "у̀"
-<Ufe50> <U6e1> : "А̀"
-<Ufe50> <U6ef> : "О̀"
-<Ufe50> <U6f2> : "Р̀"
-<Ufe50> <U6f5> : "У̀"
 <Ufe50> <U1f00> : "ἂ" # U1f02
 <Ufe50> <U1f01> : "ἃ" # U1f03
 <Ufe50> <U1f08> : "Ἂ" # U1f0a
@@ -131,9 +119,7 @@
 <Ufe50> <Uff20> <Uaf> <U65> : "ḕ" # U1e15
 <Ufe50> <Uff20> <Uaf> <U6f> : "ṑ" # U1e51
 <Ufe51> <U20> : "'" # U27
-<Ufe51> <U4a> : "J́"
 <Ufe51> <U56> : "Ǘ" # U1d7
-<Ufe51> <U6a> : "j́"
 <Ufe51> <U76> : "ǘ" # U1d8
 <Ufe51> <Ua0> : "́" # U301
 <Ufe51> <U186> : "Ɔ́"
@@ -148,28 +134,7 @@
 <Ufe51> <U269> : "ɩ́"
 <Ufe51> <U28a> : "ʊ́"
 <Ufe51> <U28b> : "ʋ́"
-<Ufe51> <U3bd> : "Ŋ́"
-<Ufe51> <U3bf> : "ŋ́"
-<Ufe51> <U6c0> : "ю́"
-<Ufe51> <U6c1> : "а́"
-<Ufe51> <U6c5> : "е́"
-<Ufe51> <U6c9> : "и́"
-<Ufe51> <U6cf> : "о́"
-<Ufe51> <U6d1> : "я́"
-<Ufe51> <U6d2> : "р́"
-<Ufe51> <U6d5> : "у́"
-<Ufe51> <U6d9> : "ы́"
-<Ufe51> <U6dc> : "э́"
 <Ufe51> <U6e0> : "Ю́́"
-<Ufe51> <U6e1> : "А́"
-<Ufe51> <U6e5> : "Е́"
-<Ufe51> <U6e9> : "И́"
-<Ufe51> <U6ef> : "О́"
-<Ufe51> <U6f1> : "Я́"
-<Ufe51> <U6f2> : "Р́"
-<Ufe51> <U6f5> : "У́"
-<Ufe51> <U6f9> : "Ы́"
-<Ufe51> <U6fc> : "Э́"
 <Ufe51> <U1f00> : "ἄ" # U1f04
 <Ufe51> <U1f01> : "ἅ" # U1f05
 <Ufe51> <U1f08> : "Ἄ" # U1f0c
@@ -210,6 +175,8 @@
 <Ufe51> <Ufe53> <U1dd> : "ǝ̃́"
 <Ufe51> <Ufe53> <U254> : "ɔ̃́"
 <Ufe51> <Ufe53> <U25b> : "ɛ̃́"
+<Ufe51> <Ufe56> <U53> : "Ṥ" # U1e64
+<Ufe51> <Ufe56> <U73> : "ṥ" # U1e65
 <Ufe51> <Ufe57> <U20> : "΅" # U385
 <Ufe51> <Ufe63> <U4f> : "Ǿ" # U1fe
 <Ufe51> <Ufe63> <U6f> : "ǿ" # U1ff
@@ -327,18 +294,6 @@
 <Ufe52> <U269> : "ɩ̂"
 <Ufe52> <U28a> : "ʊ̂"
 <Ufe52> <U28b> : "ʋ̂"
-<Ufe52> <U6c1> : "а̂"
-<Ufe52> <U6c5> : "е̂"
-<Ufe52> <U6c9> : "и̂"
-<Ufe52> <U6cf> : "о̂"
-<Ufe52> <U6d2> : "р̂"
-<Ufe52> <U6d5> : "у̂"
-<Ufe52> <U6e1> : "А̂"
-<Ufe52> <U6e5> : "Е̂"
-<Ufe52> <U6e9> : "И̂"
-<Ufe52> <U6ef> : "О̂"
-<Ufe52> <U6f2> : "Р̂"
-<Ufe52> <U6f5> : "У̂"
 <Ufe52> <U1ea0> : "Ậ" # U1eac
 <Ufe52> <U1ea1> : "ậ" # U1ead
 <Ufe52> <U1eb8> : "Ệ" # U1ec6
@@ -374,6 +329,30 @@
 <Ufe52> <Uffb8> : "⁸" # U2078
 <Ufe52> <Uffb9> : "⁹" # U2079
 <Ufe52> <Uffbd> : "⁼" # U207c
+<Ufe52> <Ufe50> <U41> : "Ầ" # U1ea6
+<Ufe52> <Ufe50> <U45> : "Ề" # U1ec0
+<Ufe52> <Ufe50> <U4f> : "Ồ" # U1ed2
+<Ufe52> <Ufe50> <U61> : "ầ" # U1ea7
+<Ufe52> <Ufe50> <U65> : "ề" # U1ec1
+<Ufe52> <Ufe50> <U6f> : "ồ" # U1ed3
+<Ufe52> <Ufe51> <U41> : "Ấ" # U1ea4
+<Ufe52> <Ufe51> <U45> : "Ế" # U1ebe
+<Ufe52> <Ufe51> <U4f> : "Ố" # U1ed0
+<Ufe52> <Ufe51> <U61> : "ấ" # U1ea5
+<Ufe52> <Ufe51> <U65> : "ế" # U1ebf
+<Ufe52> <Ufe51> <U6f> : "ố" # U1ed1
+<Ufe52> <Ufe53> <U41> : "Ẫ" # U1eaa
+<Ufe52> <Ufe53> <U45> : "Ễ" # U1ec4
+<Ufe52> <Ufe53> <U4f> : "Ỗ" # U1ed6
+<Ufe52> <Ufe53> <U61> : "ẫ" # U1eab
+<Ufe52> <Ufe53> <U65> : "ễ" # U1ec5
+<Ufe52> <Ufe53> <U6f> : "ỗ" # U1ed7
+<Ufe52> <Ufe61> <U41> : "Ẩ" # U1ea8
+<Ufe52> <Ufe61> <U45> : "Ể" # U1ec2
+<Ufe52> <Ufe61> <U4f> : "Ổ" # U1ed4
+<Ufe52> <Ufe61> <U61> : "ẩ" # U1ea9
+<Ufe52> <Ufe61> <U65> : "ể" # U1ec3
+<Ufe52> <Ufe61> <U6f> : "ổ" # U1ed5
 <Ufe52> <Uff20> <U21> <U41> : "Ậ" # U1eac
 <Ufe52> <Uff20> <U21> <U45> : "Ệ" # U1ec6
 <Ufe52> <Uff20> <U21> <U4f> : "Ộ" # U1ed8
@@ -463,6 +442,14 @@
 <Ufe53> <U1f68> : "Ὦ" # U1f6e
 <Ufe53> <U1f69> : "Ὧ" # U1f6f
 <Ufe53> <Ufe53> : "~" # U7e
+<Ufe53> <Ufe51> <U4f> : "Ṍ" # U1e4c
+<Ufe53> <Ufe51> <U55> : "Ṹ" # U1e78
+<Ufe53> <Ufe51> <U6f> : "ṍ" # U1e4d
+<Ufe53> <Ufe51> <U75> : "ṹ" # U1e79
+<Ufe53> <Ufe54> <U4f> : "Ȭ" # U22c
+<Ufe53> <Ufe54> <U6f> : "ȭ" # U22d
+<Ufe53> <Ufe57> <U4f> : "Ṏ" # U1e4e
+<Ufe53> <Ufe57> <U6f> : "ṏ" # U1e4f
 <Ufe53> <Uff20> <U22> <U7e9> : "ῗ" # U1fd7
 <Ufe53> <Uff20> <U22> <U7f5> : "ῧ" # U1fe7
 <Ufe53> <Uff20> <U28> <U7c1> : "Ἇ" # U1f0f
@@ -528,21 +515,19 @@
 <Ufe54> <U269> : "ɩ̄"
 <Ufe54> <U28a> : "ʊ̄"
 <Ufe54> <U28b> : "ʋ̄"
-<Ufe54> <U6c1> : "а̄"
-<Ufe54> <U6c5> : "е̄"
-<Ufe54> <U6cf> : "о̄"
-<Ufe54> <U6d2> : "р̄"
-<Ufe54> <U6e1> : "А̄"
-<Ufe54> <U6e5> : "Е̄"
-<Ufe54> <U6ef> : "О̄"
-<Ufe54> <U6f2> : "Р̄"
 <Ufe54> <U1e36> : "Ḹ" # U1e38
 <Ufe54> <U1e37> : "ḹ" # U1e39
 <Ufe54> <U1e5a> : "Ṝ" # U1e5c
 <Ufe54> <U1e5b> : "ṝ" # U1e5d
 <Ufe54> <Ufe54> : "¯" # Uaf
-<Ufe54> <Ufe57> <U55> : "Ǖ" # U1d5
-<Ufe54> <Ufe57> <U75> : "ǖ" # U1d6
+<Ufe54> <Ufe50> <U45> : "Ḕ" # U1e14
+<Ufe54> <Ufe50> <U4f> : "Ṑ" # U1e50
+<Ufe54> <Ufe50> <U65> : "ḕ" # U1e15
+<Ufe54> <Ufe50> <U6f> : "ṑ" # U1e51
+<Ufe54> <Ufe51> <U45> : "Ḗ" # U1e16
+<Ufe54> <Ufe51> <U4f> : "Ṓ" # U1e52
+<Ufe54> <Ufe51> <U65> : "ḗ" # U1e17
+<Ufe54> <Ufe51> <U6f> : "ṓ" # U1e53
 <Ufe54> <Ufe8c> <U41> : "Ᾱ" # U1fb9
 <Ufe54> <Ufe8c> <U49> : "Ῑ" # U1fd9
 <Ufe54> <Ufe8c> <U55> : "Ῡ" # U1fe9
@@ -580,6 +565,14 @@
 <Ufe55> <U1ea0> : "Ặ" # U1eb6
 <Ufe55> <U1ea1> : "ặ" # U1eb7
 <Ufe55> <Ufe55> : "˘" # U2d8
+<Ufe55> <Ufe50> <U41> : "Ằ" # U1eb0
+<Ufe55> <Ufe50> <U61> : "ằ" # U1eb1
+<Ufe55> <Ufe51> <U41> : "Ắ" # U1eae
+<Ufe55> <Ufe51> <U61> : "ắ" # U1eaf
+<Ufe55> <Ufe53> <U41> : "Ẵ" # U1eb4
+<Ufe55> <Ufe53> <U61> : "ẵ" # U1eb5
+<Ufe55> <Ufe61> <U41> : "Ẳ" # U1eb2
+<Ufe55> <Ufe61> <U61> : "ẳ" # U1eb3
 <Ufe55> <Uff20> <U21> <U41> : "Ặ" # U1eb6
 <Ufe55> <Uff20> <U21> <U61> : "ặ" # U1eb7
 <Ufe55> <Uff20> <U2c> <U45> : "Ḝ" # U1e1c
@@ -600,6 +593,10 @@
 <Ufe56> <U1e62> : "Ṩ" # U1e68
 <Ufe56> <U1e63> : "ṩ" # U1e69
 <Ufe56> <Ufe56> : "˙" # U2d9
+<Ufe56> <Ufe54> <U41> : "Ǡ" # U1e0
+<Ufe56> <Ufe54> <U4f> : "Ȱ" # U230
+<Ufe56> <Ufe54> <U61> : "ǡ" # U1e1
+<Ufe56> <Ufe54> <U6f> : "ȱ" # U231
 <Ufe56> <Ufe63> <U6a> : "ɟ" # U25f
 <Ufe56> <Uff20> <U21> <U53> : "Ṩ" # U1e68
 <Ufe56> <Uff20> <U21> <U73> : "ṩ" # U1e69
@@ -631,9 +628,21 @@
 <Ufe57> <U4e8> : "Ӫ" # U4ea
 <Ufe57> <U4e9> : "ӫ" # U4eb
 <Ufe57> <Ufe57> : "¨" # Ua8
+<Ufe57> <Ufe50> <U55> : "Ǜ" # U1db
+<Ufe57> <Ufe50> <U75> : "ǜ" # U1dc
 <Ufe57> <Ufe51> <U20> : "΅" # U385
-<Ufe57> <Ufe54> <U55> : "Ṻ" # U1e7a
-<Ufe57> <Ufe54> <U75> : "ṻ" # U1e7b
+<Ufe57> <Ufe51> <U49> : "Ḯ" # U1e2e
+<Ufe57> <Ufe51> <U55> : "Ǘ" # U1d7
+<Ufe57> <Ufe51> <U69> : "ḯ" # U1e2f
+<Ufe57> <Ufe51> <U75> : "ǘ" # U1d8
+<Ufe57> <Ufe51> <U7e9> : "ΐ" # U390
+<Ufe57> <Ufe51> <U7f5> : "ΰ" # U3b0
+<Ufe57> <Ufe54> <U41> : "Ǟ" # U1de
+<Ufe57> <Ufe54> <U4f> : "Ȫ" # U22a
+<Ufe57> <Ufe54> <U61> : "ǟ" # U1df
+<Ufe57> <Ufe54> <U6f> : "ȫ" # U22b
+<Ufe57> <Ufe5a> <U55> : "Ǚ" # U1d9
+<Ufe57> <Ufe5a> <U75> : "ǚ" # U1da
 <Ufe57> <Ufe6c> <U3d> : "⩷" # U2a77
 <Ufe57> <Uff20> <U5f> <U55> : "Ṻ" # U1e7a
 <Ufe57> <Uff20> <U5f> <U75> : "ṻ" # U1e7b
@@ -646,6 +655,8 @@
 <Ufe58> <Uc1> : "Ǻ" # U1fa
 <Ufe58> <Ue1> : "ǻ" # U1fb
 <Ufe58> <Ufe58> : "°" # Ub0
+<Ufe58> <Ufe51> <U41> : "Ǻ" # U1fa
+<Ufe58> <Ufe51> <U61> : "ǻ" # U1fb
 <Ufe59> <U20> : "˝" # U2dd
 <Ufe59> <Ua0> : "̋" # U30b
 <Ufe59> <Ufe59> : "˝" # U2dd
@@ -682,6 +693,8 @@
 <Ufe5a> <U28a> : "ʊ̌"
 <Ufe5a> <U28b> : "ʋ̌"
 <Ufe5a> <Ufe5a> : "ˇ" # U2c7
+<Ufe5a> <Ufe56> <U53> : "Ṧ" # U1e66
+<Ufe5a> <Ufe56> <U73> : "ṧ" # U1e67
 <Ufe5a> <Uff20> <U22> <U55> : "Ǚ" # U1d9
 <Ufe5a> <Uff20> <U22> <U75> : "ǚ" # U1da
 <Ufe5b> <U20> : "¸" # Ub8
@@ -787,42 +800,6 @@
 <Ufe5d> <Ufe53> <U1f61> : "ᾧ" # U1fa7
 <Ufe5d> <Ufe53> <U1f68> : "ᾮ" # U1fae
 <Ufe5d> <Ufe53> <U1f69> : "ᾯ" # U1faf
-<Ufe5d> <Ufe50> <Ufe64> <U7c1> : "ᾊ" # U1f8a
-<Ufe5d> <Ufe50> <Ufe64> <U7c7> : "ᾚ" # U1f9a
-<Ufe5d> <Ufe50> <Ufe64> <U7d9> : "ᾪ" # U1faa
-<Ufe5d> <Ufe50> <Ufe64> <U7e1> : "ᾂ" # U1f82
-<Ufe5d> <Ufe50> <Ufe64> <U7e7> : "ᾒ" # U1f92
-<Ufe5d> <Ufe50> <Ufe64> <U7f9> : "ᾢ" # U1fa2
-<Ufe5d> <Ufe50> <Ufe65> <U7c1> : "ᾋ" # U1f8b
-<Ufe5d> <Ufe50> <Ufe65> <U7c7> : "ᾛ" # U1f9b
-<Ufe5d> <Ufe50> <Ufe65> <U7d9> : "ᾫ" # U1fab
-<Ufe5d> <Ufe50> <Ufe65> <U7e1> : "ᾃ" # U1f83
-<Ufe5d> <Ufe50> <Ufe65> <U7e7> : "ᾓ" # U1f93
-<Ufe5d> <Ufe50> <Ufe65> <U7f9> : "ᾣ" # U1fa3
-<Ufe5d> <Ufe51> <Ufe64> <U7c1> : "ᾌ" # U1f8c
-<Ufe5d> <Ufe51> <Ufe64> <U7c7> : "ᾜ" # U1f9c
-<Ufe5d> <Ufe51> <Ufe64> <U7d9> : "ᾬ" # U1fac
-<Ufe5d> <Ufe51> <Ufe64> <U7e1> : "ᾄ" # U1f84
-<Ufe5d> <Ufe51> <Ufe64> <U7e7> : "ᾔ" # U1f94
-<Ufe5d> <Ufe51> <Ufe64> <U7f9> : "ᾤ" # U1fa4
-<Ufe5d> <Ufe51> <Ufe65> <U7c1> : "ᾍ" # U1f8d
-<Ufe5d> <Ufe51> <Ufe65> <U7c7> : "ᾝ" # U1f9d
-<Ufe5d> <Ufe51> <Ufe65> <U7d9> : "ᾭ" # U1fad
-<Ufe5d> <Ufe51> <Ufe65> <U7e1> : "ᾅ" # U1f85
-<Ufe5d> <Ufe51> <Ufe65> <U7e7> : "ᾕ" # U1f95
-<Ufe5d> <Ufe51> <Ufe65> <U7f9> : "ᾥ" # U1fa5
-<Ufe5d> <Ufe53> <Ufe64> <U7c1> : "ᾎ" # U1f8e
-<Ufe5d> <Ufe53> <Ufe64> <U7c7> : "ᾞ" # U1f9e
-<Ufe5d> <Ufe53> <Ufe64> <U7d9> : "ᾮ" # U1fae
-<Ufe5d> <Ufe53> <Ufe64> <U7e1> : "ᾆ" # U1f86
-<Ufe5d> <Ufe53> <Ufe64> <U7e7> : "ᾖ" # U1f96
-<Ufe5d> <Ufe53> <Ufe64> <U7f9> : "ᾦ" # U1fa6
-<Ufe5d> <Ufe53> <Ufe65> <U7c1> : "ᾏ" # U1f8f
-<Ufe5d> <Ufe53> <Ufe65> <U7c7> : "ᾟ" # U1f9f
-<Ufe5d> <Ufe53> <Ufe65> <U7d9> : "ᾯ" # U1faf
-<Ufe5d> <Ufe53> <Ufe65> <U7e1> : "ᾇ" # U1f87
-<Ufe5d> <Ufe53> <Ufe65> <U7e7> : "ᾗ" # U1f97
-<Ufe5d> <Ufe53> <Ufe65> <U7f9> : "ᾧ" # U1fa7
 <Ufe5d> <Uff20> <U27> <U7e1> : "ᾴ" # U1fb4
 <Ufe5d> <Uff20> <U27> <U7e7> : "ῄ" # U1fc4
 <Ufe5d> <Uff20> <U27> <U7f9> : "ῴ" # U1ff4
@@ -1175,18 +1152,6 @@
 <Ufe63> <Ufe8c> <U72> : "ϼ" # U3fc
 <Ufe66> <U474> : "Ѷ" # U476
 <Ufe66> <U475> : "ѷ" # U477
-<Ufe66> <U6c1> : "а̏"
-<Ufe66> <U6c5> : "е̏"
-<Ufe66> <U6c9> : "и̏"
-<Ufe66> <U6cf> : "о̏"
-<Ufe66> <U6d2> : "р̏"
-<Ufe66> <U6d5> : "у̏"
-<Ufe66> <U6e1> : "А̏"
-<Ufe66> <U6e5> : "Е̏"
-<Ufe66> <U6e9> : "И̏"
-<Ufe66> <U6ef> : "О̏"
-<Ufe66> <U6f2> : "Р̏"
-<Ufe66> <U6f5> : "У̏"
 <Ufe67> <U7c> : "⫰" # U2af0
 <Ufe6a> <U2b> : "⨦" # U2a26
 <Ufe6c> <Ufe57> <U3d> : "⩷" # U2a77
diff --git a/testsuite/gtk/composetable.c b/testsuite/gtk/composetable.c
index 1a77e5b..dec41af 100644
--- a/testsuite/gtk/composetable.c
+++ b/testsuite/gtk/composetable.c
@@ -310,81 +310,86 @@
 {
   guint16 buffer[8] = { 0, };
   gboolean ret;
-  gunichar ch;
+  GString *output;
+
+  output = g_string_new ("");
 
   buffer[0] = GDK_KEY_a;
   buffer[1] = GDK_KEY_b;
 
-  ret = gtk_check_algorithmically (buffer, 2, &ch);
+  ret = gtk_check_algorithmically (buffer, 2, output);
   g_assert_false (ret);
-  g_assert_true (ch == 0);
+  g_assert_cmpstr (output->str, ==, "");
 
   buffer[0] = GDK_KEY_dead_abovering;
   buffer[1] = GDK_KEY_A;
 
-  ret = gtk_check_algorithmically (buffer, 2, &ch);
+  ret = gtk_check_algorithmically (buffer, 2, output);
   g_assert_true (ret);
-  g_assert_true (ch == 0xc5);
+  g_assert_cmpstr (output->str, ==, "Å");
 
   buffer[0] = GDK_KEY_A;
   buffer[1] = GDK_KEY_dead_abovering;
 
-  ret = gtk_check_algorithmically (buffer, 2, &ch);
+  ret = gtk_check_algorithmically (buffer, 2, output);
   g_assert_false (ret);
-  g_assert_true (ch == 0);
+  g_assert_cmpstr (output->str, ==, "");
 
   buffer[0] = GDK_KEY_dead_dasia;
   buffer[1] = GDK_KEY_dead_perispomeni;
   buffer[2] = GDK_KEY_Greek_alpha;
 
-  ret = gtk_check_algorithmically (buffer, 3, &ch);
+  ret = gtk_check_algorithmically (buffer, 3, output);
   g_assert_true (ret);
-  g_assert_true (ch == 0x1f07);
+  g_assert_cmpstr (output->str, ==, "ᾶ\xcc\x94");
 
   buffer[0] = GDK_KEY_dead_perispomeni;
   buffer[1] = GDK_KEY_dead_dasia;
   buffer[2] = GDK_KEY_Greek_alpha;
 
-  ret = gtk_check_algorithmically (buffer, 3, &ch);
+  ret = gtk_check_algorithmically (buffer, 3, output);
   g_assert_true (ret);
-  g_assert_true (ch == 0x1f07);
+  g_assert_cmpstr (output->str, ==, "ἇ");
 
   buffer[0] = GDK_KEY_dead_acute;
   buffer[1] = GDK_KEY_dead_cedilla;
   buffer[2] = GDK_KEY_c;
 
-  ret = gtk_check_algorithmically (buffer, 2, &ch);
+  ret = gtk_check_algorithmically (buffer, 2, output);
   g_assert_true (ret);
-  g_assert_cmphex (ch, ==, 0);
+  g_assert_cmpstr (output->str, ==, "");
 
-  ret = gtk_check_algorithmically (buffer, 3, &ch);
+  ret = gtk_check_algorithmically (buffer, 3, output);
   g_assert_true (ret);
-  g_assert_cmphex (ch, ==, 0x1e09);
+  g_assert_cmpstr (output->str, ==, "ḉ");
 
   buffer[0] = GDK_KEY_dead_cedilla;
   buffer[1] = GDK_KEY_dead_acute;
   buffer[2] = GDK_KEY_c;
 
-  ret = gtk_check_algorithmically (buffer, 3, &ch);
+  ret = gtk_check_algorithmically (buffer, 3, output);
   g_assert_true (ret);
-  g_assert_cmphex (ch, ==, 0x1e09);
+  g_assert_cmpstr (output->str, ==, "ḉ");
 
-  ret = gtk_check_algorithmically (buffer, 2, &ch);
+  ret = gtk_check_algorithmically (buffer, 2, output);
   g_assert_true (ret);
 
   buffer[0] = GDK_KEY_dead_acute;
   buffer[1] = GDK_KEY_dead_cedilla;
   buffer[2] = GDK_KEY_dead_grave;
 
-  ret = gtk_check_algorithmically (buffer, 3, &ch);
-  g_assert_false (ret);
+  ret = gtk_check_algorithmically (buffer, 3, output);
+  g_assert_true (ret);
+  g_assert_cmpstr (output->str, ==, "");
 
   buffer[0] = GDK_KEY_dead_diaeresis;
   buffer[1] = GDK_KEY_a;
 
-  ret = gtk_check_algorithmically (buffer, 2, &ch);
+  ret = gtk_check_algorithmically (buffer, 2, output);
   g_assert_true (ret);
-  g_assert_cmphex (ch, ==, 0xe4);
+  g_assert_cmpstr (output->str, ==, "ä");
+
+  g_string_free (output, TRUE);
 }
 
 int