[gtk/wip.win32.fixes: 923/924] Win32 IME fixes




commit 0a39ceae0e49a75914807557f002ac14904929b0
Author: Philip Zander <philip zander+gtk gmail com>
Date:   Mon Sep 7 19:04:47 2020 +0800

    Win32 IME fixes
    
    See merge request !1063

 gtk/gtkimcontextime.c | 357 ++++++++++++++++++++++----------------------------
 1 file changed, 157 insertions(+), 200 deletions(-)
---
diff --git a/gtk/gtkimcontextime.c b/gtk/gtkimcontextime.c
index 1880332978..c8378c836e 100644
--- a/gtk/gtkimcontextime.c
+++ b/gtk/gtkimcontextime.c
@@ -50,34 +50,39 @@
 #   include <pango/pangowin32.h>
 #endif /* STRICT */
 
-/* #define BUFSIZE 4096 */
+/* Determines what happens when focus is lost while preedit is in process. */
+typedef enum {
+  /* Preedit is committed. */
+  GTK_WIN32_IME_FOCUS_BEHAVIOR_COMMIT,
+  /* Preedit is discarded. */
+  GTK_WIN32_IME_FOCUS_BEHAVIOR_DISCARD,
+  /* Preedit follows the cursor (that means it will appear in the widget
+   * that receives the focus) */
+  GTK_WIN32_IME_FOCUS_BEHAVIOR_FOLLOW,
+} GtkWin32IMEFocusBehavior;
 
 #define IS_DEAD_KEY(k) \
     ((k) >= GDK_KEY_dead_grave && (k) <= (GDK_KEY_dead_dasia+1))
 
-#define FREE_PREEDIT_BUFFER(ctx) \
-{                                \
-  g_free((ctx)->priv->comp_str); \
-  g_free((ctx)->priv->read_str); \
-  (ctx)->priv->comp_str = NULL;  \
-  (ctx)->priv->read_str = NULL;  \
-  (ctx)->priv->comp_str_len = 0; \
-  (ctx)->priv->read_str_len = 0; \
-}
-
-
 struct _GtkIMContextIMEPrivate
 {
-  /* save IME context when the client window is focused out */
-  DWORD conversion_mode;
-  DWORD sentence_mode;
-
-  LPVOID comp_str;
-  DWORD comp_str_len;
-  LPVOID read_str;
-  DWORD read_str_len;
-
+  /* When pretend_empty_preedit is set to TRUE,
+   * gtk_im_context_ime_get_preedit_string() will return an empty string
+   * instead of the actual content of ImmGetCompositionStringW().
+   *
+   * This is necessary because GtkEntry expects the preedit buffer to be
+   * cleared before commit() is called, otherwise it leads to an assertion
+   * failure in Pango. However, since we emit the commit() signal while
+   * handling the WM_IME_COMPOSITION message, the IME buffer will be non-empty,
+   * so we temporarily set this flag while emmiting the appropriate signals.
+   *
+   * See also:
+   *   https://bugzilla.gnome.org/show_bug.cgi?id=787142
+   *   https://gitlab.gnome.org/GNOME/gtk/commit/c255ba68fc2c918dd84da48a472e7973d3c00b03
+   */
+  gboolean pretend_empty_preedit;
   guint32 dead_key_keyval;
+  GtkWin32IMEFocusBehavior focus_behavior;
 };
 
 
@@ -166,12 +171,7 @@ gtk_im_context_ime_init (GtkIMContextIME *context_ime)
   context_ime->commit_string          = NULL;
 
   context_ime->priv = g_malloc0 (sizeof (GtkIMContextIMEPrivate));
-  context_ime->priv->conversion_mode  = 0;
-  context_ime->priv->sentence_mode    = 0;
-  context_ime->priv->comp_str         = NULL;
-  context_ime->priv->comp_str_len     = 0;
-  context_ime->priv->read_str         = NULL;
-  context_ime->priv->read_str_len     = 0;
+  context_ime->priv->focus_behavior = GTK_WIN32_IME_FOCUS_BEHAVIOR_COMMIT;
 }
 
 
@@ -184,8 +184,6 @@ gtk_im_context_ime_dispose (GObject *obj)
   if (context_ime->client_surface)
     gtk_im_context_ime_set_client_widget (context, NULL);
 
-  FREE_PREEDIT_BUFFER (context_ime);
-
   G_OBJECT_CLASS (gtk_im_context_ime_parent_class)->dispose (obj);
 }
 
@@ -193,7 +191,6 @@ gtk_im_context_ime_dispose (GObject *obj)
 static void
 gtk_im_context_ime_finalize (GObject *obj)
 {
-  /* GtkIMContext *context = GTK_IM_CONTEXT (obj); */
   GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (obj);
 
   g_free (context_ime->priv);
@@ -251,30 +248,27 @@ gtk_im_context_ime_set_client_widget (GtkIMContext *context,
                                       GtkWidget    *widget)
 {
   GtkIMContextIME *context_ime;
-  GdkSurface *client_surface;
+  GdkSurface *client_surface = NULL;
 
   g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context));
   context_ime = GTK_IM_CONTEXT_IME (context);
 
-  client_surface = NULL;
   if (widget)
     client_surface = gtk_native_get_surface (gtk_widget_get_native (widget));
 
-  if (client_surface)
+  if (client_surface != NULL)
     {
-      HIMC himc;
-      HWND hwnd;
-
-      hwnd = gdk_win32_surface_get_impl_hwnd (client_surface);
-      himc = ImmGetContext (hwnd);
+      HWND hwnd = gdk_win32_surface_get_impl_hwnd (client_surface);
+      HIMC himc = ImmGetContext (hwnd);
       if (himc)
-       {
-         context_ime->opened = ImmGetOpenStatus (himc);
-         ImmGetConversionStatus (himc,
-                                 &context_ime->priv->conversion_mode,
-                                 &context_ime->priv->sentence_mode);
-         ImmReleaseContext (hwnd, himc);
-       }
+        {
+          context_ime->opened = ImmGetOpenStatus (himc);
+          ImmReleaseContext (hwnd, himc);
+        }
+      else
+        {
+          context_ime->opened = FALSE;
+        }
     }
   else if (context_ime->focus)
     {
@@ -427,11 +421,10 @@ gtk_im_context_ime_reset (GtkIMContext *context)
   if (!himc)
     return;
 
+  ImmNotifyIME (himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
+
   if (context_ime->preediting)
     {
-      if (ImmGetOpenStatus (himc))
-        ImmNotifyIME (himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
-
       context_ime->preediting = FALSE;
       g_signal_emit_by_name (context, "preedit-changed");
     }
@@ -441,12 +434,17 @@ gtk_im_context_ime_reset (GtkIMContext *context)
 
 
 static char *
-get_utf8_preedit_string (GtkIMContextIME *context_ime, int *pos_ret)
+get_utf8_preedit_string (GtkIMContextIME *context_ime,
+                         int  kind,
+                         int *pos_ret)
 {
+  gunichar2 *utf16str = NULL;
+  glong size;
   char *utf8str = NULL;
   HWND hwnd;
   HIMC himc;
   int pos = 0;
+  GError *error = NULL;
 
   if (pos_ret)
     *pos_ret = 0;
@@ -458,58 +456,31 @@ get_utf8_preedit_string (GtkIMContextIME *context_ime, int *pos_ret)
   if (!himc)
     return g_strdup ("");
 
-  if (context_ime->preediting)
+  size = ImmGetCompositionStringW (himc, kind, NULL, 0);
+
+  if (size > 0)
     {
-      glong len;
+      utf16str = g_malloc (size);
 
-      len = ImmGetCompositionStringW (himc, GCS_COMPSTR, NULL, 0);
-      if (len > 0)
+      ImmGetCompositionStringW (himc, kind, utf16str, size);
+      utf8str = g_utf16_to_utf8 (utf16str, size / sizeof (gunichar2),
+                                 NULL, NULL, &error);
+      if (error)
         {
-          GError *error = NULL;
-          gpointer buf = g_alloca (len);
-
-          ImmGetCompositionStringW (himc, GCS_COMPSTR, buf, len);
-          len /= 2;
-          utf8str = g_utf16_to_utf8 (buf, len, NULL, NULL, &error);
-          if (error)
-            {
-               g_warning ("%s", error->message);
-               g_error_free (error);
-            }
+          g_warning ("%s", error->message);
+          g_error_free (error);
 
-          if (pos_ret)
-            {
-              pos = ImmGetCompositionStringW (himc, GCS_CURSORPOS, NULL, 0);
-              if (pos < 0 || len < pos)
-                {
-                  g_warning ("ImmGetCompositionString: "
-                             "Invalid cursor position!");
-                  pos = 0;
-                }
-            }
         }
     }
 
-  if (context_ime->commit_string)
+  if (pos_ret)
     {
-      if (utf8str)
-        {
-          char *utf8str_new = g_strdup (utf8str);
-
-          /* Note: We *don't* want to update context_ime->commit_string here!
-           * Otherwise it will be updated repeatedly, not what we want!
-           */
-          g_free (utf8str);
-          utf8str = g_strconcat (context_ime->commit_string,
-                                 utf8str_new,
-                                 NULL);
-          g_free (utf8str_new);
-          pos += g_utf8_strlen (context_ime->commit_string, -1);
-        }
-      else
+      pos = ImmGetCompositionStringW (himc, GCS_CURSORPOS, NULL, 0);
+      if (pos < 0 || size < pos)
         {
-          utf8str = g_strdup (context_ime->commit_string);
-          pos = g_utf8_strlen (context_ime->commit_string, -1);
+          g_warning ("ImmGetCompositionString: "
+                     "Invalid cursor position!");
+          pos = 0;
         }
     }
 
@@ -523,6 +494,7 @@ get_utf8_preedit_string (GtkIMContextIME *context_ime, int *pos_ret)
     *pos_ret = pos;
 
   ImmReleaseContext (hwnd, himc);
+  g_free (utf16str);
 
   return utf8str;
 }
@@ -534,6 +506,7 @@ get_pango_attr_list (GtkIMContextIME *context_ime, const char *utf8str)
   PangoAttrList *attrs = pango_attr_list_new ();
   HWND hwnd;
   HIMC himc;
+  guint8 *buf = NULL;
 
   if (!context_ime->client_surface)
     return attrs;
@@ -545,7 +518,6 @@ get_pango_attr_list (GtkIMContextIME *context_ime, const char *utf8str)
   if (context_ime->preediting)
     {
       const char *schr = utf8str, *echr;
-      guint8 *buf;
       guint16 f_red, f_green, f_blue, b_red, b_green, b_blue;
       glong len, spos = 0, epos, sidx = 0, eidx;
       PangoAttribute *attr;
@@ -554,7 +526,7 @@ get_pango_attr_list (GtkIMContextIME *context_ime, const char *utf8str)
        *  get attributes list of IME.
        */
       len = ImmGetCompositionStringW (himc, GCS_COMPATTR, NULL, 0);
-      buf = g_alloca (len);
+      buf = g_malloc (len);
       ImmGetCompositionStringW (himc, GCS_COMPATTR, buf, len);
 
       /*
@@ -623,6 +595,7 @@ get_pango_attr_list (GtkIMContextIME *context_ime, const char *utf8str)
     }
 
   ImmReleaseContext (hwnd, himc);
+  g_free (buf);
 
   return attrs;
 }
@@ -640,20 +613,18 @@ gtk_im_context_ime_get_preedit_string (GtkIMContext   *context,
 
   context_ime = GTK_IM_CONTEXT_IME (context);
 
-  utf8str = get_utf8_preedit_string (context_ime, &pos);
+  if (!context_ime->focus || context_ime->priv->pretend_empty_preedit)
+    utf8str = g_strdup ("");
+  else
+    utf8str = get_utf8_preedit_string (context_ime, GCS_COMPSTR, &pos);
 
   if (attrs)
     *attrs = get_pango_attr_list (context_ime, utf8str);
 
   if (str)
-    {
-      *str = utf8str;
-    }
+    *str = utf8str;
   else
-    {
-      g_free (utf8str);
-      utf8str = NULL;
-    }
+    utf8str = NULL;
 
   if (cursor_pos)
     *cursor_pos = pos;
@@ -674,41 +645,45 @@ gtk_im_context_ime_focus_in (GtkIMContext *context)
   /* switch current context */
   context_ime->focus = TRUE;
 
-  hwnd = gdk_win32_surface_get_impl_hwnd (context_ime->client_surface);
-  himc = ImmGetContext (hwnd);
-  if (!himc)
-    return;
-
   toplevel = context_ime->client_surface;
-  if (GDK_IS_SURFACE (toplevel))
-    {
-      gdk_win32_display_add_filter (GDK_WIN32_DISPLAY (gdk_surface_get_display (toplevel)),
-                                    gtk_im_context_ime_message_filter, context_ime);
-    }
-  else
+  if (!GDK_IS_SURFACE (toplevel))
     {
       g_warning ("gtk_im_context_ime_focus_in(): "
                  "cannot find toplevel window.");
       return;
     }
 
+  hwnd = gdk_win32_surface_get_impl_hwnd (toplevel);
+  himc = ImmGetContext (hwnd);
+  if (!himc)
+    return;
+
+  gdk_win32_display_add_filter (GDK_WIN32_DISPLAY (gdk_surface_get_display (toplevel)),
+                                gtk_im_context_ime_message_filter, context_ime);
+
   /* restore preedit context */
-  ImmSetConversionStatus (himc,
-                          context_ime->priv->conversion_mode,
-                          context_ime->priv->sentence_mode);
+  context_ime->opened = ImmGetOpenStatus (himc);
 
-  if (context_ime->opened)
+  switch (context_ime->priv->focus_behavior)
     {
-      if (!ImmGetOpenStatus (himc))
-        ImmSetOpenStatus (himc, TRUE);
-      if (context_ime->preediting)
+      case GTK_WIN32_IME_FOCUS_BEHAVIOR_COMMIT:
+      case GTK_WIN32_IME_FOCUS_BEHAVIOR_DISCARD:
+        gtk_im_context_ime_reset (context);
+        break;
+
+      case GTK_WIN32_IME_FOCUS_BEHAVIOR_FOLLOW:
         {
-          ImmSetCompositionStringW (himc,
-                                   SCS_SETSTR,
-                                   context_ime->priv->comp_str,
-                                   context_ime->priv->comp_str_len, NULL, 0);
-          FREE_PREEDIT_BUFFER (context_ime);
+          gchar *utf8str = get_utf8_preedit_string (context_ime, GCS_COMPSTR, NULL);
+          if (utf8str != NULL && strlen(utf8str) > 0)
+            {
+              context_ime->preediting = TRUE;
+              gtk_im_context_ime_set_cursor_location (context, NULL);
+              g_signal_emit_by_name (context, "preedit-start");
+              g_signal_emit_by_name (context, "preedit-changed");
+            }
+          g_free (utf8str);
         }
+        break;
     }
 
   /* clean */
@@ -720,78 +695,66 @@ static void
 gtk_im_context_ime_focus_out (GtkIMContext *context)
 {
   GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (context);
-  GdkSurface *toplevel;
   HWND hwnd;
   HIMC himc;
+  gboolean was_preediting;
 
   if (!GDK_IS_SURFACE (context_ime->client_surface))
     return;
 
   /* switch current context */
+  was_preediting = context_ime->preediting;
+  context_ime->opened = FALSE;
+  context_ime->preediting = FALSE;
   context_ime->focus = FALSE;
 
-  hwnd = gdk_win32_surface_get_impl_hwnd (context_ime->client_surface);
-  himc = ImmGetContext (hwnd);
-  if (!himc)
-    return;
-
-  /* save preedit context */
-  ImmGetConversionStatus (himc,
-                          &context_ime->priv->conversion_mode,
-                          &context_ime->priv->sentence_mode);
-
-  if (ImmGetOpenStatus (himc))
+  switch (context_ime->priv->focus_behavior)
     {
-      gboolean preediting = context_ime->preediting;
+      case GTK_WIN32_IME_FOCUS_BEHAVIOR_COMMIT:
+        if (was_preediting)
+          {
+            gchar *utf8str = get_utf8_preedit_string (context_ime, GCS_COMPSTR, NULL);
+
+            context_ime->priv->pretend_empty_preedit = TRUE;
+            g_signal_emit_by_name (context, "preedit-changed");
+            g_signal_emit_by_name (context, "preedit-end");
+                       g_signal_emit_by_name (context, "commit", utf8str);
+            g_signal_emit_by_name (context, "preedit-start");
+            g_signal_emit_by_name (context, "preedit-changed");
+            context_ime->priv->pretend_empty_preedit = FALSE;
+            g_free (utf8str);
+          }
+        /* fallthrough */
+      case GTK_WIN32_IME_FOCUS_BEHAVIOR_DISCARD:
+        gtk_im_context_ime_reset (context);
 
-      if (preediting)
-        {
-          FREE_PREEDIT_BUFFER (context_ime);
-
-          context_ime->priv->comp_str_len
-            = ImmGetCompositionStringW (himc, GCS_COMPSTR, NULL, 0);
-          context_ime->priv->comp_str
-            = g_malloc (context_ime->priv->comp_str_len);
-          ImmGetCompositionStringW (himc, GCS_COMPSTR,
-                                   context_ime->priv->comp_str,
-                                   context_ime->priv->comp_str_len);
-
-          context_ime->priv->read_str_len
-            = ImmGetCompositionStringW (himc, GCS_COMPREADSTR, NULL, 0);
-          context_ime->priv->read_str
-            = g_malloc (context_ime->priv->read_str_len);
-          ImmGetCompositionStringW (himc, GCS_COMPREADSTR,
-                                   context_ime->priv->read_str,
-                                   context_ime->priv->read_str_len);
-        }
+        /* Callbacks triggered by im_context_ime_reset() could set the focus back to our
+           context. In that case, we want to exit here. */
 
-      ImmSetOpenStatus (himc, FALSE);
+        if (context_ime->focus)
+          return;
 
-      context_ime->opened = TRUE;
-      context_ime->preediting = preediting;
-    }
-  else
-    {
-      context_ime->opened = FALSE;
-      context_ime->preediting = FALSE;
+        break;
+
+      case GTK_WIN32_IME_FOCUS_BEHAVIOR_FOLLOW:
+        break;
     }
 
   /* remove event filter */
-  toplevel = context_ime->client_surface;
-  if (GDK_IS_SURFACE (toplevel))
+  if (GDK_IS_SURFACE (context_ime->client_surface))
     {
-      gdk_win32_display_remove_filter (GDK_WIN32_DISPLAY (gdk_surface_get_display (toplevel)),
+      gdk_win32_display_remove_filter (GDK_WIN32_DISPLAY (gdk_surface_get_display 
(context_ime->client_surface)),
                                        gtk_im_context_ime_message_filter,
                                        context_ime);
     }
-  else
+
+  if (was_preediting)
     {
       g_warning ("gtk_im_context_ime_focus_out(): "
                  "cannot find toplevel window.");
+      g_signal_emit_by_name (context, "preedit-changed");
+      g_signal_emit_by_name (context, "preedit-end");
     }
-
-  /* clean */
-  ImmReleaseContext (hwnd, himc);
 }
 
 
@@ -1012,16 +975,16 @@ gtk_im_context_ime_message_filter (GdkWin32Display *display,
         get_window_position (context_ime->client_surface, &wx, &wy);
         /* FIXME! */
         {
-          HWND hwnd_top;
+          HWND hwnd;
           POINT pt;
           RECT rc;
 
-          hwnd_top =
+          hwnd =
             gdk_win32_surface_get_impl_hwnd (context_ime->client_surface);
-          GetWindowRect (hwnd_top, &rc);
+          GetWindowRect (hwnd, &rc);
           pt.x = wx;
           pt.y = wy;
-          ClientToScreen (hwnd_top, &pt);
+          ClientToScreen (hwnd, &pt);
           wx = pt.x - rc.left;
           wy = pt.y - rc.top;
         }
@@ -1037,38 +1000,32 @@ gtk_im_context_ime_message_filter (GdkWin32Display *display,
 
         if (msg->lParam & GCS_RESULTSTR)
           {
-            gsize len;
-            GError *error = NULL;
+            gchar *utf8str = get_utf8_preedit_string (context_ime, GCS_RESULTSTR, NULL);
 
-            len = ImmGetCompositionStringW (himc, GCS_RESULTSTR, NULL, 0);
-
-            if (len > 0)
+            if (utf8str)
               {
-                gpointer buf = g_alloca (len);
-                ImmGetCompositionStringW (himc, GCS_RESULTSTR, buf, len);
-                len /= 2;
-                context_ime->commit_string = g_utf16_to_utf8 (buf, len, NULL, NULL, &error);
-                if (error)
-                  {
-                    g_warning ("%s", error->message);
-                    g_error_free (error);
-                  }
-
-                if (context_ime->commit_string)
-                  {
-                    g_signal_emit_by_name (context, "commit", context_ime->commit_string);
-                    g_free (context_ime->commit_string);
-                    context_ime->commit_string = NULL;
-                    retval = GDK_WIN32_MESSAGE_FILTER_REMOVE;
-                  }
+                context_ime->priv->pretend_empty_preedit = TRUE;
+                g_signal_emit_by_name (context, "preedit-changed");
+                g_signal_emit_by_name (context, "preedit-end");
+
+                g_signal_emit_by_name (context, "commit", utf8str);
+
+                g_signal_emit_by_name (context, "preedit-start");
+                g_signal_emit_by_name (context, "preedit-changed");
+                context_ime->priv->pretend_empty_preedit = FALSE;
+
+                retval = TRUE;
               }
+
+            g_free (utf8str);
           }
 
         if (context_ime->use_preedit)
           retval = GDK_WIN32_MESSAGE_FILTER_REMOVE;
-        break;
       }
 
+      break;
+
     case WM_IME_STARTCOMPOSITION:
       context_ime->preediting = TRUE;
       gtk_im_context_ime_set_cursor_location (context, NULL);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]