File ibus-sync-ibus_input_context_process_key_event.patch of Package ibus

From 38f09c657fd5713e39f698aae43a09a07574f1a6 Mon Sep 17 00:00:00 2001
From: fujiwarat <takao.fujiwara1@gmail.com>
Date: Wed, 12 Jul 2023 07:50:22 +0900
Subject: [PATCH] src: Fix sync ibus_input_context_process_key_event()

The synchronous "ProcessKeyEvent" D-Bus method cannot receive
"CommitText" and "ForwardKeyEvent" D-Bus signals during calling the method.
To resolve the issue, now
ibus_input_context_set_post_process_key_event() and
ibus_input_context_post_process_key_event() are added newly.
  
ibus_input_context_post_process_key_event() retries "CommitText" and
"ForwardKeyEvent" D-Bus signals during calling the "ProcessKeyEvent" D-Bus
method and ibus-daemon does not handle those signals.
  
--- a/bus/inputcontext.c
+++ b/bus/inputcontext.c
@@ -31,6 +31,8 @@
 #include "marshalers.h"
 #include "types.h"
 
+#define MAX_SYNC_DATA 30
+
 struct _SetEngineByDescData {
     /* context related to the data */
     BusInputContext *context;
@@ -45,6 +47,11 @@
 };
 typedef struct _SetEngineByDescData SetEngineByDescData;
 
+typedef struct _SyncForwardingData {
+    gchar     key;
+    IBusText *text;
+} SyncForwardingData;
+
 struct _BusInputContext {
     IBusService parent;
 
@@ -98,6 +105,9 @@
 
     BusPanelProxy *emoji_extension;
     gboolean is_extension_lookup_table;
+    GQueue *queue_during_process_key_event;
+    gboolean use_post_process_key_event;
+    gboolean processing_key_event;
 };
 
 struct _BusInputContextClass {
@@ -155,6 +165,15 @@
                                     const gchar           *method_name,
                                     GVariant              *parameters,
                                     GDBusMethodInvocation *invocation);
+static GVariant *
+                bus_input_context_service_get_property
+                                   (IBusService           *service,
+                                    GDBusConnection       *connection,
+                                    const gchar           *sender,
+                                    const gchar           *object_path,
+                                    const gchar           *interface_name,
+                                    const gchar           *property_name,
+                                    GError               **error);
 static gboolean bus_input_context_service_set_property
                                    (IBusService           *service,
                                     GDBusConnection       *connection,
@@ -214,8 +233,21 @@
     "<node>"
     "  <interface name='org.freedesktop.IBus.InputContext'>"
     /* properties */
+    "    <property name='PostProcessKeyEvent' type='(a(yv))' access='read'>\n"
+    "      <annotation name='org.gtk.GDBus.Since'\n"
+    "          value='1.5.29' />\n"
+    "      <annotation name='org.gtk.GDBus.DocString'\n"
+    "          value='Stability: Unstable' />\n"
+    "    </property>\n"
     "    <property name='ContentType' type='(uu)' access='write' />"
     "    <property name='ClientCommitPreedit' type='(b)' access='write' />\n"
+    "    <property name='EffectivePostProcessKeyEvent' type='(b)' \n"
+    "                                                  access='write'>\n"
+    "      <annotation name='org.gtk.GDBus.Since'\n"
+    "          value='1.5.29' />\n"
+    "      <annotation name='org.gtk.GDBus.DocString'\n"
+    "          value='Stability: Unstable' />\n"
+    "    </property>\n"
     /* methods */
     "    <method name='ProcessKeyEvent'>"
     "      <arg direction='in'  type='u' name='keyval' />"
@@ -348,6 +380,8 @@
     /* override the parent class's implementation. */
     IBUS_SERVICE_CLASS (class)->service_method_call =
         bus_input_context_service_method_call;
+    IBUS_SERVICE_CLASS (class)->service_get_property =
+        bus_input_context_service_get_property;
     IBUS_SERVICE_CLASS (class)->service_set_property =
         bus_input_context_service_set_property;
     /* register the xml so that bus_ibus_impl_service_method_call will be called on a method call defined in the xml (e.g. 'FocusIn'.) */
@@ -762,6 +796,10 @@
                                           error);
 }
 
+typedef struct _PanelProcessKeyEventData {
+    GDBusMethodInvocation *invocation;
+    BusInputContext *context;
+} PanelProcessKeyEventData;
 
 /**
  * _panel_process_key_event_cb:
@@ -770,14 +808,21 @@
  * bus_panel_proxy_process_key_event() is finished.
  */
 static void
-_panel_process_key_event_cb (GObject               *source,
-                             GAsyncResult          *res,
-                             GDBusMethodInvocation *invocation)
+_panel_process_key_event_cb (GObject                  *source,
+                             GAsyncResult             *res,
+                             PanelProcessKeyEventData *data)
 {
     GError *error = NULL;
     GVariant *value = g_dbus_proxy_call_finish ((GDBusProxy *)source,
                                                  res,
                                                  &error);
+    GDBusMethodInvocation *invocation;
+    BusInputContext *context;
+
+    g_assert (data);
+    invocation = data->invocation;
+    context = data->context;
+    g_slice_free (PanelProcessKeyEventData, data);
     if (value != NULL) {
         g_dbus_method_invocation_return_value (invocation, value);
         g_variant_unref (value);
@@ -786,6 +831,7 @@
         g_dbus_method_invocation_return_gerror (invocation, error);
         g_error_free (error);
     }
+    context->processing_key_event = FALSE;
 }
 
 typedef struct _ProcessKeyEventData ProcessKeyEventData;
@@ -822,23 +868,28 @@
         gboolean retval = FALSE;
         g_variant_get (value, "(b)", &retval);
         if (context->emoji_extension && !retval) {
+            PanelProcessKeyEventData *pdata =
+                    g_slice_new (PanelProcessKeyEventData);
+            pdata->invocation = invocation;
+            pdata->context = context;
             bus_panel_proxy_process_key_event (context->emoji_extension,
                                                keyval,
                                                keycode,
                                                modifiers,
                                                (GAsyncReadyCallback)
                                                     _panel_process_key_event_cb,
-                                               invocation);
+                                               pdata);
         } else {
             g_dbus_method_invocation_return_value (invocation, value);
+            context->processing_key_event = FALSE;
         }
         g_variant_unref (value);
     }
     else {
         g_dbus_method_invocation_return_gerror (invocation, error);
         g_error_free (error);
+        context->processing_key_event = FALSE;
     }
-
     g_object_unref (context);
     g_slice_free (ProcessKeyEventData, data);
 }
@@ -858,6 +909,8 @@
     guint keycode = 0;
     guint modifiers = 0;
 
+    if (context->use_post_process_key_event)
+        context->processing_key_event = TRUE;
     g_variant_get (parameters, "(uuu)", &keyval, &keycode, &modifiers);
     if (G_UNLIKELY (!context->has_focus)) {
         /* workaround: set focus if context does not have focus */
@@ -1330,17 +1383,109 @@
     g_return_if_reached ();
 }
 
-static void
+/**
+ * _ic_get_post_process_key_event:
+ *
+ * Implement the "PostProcessKeyEvent" get property of the
+ * org.freedesktop.IBus.InputContext interface because currently the Gio
+ * D-Bus method calls don't support multiple nested tuples likes
+ * G_VARIANT_TYPE ("((ba(yv)))")) in "ProcessKeyEvent" D-Bus method
+ * So these post events are separated from the return value "b" of
+ * the "ProcessKeyEvent" D-Bus method call.
+ */
+static GVariant *
+_ic_get_post_process_key_event (BusInputContext *context,
+                                GDBusConnection *connection,
+                                GError         **error)
+{
+    const char *error_message = NULL;
+    GVariantBuilder array;
+    SyncForwardingData *data;
+
+    do {
+        if (!BUS_IS_INPUT_CONTEXT (context)) {
+            error_message = "BusInputContext is freed";
+            break;
+        }
+        if (context->processing_key_event) {
+            error_message = "Another ProcessKeyEvent is called.";
+            break;
+        }
+        g_variant_builder_init (&array, G_VARIANT_TYPE ("a(yv)"));
+        while ((data =
+                g_queue_pop_head (context->queue_during_process_key_event))) {
+            GVariant *variant = ibus_serializable_serialize_object (
+                    IBUS_SERIALIZABLE (data->text));
+            g_variant_builder_add (&array, "(yv)", data->key, variant);
+            g_object_unref (data->text);
+            g_slice_free (SyncForwardingData, data);
+        }
+    } while (FALSE);
+    if (error_message) {
+        g_set_error (error,
+                     G_DBUS_ERROR,
+                     G_DBUS_ERROR_FAILED,
+                     "%s", error_message);
+        return NULL;
+    }
+    return g_variant_builder_end (&array);
+}
+
+static GVariant *
+bus_input_context_service_get_property (IBusService           *service,
+                                        GDBusConnection       *connection,
+                                        const gchar           *sender,
+                                        const gchar           *object_path,
+                                        const gchar           *interface_name,
+                                        const gchar           *property_name,
+                                        GError               **error)
+{
+    int i;
+    static const struct {
+        const char *property_name;
+        GVariant * (* property_callback) (BusInputContext *,
+                                          GDBusConnection *,
+                                          GError **);
+    } properties [] =  {
+        { "PostProcessKeyEvent",   _ic_get_post_process_key_event },
+    };
+
+    if (error)
+        *error = NULL;
+    if (g_strcmp0 (interface_name, IBUS_INTERFACE_INPUT_CONTEXT) != 0) {
+        return IBUS_SERVICE_CLASS (bus_input_context_parent_class)->
+                service_get_property (
+                        service, connection, sender, object_path,
+                        interface_name, property_name,
+                        error);
+    }
+    for (i = 0; i < G_N_ELEMENTS (properties); i++) {
+        if (g_strcmp0 (properties[i].property_name, property_name) == 0) {
+            return properties[i].property_callback ((BusInputContext *)service,
+                                                    connection,
+                                                    error);
+        }
+    }
+
+    g_set_error (error,
+                 G_DBUS_ERROR,
+                 G_DBUS_ERROR_FAILED,
+                 "service_get_property received an unknown property: %s",
+                 property_name ? property_name : "(null)");
+    g_return_val_if_reached (NULL);
+}
+
+static gboolean
 _ic_set_content_type (BusInputContext *context,
-                      GVariant        *value)
+                      GVariant        *value,
+                      GError         **error)
 {
     guint purpose = 0;
     guint hints = 0;
+    gboolean retval = TRUE;
 
     g_variant_get (value, "(uu)", &purpose, &hints);
     if (purpose != context->purpose || hints != context->hints) {
-        GError *error;
-        gboolean retval;
 
         context->purpose = purpose;
         context->hints = hints;
@@ -1358,24 +1503,30 @@
                            context->hints);
         }
 
-        error = NULL;
         retval = bus_input_context_property_changed (context,
                                                      "ContentType",
                                                      value,
-                                                     &error);
-        if (!retval) {
-            g_warning ("Failed to emit PropertiesChanged signal: %s",
-                       error->message);
-            g_error_free (error);
-        }
+                                                     error);
     }
+    return retval;
 }
 
-static void
+static gboolean
 _ic_set_client_commit_preedit (BusInputContext *context,
-                               GVariant        *value)
+                               GVariant        *value,
+                               GError         **error)
 {
     g_variant_get (value, "(b)", &context->client_commit_preedit);
+    return TRUE;
+}
+
+static gboolean
+_ic_set_use_post_process_key_event (BusInputContext *context,
+                                    GVariant        *value,
+                                    GError         **error)
+{
+    g_variant_get (value, "(b)", &context->use_post_process_key_event);
+    return TRUE;
 }
 
 static gboolean
@@ -1388,6 +1539,18 @@
                                         GVariant        *value,
                                         GError         **error)
 {
+    int i;
+    static const struct {
+        const char *property_name;
+        gboolean (* property_callback) (BusInputContext *,
+                                        GVariant *,
+                                        GError **);
+    } properties [] =  {
+        { "ContentType",                   _ic_set_content_type },
+        { "ClientCommitPreedit",           _ic_set_client_commit_preedit },
+        { "EffectivePostProcessKeyEvent",  _ic_set_use_post_process_key_event },
+    };
+
     if (error)
         *error = NULL;
     if (g_strcmp0 (interface_name, IBUS_INTERFACE_INPUT_CONTEXT) != 0) {
@@ -1418,14 +1581,12 @@
                      " ");
         return FALSE;
     }
-
-    if (g_strcmp0 (property_name, "ContentType") == 0) {
-        _ic_set_content_type (BUS_INPUT_CONTEXT (service), value);
-        return TRUE;
-    }
-    if (g_strcmp0 (property_name, "ClientCommitPreedit") == 0) {
-        _ic_set_client_commit_preedit (BUS_INPUT_CONTEXT (service), value);
-        return TRUE;
+    for (i = 0; i < G_N_ELEMENTS (properties); i++) {
+        if (g_strcmp0 (properties[i].property_name, property_name) == 0) {
+            return properties[i].property_callback ((BusInputContext *) service,
+                                                    value,
+                                                    error);
+        }
     }
 
     g_set_error (error,
@@ -2122,6 +2283,23 @@
 
     g_assert (context->engine == engine);
 
+    g_assert (context->queue_during_process_key_event);
+
+    if (context->processing_key_event && g_queue_get_length (
+                   context->queue_during_process_key_event) <= MAX_SYNC_DATA) {
+        SyncForwardingData *data;
+        IBusText *text = ibus_text_new_from_printf ("%u,%u,%u",
+                                                    keyval, keycode, state);
+        if (g_queue_get_length (context->queue_during_process_key_event)
+            == MAX_SYNC_DATA) {
+            g_warning ("Exceed max number of post process_key_event data");
+        }
+        data = g_slice_new (SyncForwardingData);
+        data->key = 'f';
+        data->text = text;
+        g_queue_push_tail (context->queue_during_process_key_event, data);
+        return;
+    }
     bus_input_context_emit_signal (context,
                                    "ForwardKeyEvent",
                                    g_variant_new ("(uuu)", keyval, keycode, state),
@@ -2362,6 +2540,7 @@
 
     /* it is a fake input context, just need process hotkey */
     context->fake = (strncmp (client, "fake", 4) == 0);
+    context->queue_during_process_key_event = g_queue_new ();
 
     if (connection) {
         g_object_ref_sink (connection);
@@ -2828,11 +3007,17 @@
                                     guint            hints)
 {
     GVariant *value;
+    GError *error = NULL;
 
     g_assert (BUS_IS_INPUT_CONTEXT (context));
 
     value = g_variant_ref_sink (g_variant_new ("(uu)", purpose, hints));
-    _ic_set_content_type (context, value);
+    _ic_set_content_type (context, value, &error);
+    if (error) {
+        g_warning ("Failed to emit PropertiesChanged signal: %s",
+                   error->message);
+        g_error_free (error);
+    }
     g_variant_unref (value);
 }
 
@@ -2842,12 +3027,24 @@
                                              gboolean         use_extension)
 {
     g_assert (BUS_IS_INPUT_CONTEXT (context));
+    g_assert (context->queue_during_process_key_event);
 
     if (text == text_empty || text == NULL)
         return;
 
     if (use_extension && context->emoji_extension) {
         bus_panel_proxy_commit_text_received (context->emoji_extension, text);
+    } else if (context->processing_key_event && g_queue_get_length (
+                   context->queue_during_process_key_event) <= MAX_SYNC_DATA) {
+        SyncForwardingData *data;
+        if (g_queue_get_length (context->queue_during_process_key_event)
+            == MAX_SYNC_DATA) {
+            g_warning ("Exceed max number of sync process_key_event data");
+        }
+        data = g_slice_new (SyncForwardingData);
+        data->key = 'c';
+        data->text = g_object_ref (text);
+        g_queue_push_tail (context->queue_during_process_key_event, data);
     } else {
         GVariant *variant = ibus_serializable_serialize (
                 (IBusSerializable *)text);

--- a/client/gtk2/ibusimcontext.c
+++ b/client/gtk2/ibusimcontext.c
@@ -111,7 +111,7 @@
 static guint    _signal_retrieve_surrounding_id = 0;
 
 #if GTK_CHECK_VERSION (3, 98, 4)
-static char _use_sync_mode = 2;
+static char _use_sync_mode = 1;
 #else
 static const gchar *_no_snooper_apps = NO_SNOOPER_APPS;
 static gboolean _use_key_snooper = ENABLE_SNOOPER;
@@ -473,6 +473,97 @@
 }
 
 static gboolean
+_process_key_event_sync (IBusInputContext *context,
+                         guint             keyval,
+                         guint             keycode,
+                         guint             state)
+{
+    gboolean retval;
+
+    g_assert (IBUS_IS_INPUT_CONTEXT (context));
+    retval = ibus_input_context_process_key_event (context,
+                                                   keyval,
+                                                   keycode - 8,
+                                                   state);
+    ibus_input_context_post_process_key_event (context);
+    return retval;
+}
+
+static gboolean
+_process_key_event_async (IBusInputContext *context,
+                          guint             keyval,
+                          guint             keycode,
+                          guint             state,
+                          GdkEvent         *event,
+                          IBusIMContext    *ibusimcontext)
+{
+    ProcessKeyEventData *data = g_slice_new0 (ProcessKeyEventData);
+
+    g_assert (event);
+    if (!data) {
+        g_warning ("Cannot allocate async data");
+        return _process_key_event_sync (context, keyval, keycode, state);
+    }
+#if GTK_CHECK_VERSION (3, 98, 4)
+    data->event = gdk_event_ref (event);
+#else
+    data->event = gdk_event_copy (event);
+#endif
+    data->ibusimcontext = ibusimcontext;
+    ibus_input_context_process_key_event_async (context,
+            keyval,
+            keycode - 8,
+            state,
+            -1,
+            NULL,
+            _process_key_event_done,
+            data);
+
+    return TRUE;
+}
+
+static gboolean
+_process_key_event_hybrid_async (IBusInputContext *context,
+                                 guint             keyval,
+                                 guint             keycode,
+                                 guint             state)
+{
+    GSource *source = g_timeout_source_new (1);
+    ProcessKeyEventReplyData *data = NULL;
+    gboolean retval = FALSE;
+
+    if (source)
+        data = g_slice_new0 (ProcessKeyEventReplyData);
+    if (!data) {
+        g_warning ("Cannot wait for the reply of the process key event.");
+        retval = _process_key_event_sync (context, keyval, keycode, state);
+        if (source)
+            g_source_destroy (source);
+        return retval;
+    }
+    data->count = 1;
+    g_source_attach (source, NULL);
+    g_source_unref (source);
+    data->count_cb_id = g_source_get_id (source);
+    ibus_input_context_process_key_event_async (context,
+            keyval,
+            keycode - 8,
+            state,
+            -1,
+            NULL,
+            _process_key_event_reply_done,
+            data);
+    g_source_set_callback (source, _process_key_event_count_cb, data, NULL);
+    while (data->count)
+        g_main_context_iteration (NULL, TRUE);
+    /* #2498 Checking source->ref_count might cause Nautilus hang up
+     */
+    retval = data->retval;
+    g_slice_free (ProcessKeyEventReplyData, data);
+    return retval;
+}
+
+static gboolean
 _process_key_event (IBusInputContext *context,
 #if GTK_CHECK_VERSION (3, 98, 4)
                     GdkEvent         *event,
@@ -505,70 +596,20 @@
 
     switch (_use_sync_mode) {
     case 1: {
-        retval = ibus_input_context_process_key_event (context,
-                                                       keyval,
-                                                       keycode - 8,
-                                                       state);
+        retval = _process_key_event_sync (context, keyval, keycode, state);
         break;
     }
     case 2: {
-        GSource *source = g_timeout_source_new (1);
-        ProcessKeyEventReplyData *data = NULL;
-
-        if (source)
-            data = g_slice_new0 (ProcessKeyEventReplyData);
-        if (!data) {
-            g_warning ("Cannot wait for the reply of the process key event.");
-            retval = ibus_input_context_process_key_event (context,
-                                                           keyval,
-                                                           keycode - 8,
-                                                           state);
-            if (source)
-                g_source_destroy (source);
-            break;
-        }
-        data->count = 1;
-        g_source_attach (source, NULL);
-        g_source_unref (source);
-        data->count_cb_id = g_source_get_id (source);
-        ibus_input_context_process_key_event_async (context,
-            keyval,
-            keycode - 8,
-            state,
-            -1,
-            NULL,
-            _process_key_event_reply_done,
-            data);
-        g_source_set_callback (source, _process_key_event_count_cb, data, NULL);
-        while (data->count)
-            g_main_context_iteration (NULL, TRUE);
-        if (source->ref_count > 0) {
-            /* g_source_get_id() could causes a SEGV */
-            g_info ("Broken GSource.ref_count and maybe a timing issue in %p.",
-                    source);
-        }
-        retval = data->retval;
-        g_slice_free (ProcessKeyEventReplyData, data);
+        retval = _process_key_event_hybrid_async (context,
+                                                  keyval, keycode, state);
         break;
     }
     default: {
-        ProcessKeyEventData *data = g_slice_new0 (ProcessKeyEventData);
-#if GTK_CHECK_VERSION (3, 98, 4)
-        data->event = gdk_event_ref (event);
-#else
-        data->event = gdk_event_copy ((GdkEvent *)event);
-#endif
-        data->ibusimcontext = ibusimcontext;
-        ibus_input_context_process_key_event_async (context,
-            keyval,
-            keycode - 8,
-            state,
-            -1,
-            NULL,
-            _process_key_event_done,
-            data);
-
-        retval = TRUE;
+        retval = _process_key_event_async (context,
+                                           keyval, keycode, state,
+                                           (GdkEvent *)event,
+                                           ibusimcontext);
+        break;
     }
     }
 
@@ -877,7 +918,55 @@
     g_assert (_signal_retrieve_surrounding_id != 0);
 
 #if GTK_CHECK_VERSION (3, 98, 4)
-    _use_sync_mode = _get_char_env ("IBUS_ENABLE_SYNC_MODE", 2);
+    /* IBus GtkIMModule, QtIMModlue, ibus-x11, ibus-wayland are called as
+     * IBus clients.
+     * Each GTK application, each QT application, Xorg server, Wayland
+     * comppsitor are called as IBus event owners here.
+     *
+     * The IBus client processes the key events between the IBus event owner
+     * and the IBus daemon and the procedure step is to:
+     *
+     * receive the key event from the IBus event owner and forward the
+     * event to the IBus daemon with the "ProcessKeyEvent" D-Bus method at
+     * first,
+     *
+     * receive the return value from the IBus daemon with the "ProessKeyEvent"
+     * D-Bus method and forward the value to the IBus event owner secondly and
+     * the return value includes if the key event is processed normally or not.
+     *
+     * The procedure behavior can be changed by the "IBUS_ENABLE_SYNC_MODE"
+     * environment variable with the synchronous procedure or asynchronous
+     * one and value is:
+     *
+     * 1: Synchronous process key event:
+     *    Wait for the return of the IBus "ProcessKeyEvent" D-Bus method
+     *    synchronously and forward the return value to the IBus event owner
+     *    synchronously.
+     * 0: Asynchronous process key event:
+     *    Return to the IBus event owner as the key event is processed normally
+     *    at first as soon as the IBus client receives the event from the
+     *    IBus event owner and also forward the event to the IBus daemon with
+     *    the "ProcessKeyEvent" D-Bus method and wait for the return value of
+     *    the D-Bus method *asynchronously*.
+     *    If the return value indicates the key event is disposed by IBus,
+     *    the IBus client does not perform anything. Otherwise the IBus client
+     *    forwards the key event with the gdk_event_put() in GTK3,
+     *    gtk_im_context_filter_key() in GTK4, IMForwardEvent() in XIM API.
+     * 2: Hybrid asynchronous process key event:
+     *    Wait for the return of the IBus "ProcessKeyEvent" D-Bus method
+     *    *asynchronously* with a GSource loop and forward the return value
+     *    to the IBus event owner synchronously. So IBus clients perform
+     *    virtually synchronously to cover problems of IBus synchronous APIs.
+     *
+     * The purpose of the asynchronous process is that each IBus input
+     * method can process the key events without D-Bus timeout and also
+     * the IBus synchronous process has a problem that the IBus
+     * "ProcessKeyEvent" D-Bus method cannot send the commit-text and
+     * forwar-key-event D-Bus signals until the D-Bus method is finished.
+     *
+     * Relative issues: #1713, #2486
+     */
+    _use_sync_mode = _get_char_env ("IBUS_ENABLE_SYNC_MODE", 1);
 #else
     _use_key_snooper = !_get_boolean_env ("IBUS_DISABLE_SNOOPER",
                                           !(ENABLE_SNOOPER));
@@ -1004,8 +1093,6 @@
 #else
     ibusimcontext->caps = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS;
 #endif
-    if (_use_sync_mode == 1)
-        ibusimcontext->caps |= IBUS_CAP_SYNC_PROCESS_KEY_V2;
 
     ibusimcontext->events_queue = g_queue_new ();
 
@@ -2265,6 +2352,8 @@
     else {
         gboolean requested_surrounding_text = FALSE;
         ibus_input_context_set_client_commit_preedit (context, TRUE);
+        if (_use_sync_mode == 1)
+            ibus_input_context_set_post_process_key_event (context, TRUE);
         ibusimcontext->ibuscontext = context;
 
         g_signal_connect (ibusimcontext->ibuscontext,
@@ -2489,9 +2578,8 @@
                       G_CALLBACK (_ibus_fake_context_destroy_cb),
                       NULL);
 
-    guint32 caps = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS | IBUS_CAP_SURROUNDING_TEXT;
-    if (_use_sync_mode == 1)
-        caps |= IBUS_CAP_SYNC_PROCESS_KEY_V2;
+    guint32 caps = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS
+                   | IBUS_CAP_SURROUNDING_TEXT;
     ibus_input_context_set_capabilities (_fake_context, caps);
 
     /* focus in/out the fake context */

--- a/src/ibusinputcontext.c
+++ b/src/ibusinputcontext.c
@@ -1190,14 +1190,14 @@
     g_assert (IBUS_IS_INPUT_CONTEXT (context));
 
     cached_content_type =
-        g_dbus_proxy_get_cached_property ((GDBusProxy *) context,
+        g_dbus_proxy_get_cached_property ((GDBusProxy *)context,
                                           "ContentType");
     content_type = g_variant_new ("(uu)", purpose, hints);
 
     g_variant_ref_sink (content_type);
-    if (cached_content_type == NULL ||
+    if (!cached_content_type ||
         !g_variant_equal (content_type, cached_content_type)) {
-        g_dbus_proxy_call ((GDBusProxy *) context,
+        g_dbus_proxy_call ((GDBusProxy *)context,
                            "org.freedesktop.DBus.Properties.Set",
                            g_variant_new ("(ssv)",
                                           IBUS_INTERFACE_INPUT_CONTEXT,
@@ -1209,9 +1209,13 @@
                            NULL, /* callback */
                            NULL  /* user_data */
                            );
+        /* Need to update the cache by manual since there is a timing issue. */
+        g_dbus_proxy_set_cached_property ((GDBusProxy *)context,
+                                          "ContentType",
+                                          content_type);
     }
 
-    if (cached_content_type != NULL)
+    if (cached_content_type)
         g_variant_unref (cached_content_type);
     g_variant_unref (content_type);
 }
@@ -1324,19 +1328,20 @@
 ibus_input_context_set_client_commit_preedit (IBusInputContext *context,
                                               gboolean          client_commit)
 {
-    GVariant *cached_content_type;
+    GVariant *cached_var_client_commit;
     GVariant *var_client_commit;
 
     g_assert (IBUS_IS_INPUT_CONTEXT (context));
 
-    cached_content_type =
-        g_dbus_proxy_get_cached_property ((GDBusProxy *) context,
+    cached_var_client_commit =
+        g_dbus_proxy_get_cached_property ((GDBusProxy *)context,
                                           "ClientCommitPreedit");
     var_client_commit = g_variant_new ("(b)", client_commit);
 
     g_variant_ref_sink (var_client_commit);
-    if (cached_content_type == NULL) {
-        g_dbus_proxy_call ((GDBusProxy *) context,
+    if (!cached_var_client_commit ||
+        !g_variant_equal (var_client_commit, cached_var_client_commit)) {
+        g_dbus_proxy_call ((GDBusProxy *)context,
                            "org.freedesktop.DBus.Properties.Set",
                            g_variant_new ("(ssv)",
                                           IBUS_INTERFACE_INPUT_CONTEXT,
@@ -1348,13 +1353,146 @@
                            NULL, /* callback */
                            NULL  /* user_data */
                            );
+        /* Need to update the cache by manual since there is a timing issue. */
+        g_dbus_proxy_set_cached_property ((GDBusProxy *)context,
+                                          "ClientCommitPreedit",
+                                          var_client_commit);
     }
 
-    if (cached_content_type != NULL)
-        g_variant_unref (cached_content_type);
+    if (cached_var_client_commit)
+        g_variant_unref (cached_var_client_commit);
     g_variant_unref (var_client_commit);
 }
 
+void
+ibus_input_context_set_post_process_key_event (IBusInputContext *context,
+                                               gboolean          enable)
+{
+    GVariant *cached_var_post;
+    GVariant *var_post;
+
+    g_assert (IBUS_IS_INPUT_CONTEXT (context));
+
+    cached_var_post =
+        g_dbus_proxy_get_cached_property ((GDBusProxy *)context,
+                                          "EffectivePostProcessKeyEvent");
+    var_post = g_variant_new ("(b)", enable);
+    g_variant_ref_sink (var_post);
+    if (!cached_var_post ||
+        !g_variant_equal (var_post, cached_var_post)) {
+        g_dbus_proxy_call ((GDBusProxy *)context,
+                           "org.freedesktop.DBus.Properties.Set",
+                           g_variant_new ("(ssv)",
+                                          IBUS_INTERFACE_INPUT_CONTEXT,
+                                          "EffectivePostProcessKeyEvent",
+                                          var_post),
+                           G_DBUS_CALL_FLAGS_NONE,
+                           -1,
+                           NULL, /* cancellable */
+                           NULL, /* callback */
+                           NULL  /* user_data */
+                           );
+        /* Need to update the cache by manual since there is a timing issue. */
+        g_dbus_proxy_set_cached_property ((GDBusProxy *)context,
+                                          "EffectivePostProcessKeyEvent",
+                                          var_post);
+    }
+
+    if (cached_var_post)
+        g_variant_unref (cached_var_post);
+    g_variant_unref (var_post);
+}
+
+void
+ibus_input_context_post_process_key_event (IBusInputContext *context)
+{
+    GVariant *cached_var_post;
+    gboolean enable = FALSE;
+    GVariant *result;
+    GError *error = NULL;
+    GVariant *variant = NULL;
+    GVariantIter iter;
+    gsize size;
+    char type = 0;
+    GVariant *vtext = NULL;
+
+    g_assert (IBUS_IS_INPUT_CONTEXT (context));
+
+    cached_var_post =
+        g_dbus_proxy_get_cached_property ((GDBusProxy *)context,
+                                          "EffectivePostProcessKeyEvent");
+    if (cached_var_post)
+        g_variant_get (cached_var_post, "(b)", &enable);
+    if (!enable) {
+        g_warning ("%s: ibus_input_context_set_post_process_key_event() "
+                   "needs to be called before.",
+                   G_STRFUNC);
+        if (cached_var_post)
+            g_variant_unref (cached_var_post);
+        return;
+    }
+    g_variant_unref (cached_var_post);
+    result = g_dbus_proxy_call_sync (
+            (GDBusProxy *)context,
+            "org.freedesktop.DBus.Properties.Get",
+            g_variant_new ("(ss)",
+                           IBUS_INTERFACE_INPUT_CONTEXT,
+                           "PostProcessKeyEvent"),
+            G_DBUS_CALL_FLAGS_NONE,
+            -1,
+            NULL,
+            &error);
+    if (error) {
+        g_warning ("%s: %s", G_STRFUNC, error->message);
+        g_error_free (error);
+        return;
+    }
+
+    g_variant_get (result, "(v)", &variant);
+    g_assert (variant);
+    g_variant_iter_init (&iter, variant);
+    size = g_variant_iter_n_children (&iter);
+    while (size >0 && g_variant_iter_loop (&iter, "(yv)", &type, &vtext)) {
+        IBusText *text =
+                (IBusText *)ibus_serializable_deserialize_object (vtext);
+        if (!IBUS_IS_TEXT (text)) {
+            g_warning ("%s: %s", G_STRFUNC, "text is not IBusText");
+            break;
+        }
+        switch (type) {
+        case 'c':
+            g_signal_emit (context, context_signals[COMMIT_TEXT], 0, text);
+            break;
+        case 'f': {
+            gchar **array = NULL;
+            guint keyval, keycode, state;
+            array = g_strsplit (text->text, ",", -1);
+            keyval = g_ascii_strtoull (array[0], NULL, 10);
+            keycode = g_ascii_strtoull (array[1], NULL, 10);
+            state = g_ascii_strtoull (array[2], NULL, 10);
+            g_strfreev (array);
+            g_signal_emit (context,
+                           context_signals[FORWARD_KEY_EVENT],
+                           0,
+                           keyval,
+                           keycode,
+                           state | IBUS_FORWARD_MASK);
+            break;
+        }
+        default:
+            g_warning ("%s: Type '%c' is not supported.", G_STRFUNC, type);
+        }
+        if (g_object_is_floating (text)) {
+            g_object_ref_sink (text);
+            g_object_unref (text);
+        }
+        g_clear_pointer (&vtext, g_variant_unref);
+    }
+
+    g_variant_unref (variant);
+    g_variant_unref (result);
+}
+
 #define DEFINE_FUNC(name, Name)                                         \
     void                                                                \
     ibus_input_context_##name (IBusInputContext *context)               \

--- a/src/ibusinputcontext.h
+++ b/src/ibusinputcontext.h
@@ -519,9 +519,37 @@
  *
  * See also ibus_engine_update_preedit_text_with_mode().
  */
-void         ibus_input_context_set_client_commit_preedit (
-                                             IBusInputContext   *context,
+void         ibus_input_context_set_client_commit_preedit
+                                            (IBusInputContext   *context,
                                              gboolean            client_commit);
+/**
+ * ibus_input_context_set_post_process_key_event:
+ * @context: An #IBusInputContext.
+ * @enable: Can use ibus_input_context_post_process_key_event() to retrieve
+ * commit-text and forwar-key-event signals during
+ * calling ibus_input_context_process_key_event() if it's %TRUE.
+ *
+ * Since: 1.5.00
+ * Stability: Unstable
+ */
+void         ibus_input_context_set_post_process_key_event
+                                            (IBusInputContext   *context,
+                                             gboolean            enable);
+/**
+ * ibus_input_context_post_process_key_event:
+ * @context: An #IBusInputContext.
+ *
+ * Call this API after ibus_input_context_process_key_event() returns
+ * to retrieve commit-text and forwar-key-event signals during
+ * calling ibus_input_context_process_key_event().
+ *
+ * See also ibus_input_context_set_post_process_key_event().
+ *
+ * Since: 1.5.00
+ * Stability: Unstable
+ */
+void         ibus_input_context_post_process_key_event
+                                            (IBusInputContext   *context);
 
 G_END_DECLS
 #endif
openSUSE Build Service is sponsored by