diff options
author | Philip Zander <philip.zander+gtk@gmail.com> | 2020-09-07 19:04:47 +0800 |
---|---|---|
committer | Chun-wei Fan <fanchunwei@src.gnome.org> | 2020-11-04 17:11:56 +0800 |
commit | e06176ae44d27d2b82e92ddb05d2cb94e4ed58a2 (patch) | |
tree | b2f3795b6a8d4ed995bfc048dcad6c194f872500 | |
parent | e47ddfb107593833c1080d5b31dbd13f704accbe (diff) | |
download | gtk+-keymap.win32.tar.gz |
Win32 IME fixeskeymap.win32
See merge request !1063
-rw-r--r-- | gtk/gtkimcontextime.c | 357 |
1 files 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); |