Add asynchronous InputContext.IsEnabled and InputContext.GetEngine APIs.
authorYusuke Sato <yusukes@chromium.org>
Tue, 22 Mar 2011 05:09:34 +0000 (14:09 +0900)
committerYusuke Sato <yusukes@chromium.org>
Tue, 22 Mar 2011 05:09:34 +0000 (14:09 +0900)
* Added asynchronous InputContext.IsEnabled and InputContext.GetEngine APIs. Now
all InputContext IPCs can be async.
* Added comments to src/ibusinputcontext.h.
* Moved input context tests from ibus-bus.c to ibus-inputcontext.c (new file),
and fixed flaky tests.
* Fixed typos in bus/.

BUG=http://code.google.com/p/ibus/issues/detail?id=1215
TEST=ran the new test

Review URL: http://codereview.appspot.com/4298049

bus/ibusimpl.c
bus/inputcontext.c
src/ibusinputcontext.c
src/ibusinputcontext.h
src/tests/.gitignore
src/tests/Makefile.am
src/tests/ibus-bus.c
src/tests/ibus-inputcontext.c [new file with mode: 0644]

index dd11457..739df0b 100644 (file)
@@ -1001,7 +1001,10 @@ bus_ibus_impl_get_engine_desc (BusIBusImpl *ibus,
     if (engine_name != NULL && engine_name[0] != '\0') {
         /* request engine by name */
         desc = _find_engine_desc_by_name (ibus, engine_name);
-        g_return_val_if_fail (desc != NULL, NULL);
+        if (desc == NULL) {
+            g_warning ("_context_request_engine_cb: Invalid engine '%s' is requested.", engine_name);
+            return NULL;
+        }
     }
     else {
         /* Use global engine if possible. */
index 8d2a36a..f36a73f 100644 (file)
@@ -1001,7 +1001,7 @@ _ic_get_engine (BusInputContext       *context,
     }
     else {
         g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
-                        "Input context does have engine.");
+                        "Input context does not have engine.");
     }
 }
 
index 0515eda..a345986 100644 (file)
@@ -698,7 +698,7 @@ ibus_input_context_get_input_context (const gchar     *path,
 
     context = ibus_input_context_new (path, connection, NULL, &error);
     if (context == NULL) {
-        g_warning ("%s", error->message);
+        g_warning ("ibus_input_context_get_input_context: %s", error->message);
         g_error_free (error);
         return NULL;
     }
@@ -790,7 +790,6 @@ ibus_input_context_process_key_event_async_finish (IBusInputContext  *context,
     }
 }
 
-
 gboolean
 ibus_input_context_process_key_event (IBusInputContext *context,
                                       guint32           keyval,
@@ -905,6 +904,45 @@ ibus_input_context_property_hide (IBusInputContext *context,
                        );
 }
 
+void
+ibus_input_context_is_enabled_async (IBusInputContext   *context,
+                                     gint                timeout_msec,
+                                     GCancellable       *cancellable,
+                                     GAsyncReadyCallback callback,
+                                     gpointer            user_data)
+{
+    g_assert (IBUS_IS_INPUT_CONTEXT (context));
+    g_dbus_proxy_call ((GDBusProxy *) context,
+                       "IsEnabled",               /* method_name */
+                       NULL,                      /* parameters */
+                       G_DBUS_CALL_FLAGS_NONE,    /* flags */
+                       timeout_msec,
+                       cancellable,
+                       callback,
+                       user_data);
+}
+
+gboolean
+ibus_input_context_is_enabled_async_finish (IBusInputContext   *context,
+                                            GAsyncResult       *res,
+                                            gboolean           *retval,
+                                            GError            **error)
+{
+    g_assert (IBUS_IS_INPUT_CONTEXT (context));
+    g_assert (G_IS_ASYNC_RESULT (res));
+    g_assert (retval != NULL);
+    g_assert (error == NULL || *error == NULL);
+
+    GVariant *variant = g_dbus_proxy_call_finish ((GDBusProxy *) context,
+                                                   res, error);
+    if (variant == NULL) {
+        return FALSE;
+    }
+    g_variant_get (variant, "(b)", retval);
+    g_variant_unref (variant);
+    return TRUE;
+}
+
 gboolean
 ibus_input_context_is_enabled (IBusInputContext *context)
 {
@@ -933,6 +971,47 @@ ibus_input_context_is_enabled (IBusInputContext *context)
     return retval;
 }
 
+void
+ibus_input_context_get_engine_async (IBusInputContext   *context,
+                                     gint                timeout_msec,
+                                     GCancellable       *cancellable,
+                                     GAsyncReadyCallback callback,
+                                     gpointer            user_data)
+{
+    g_assert (IBUS_IS_INPUT_CONTEXT (context));
+    g_dbus_proxy_call ((GDBusProxy *) context,
+                       "GetEngine",               /* method_name */
+                       NULL,                      /* parameters */
+                       G_DBUS_CALL_FLAGS_NONE,    /* flags */
+                       timeout_msec,
+                       cancellable,
+                       callback,
+                       user_data);
+}
+
+IBusEngineDesc *
+ibus_input_context_get_engine_async_finish (IBusInputContext   *context,
+                                            GAsyncResult       *res,
+                                            GError            **error)
+{
+    g_assert (IBUS_IS_INPUT_CONTEXT (context));
+    g_assert (G_IS_ASYNC_RESULT (res));
+    g_assert (error == NULL || *error == NULL);
+
+    GVariant *variant = g_dbus_proxy_call_finish ((GDBusProxy *) context,
+                                                   res, error);
+    if (variant == NULL) {
+        return NULL;
+    }
+
+    GVariant *engine_desc_variant = g_variant_get_child_value (variant, 0);
+    IBusEngineDesc *desc = IBUS_ENGINE_DESC (ibus_serializable_deserialize (engine_desc_variant));
+    g_variant_unref (engine_desc_variant);
+    g_variant_unref (variant);
+
+    return desc;
+}
+
 IBusEngineDesc *
 ibus_input_context_get_engine (IBusInputContext *context)
 {
@@ -954,9 +1033,9 @@ ibus_input_context_get_engine (IBusInputContext *context)
         return NULL;
     }
 
-    GVariant *variant = g_variant_get_child_value (result, 0);
-    IBusEngineDesc *desc = IBUS_ENGINE_DESC (ibus_serializable_deserialize (variant));
-    g_variant_unref (variant);
+    GVariant *engine_desc_variant = g_variant_get_child_value (result, 0);
+    IBusEngineDesc *desc = IBUS_ENGINE_DESC (ibus_serializable_deserialize (engine_desc_variant));
+    g_variant_unref (engine_desc_variant);
     g_variant_unref (result);
 
     return desc;
index 443d6a9..f96b6b6 100644 (file)
@@ -246,7 +246,7 @@ gboolean     ibus_input_context_process_key_event_async_finish
  * @state: Key modifier flags.
  * @returns: TRUE for successfully process the key; FALSE otherwise.
  *
- * Pass the key event to input method engine and wait for the reply from ibus.
+ * Pass the key event to input method engine and wait for the reply from ibus (i.e. synchronous IPC).
  *
  * @see_also: ibus_input_context_process_key_event_async()
  */
@@ -265,7 +265,7 @@ gboolean     ibus_input_context_process_key_event
  * @w: Width of the cursor.
  * @h: Height of the cursor.
  *
- * Set the cursor location of IBus input context.
+ * Set the cursor location of IBus input context asynchronously.
  *
  * see_also: #IBusEngine::set-cursor-location
  */
@@ -280,7 +280,7 @@ void         ibus_input_context_set_cursor_location
  * @context: An IBusInputContext.
  * @capabilities: Capabilities flags of IBusEngine, see #IBusCapabilite
  *
- * Set the capabilities flags of client application.
+ * Set the capabilities flags of client application asynchronously.
  * When IBUS_CAP_FOCUS is not set, IBUS_CAP_PREEDIT_TEXT, IBUS_CAP_AUXILIARY_TEXT, IBUS_CAP_LOOKUP_TABLE, and IBUS_CAP_PROPERTY have to be all set.
  * The panel component does nothing for an application that doesn't support focus.
  *
@@ -296,7 +296,7 @@ void         ibus_input_context_set_capabilities
  * @prop_name: A property name (e.g. "InputMode.WideLatin")
  * @state: A status of the property (e.g. PROP_STATE_CHECKED)
  *
- * Activate the property.
+ * Activate the property asynchronously.
  *
  * @see_also: #IBusEngine::property_activate
  */
@@ -309,7 +309,7 @@ void         ibus_input_context_property_activate
  * ibus_input_context_focus_in:
  * @context: An IBusInputContext.
  *
- * Invoked when the client application get focus.
+ * Invoked when the client application get focus. An asynchronous IPC will be performed.
  *
  * see_also: #IBusEngine::focus_in.
  */
@@ -319,7 +319,7 @@ void         ibus_input_context_focus_in    (IBusInputContext   *context);
  * ibus_input_context_focus_out:
  * @context: An IBusInputContext.
  *
- * Invoked when the client application get focus.
+ * Invoked when the client application get focus. An asynchronous IPC will be performed.
  *
  * see_also: #IBusEngine::focus_out.
  */
@@ -330,7 +330,7 @@ void         ibus_input_context_focus_out   (IBusInputContext   *context);
  * ibus_input_context_reset:
  * @context: An IBusInputContext.
  *
- * Invoked when the IME is reset.
+ * Invoked when the IME is reset. An asynchronous IPC will be performed.
  *
  * see_also: #IBusEngine::reset
  */
@@ -341,6 +341,7 @@ void         ibus_input_context_reset       (IBusInputContext   *context);
  * @context: An IBusInputContext.
  *
  * Invoked when the IME is enabled, either by IME switch hotkey or select from the menu.
+ * An asynchronous IPC will be performed.
  *
  * see_also: #IBusEngine::enable
  */
@@ -351,6 +352,7 @@ void         ibus_input_context_enable      (IBusInputContext   *context);
  * @context: An IBusInputContext.
  *
  * Invoked when the IME is disabled, either by IME switch hotkey or select from the menu.
+ * An asynchronous IPC will be performed.
  *
  * see_also: #IBusEngine::disable
  */
@@ -358,20 +360,91 @@ void         ibus_input_context_disable     (IBusInputContext   *context);
 
 
 /**
+ * ibus_input_context_is_enabled_async:
+ * @context: An #IBusInputContext.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL
+ *      if you don't care about the result of the method invocation.
+ * @user_data: The data to pass to callback.
+ *
+ * An asynchronous IPC will be performed.
+ */
+void         ibus_input_context_is_enabled_async
+                                            (IBusInputContext   *context,
+                                             gint                timeout_msec,
+                                             GCancellable       *cancellable,
+                                             GAsyncReadyCallback callback,
+                                             gpointer            user_data);
+
+/**
+ * ibus_input_context_process_key_event_async_finish:
+ * @context: An #IBusInputContext.
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to
+ *   ibus_input_context_is_enabled_async().
+ * @retval: If the the context is enabled, it will be assigned to %TRUE, %FALSE otherwise.
+ * @error: Return location for error or %NULL.
+ * @returns: %TRUE for success; %FALSE otherwise.
+ *
+ * Finishes an operation started with ibus_input_context_process_key_event_async().
+ */
+gboolean     ibus_input_context_is_enabled_async_finish
+                                            (IBusInputContext   *context,
+                                             GAsyncResult       *res,
+                                             gboolean           *retval,
+                                             GError            **error);
+
+/**
  * ibus_input_context_is_enabled:
  * @context: An IBusInputContext.
  * @returns: TRUE if the IME is enabled on the context.
  *
  * Returns TRUE if the IME is enabled on the context.
+ * A asynchronous IPC will be performed.
  */
 gboolean     ibus_input_context_is_enabled  (IBusInputContext   *context);
 
 /**
+ * ibus_input_context_get_engine_async:
+ * @context: An #IBusInputContext.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL
+ *      if you don't care about the result of the method invocation.
+ * @user_data: The data to pass to callback.
+ *
+ * An asynchronous IPC will be performed.
+ */
+void         ibus_input_context_get_engine_async
+                                            (IBusInputContext   *context,
+                                             gint                timeout_msec,
+                                             GCancellable       *cancellable,
+                                             GAsyncReadyCallback callback,
+                                             gpointer            user_data);
+
+/**
+ * ibus_input_context_process_key_event_async_finish:
+ * @context: An #IBusInputContext.
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to
+ *   ibus_input_context_get_engine_async().
+ * @error: Return location for error or %NULL.
+ * @returns: (transfer none): An IME engine description for the context, or %NULL.
+ *
+ * Finishes an operation started with ibus_input_context_process_key_event_async().
+ */
+IBusEngineDesc *
+             ibus_input_context_get_engine_async_finish
+                                            (IBusInputContext   *context,
+                                             GAsyncResult       *res,
+                                             GError            **error);
+
+/**
  * ibus_input_context_get_engine:
  * @context: An IBusInputContext.
- * @returns: (transfer none): An IME engine description for the context
+ * @returns: (transfer none): An IME engine description for the context, or NULL.
  *
  * Returns an IME engine description for the context.
+ * A synchronous IPC will be performed.
  */
 IBusEngineDesc
             *ibus_input_context_get_engine  (IBusInputContext   *context);
@@ -382,6 +455,7 @@ IBusEngineDesc
  * @name: A name of the engine.
  *
  * Invoked when the IME engine is changed.
+ * An asynchronous IPC will be performed.
  */
 void         ibus_input_context_set_engine  (IBusInputContext   *context,
                                              const gchar        *name);
index 1136238..9229e83 100644 (file)
@@ -21,6 +21,7 @@
 /ibus-bus
 /ibus-configservice
 /ibus-factory
+/ibus-inputcontext
 /ibus-keynames
 /ibus-serializable
 /ibus-share
index 16d9924..d143537 100644 (file)
@@ -38,6 +38,7 @@ prog_ldadd =                  \
 noinst_PROGRAMS = $(TESTS)
 TESTS =               \
        ibus-bus          \
+       ibus-inputcontext \
        ibus-keynames     \
        ibus-serializable \
        ibus-share        \
@@ -48,6 +49,9 @@ TESTS =               \
 ibus_bus_SOURCES = ibus-bus.c
 ibus_bus_LDADD = $(prog_ldadd)
 
+ibus_inputcontext_SOURCES = ibus-inputcontext.c
+ibus_inputcontext_LDADD = $(prog_ldadd)
+
 ibus_keynames_SOURCES = ibus-keynames.c
 ibus_keynames_LDADD = $(prog_ldadd)
 
index c562825..9117450 100644 (file)
@@ -46,57 +46,6 @@ test_list_engines (void)
     g_list_free (engines);
 }
 
-#if 0
-static gchar *
-get_last_engine_id (const GList *engines)
-{
-    const char *result = NULL;
-    for (; engines; engines = g_list_next (engines)) {
-        IBusEngineDesc *engine_desc = IBUS_ENGINE_DESC (engines->data);
-        g_assert (engine_desc);
-        result = ibus_engine_desc_get_name (engine_desc);
-    }
-    return g_strdup (result);
-}
-
-static void
-test_input_context (void)
-{
-    GList *engines;
-    gchar *active_engine_name = NULL;
-    IBusInputContext *context;
-    IBusEngineDesc *engine_desc;
-    gchar *current_ic;
-
-    engines = ibus_bus_list_active_engines (bus);
-    if (engines == NULL)
-        return;
-    active_engine_name = get_last_engine_id (engines);
-    g_assert (active_engine_name);
-
-    context = ibus_bus_create_input_context (bus, "test");
-    ibus_input_context_set_capabilities (context, IBUS_CAP_FOCUS);
-    ibus_input_context_disable (context);
-    g_assert (ibus_input_context_is_enabled (context) == FALSE);
-    ibus_input_context_enable (context);
-    g_assert (ibus_input_context_is_enabled (context) == TRUE);
-    ibus_input_context_focus_in (context);
-    ibus_input_context_set_engine (context, active_engine_name);
-    current_ic = ibus_bus_current_input_context (bus);
-    g_assert (!strcmp (current_ic, g_dbus_proxy_get_object_path ((GDBusProxy *)context)));
-    engine_desc = ibus_input_context_get_engine (context);
-    g_assert (engine_desc);
-    g_assert (!strcmp (active_engine_name, ibus_engine_desc_get_name(engine_desc)));
-    g_free (current_ic);
-    g_object_unref (engine_desc);
-    g_object_unref (context);
-
-    g_free (active_engine_name);
-    g_list_foreach (engines, (GFunc) g_object_unref, NULL);
-    g_list_free (engines);
-}
-#endif
-
 static void call_next_async_function (void);
 
 static void
@@ -279,9 +228,9 @@ finish_create_input_context_async_failed (GObject      *source_object,
     IBusInputContext *context =
             ibus_bus_create_input_context_async_finish (bus, res, &error);
 
-        g_assert (context == NULL);
-        g_assert (error != NULL);
-        g_error_free (error);
+    g_assert (context == NULL);
+    g_assert (error != NULL);
+    g_error_free (error);
     if (--create_input_context_count <= 0)
         g_main_loop_quit (loop);
 }
@@ -634,9 +583,6 @@ main (gint    argc,
                      test_create_input_context_async);
     g_test_add_func ("/ibus/async-apis", test_async_apis);
 
-    // FIXME This test does not pass if global engine is not available. Disabling it for now.
-    // g_test_add_func ("/ibus/input_context", test_input_context);
-
     result = g_test_run ();
     g_object_unref (bus);
 
diff --git a/src/tests/ibus-inputcontext.c b/src/tests/ibus-inputcontext.c
new file mode 100644 (file)
index 0000000..669fb47
--- /dev/null
@@ -0,0 +1,258 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* vim:set et sts=4: */
+/* ibus - The Input Bus
+ * Copyright (C) 2011 Google, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.         See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include "ibus.h"
+
+static IBusBus *bus;
+static void
+call_next_async_function (IBusInputContext *context);
+
+static gboolean
+fatal_handler(const gchar *log_domain,
+              GLogLevelFlags log_level,
+              const gchar *message,
+              gpointer user_data)
+{
+    if (!g_strcmp0 (message, "org.freedesktop.IBus.InputContext.GetEngine: GDBus.Error:org.freedesktop.DBus.Error.Failed: Input context does not have engine."))
+        return FALSE; /* do not call abort. */
+    return TRUE;
+}
+
+static gchar *
+get_last_engine_id (const GList *engines)
+{
+    const char *result = NULL;
+    for (; engines; engines = g_list_next (engines)) {
+        IBusEngineDesc *engine_desc = IBUS_ENGINE_DESC (engines->data);
+        g_assert (engine_desc);
+        result = ibus_engine_desc_get_name (engine_desc);
+    }
+    return g_strdup (result);
+}
+
+static void
+call_basic_ipcs (IBusInputContext *context)
+{
+    ibus_input_context_set_cursor_location (context, 0, 0, 1, 1);
+    ibus_input_context_set_capabilities (context, IBUS_CAP_FOCUS);
+    ibus_input_context_property_activate (context, "dummy.prop.name", PROP_STATE_CHECKED);
+    ibus_input_context_reset (context);
+    ibus_input_context_disable (context);
+    /* g_assert (ibus_input_context_is_enabled (context) == FALSE); */ /* see below. */
+    ibus_input_context_enable (context);
+    /* g_assert (ibus_input_context_is_enabled (context) == TRUE); */ /* see below. */
+
+    /* When enable() is called, ibus-daemon may start a global (or preloaded,
+     * or default) engine in an asynchrnous manner and return immediately.
+     * Therefore, it is not guaranteed that ibus_input_context_is_enabled()
+     * returns TRUE. */
+
+    ibus_input_context_focus_in (context);
+} 
+
+static void
+test_input_context (void)
+{
+    GList *engines;
+    gchar *active_engine_name = NULL;
+    IBusInputContext *context;
+    IBusEngineDesc *engine_desc;
+    gchar *current_ic;
+
+    context = ibus_bus_create_input_context (bus, "test");
+    call_basic_ipcs (context);
+
+    engines = ibus_bus_list_active_engines (bus);
+    if (engines != NULL) {
+        active_engine_name = get_last_engine_id (engines);
+    } else {
+        active_engine_name = g_strdup ("dummy-engine-name");
+    }
+    g_assert (active_engine_name);
+    g_debug ("Use '%s' for testing.", active_engine_name);
+
+    ibus_input_context_set_engine (context, active_engine_name);
+    current_ic = ibus_bus_current_input_context (bus);
+    g_assert (!strcmp (current_ic, g_dbus_proxy_get_object_path ((GDBusProxy *)context)));
+
+    g_test_log_set_fatal_handler (fatal_handler, NULL);
+    engine_desc = ibus_input_context_get_engine (context);
+    if (engine_desc) {
+        /* FIXME race condition between ibus_input_context_set_engine and _get_engine.
+         * ibus_input_context_get_engine is not guaranteed to return non-NULL
+         * (even if we use synchronous set_engine()) because ibus-daemon sets a context
+         * engine in an asynchronous manner. See _context_request_engine_cb in
+         * ibusimpl.c which handles context_signals[REQUEST_ENGINE] signal. */
+        g_assert (!strcmp (active_engine_name, ibus_engine_desc_get_name(engine_desc)));
+        g_object_unref (engine_desc);
+        engine_desc = NULL;
+    }
+    ibus_input_context_process_key_event (context, 0, 0, 0);
+
+    /* An engine is set. Try to call basic IPCs again. */
+    call_basic_ipcs (context);
+
+    g_free (current_ic);
+    g_object_unref (context);
+
+    g_free (active_engine_name);
+    g_list_foreach (engines, (GFunc) g_object_unref, NULL);
+    g_list_free (engines);
+}
+
+static void
+finish_is_enabled_async (GObject *source_object,
+                         GAsyncResult *res,
+                         gpointer user_data)
+{
+    IBusInputContext *context = IBUS_INPUT_CONTEXT (source_object);
+    GError *error = NULL;
+    gboolean retval = FALSE;
+    gboolean result = ibus_input_context_is_enabled_async_finish (context,
+                                                                  res,
+                                                                  &retval,
+                                                                  &error);
+    g_assert (result);
+    g_assert (retval);
+    g_debug ("ibus_context_is_enabled_async_finish: OK");
+    call_next_async_function (context);
+}
+
+static void
+start_is_enabled_async (IBusInputContext *context)
+{
+    ibus_input_context_is_enabled_async (context,
+                                         -1, /* timeout */
+                                         NULL, /* cancellable */
+                                         finish_is_enabled_async,
+                                         NULL); /* user_data */
+}
+
+static void
+finish_get_engine_async (GObject *source_object,
+                         GAsyncResult *res,
+                         gpointer user_data)
+{
+    IBusInputContext *context = IBUS_INPUT_CONTEXT (source_object);
+    GError *error = NULL;
+    IBusEngineDesc *desc = ibus_input_context_get_engine_async_finish (context,
+                                                                       res,
+                                                                       &error);
+    if (desc) {
+        g_object_unref (desc);
+    }
+    g_debug ("ibus_context_get_engine_async_finish: OK");
+    call_next_async_function (context);
+}
+
+static void
+start_get_engine_async (IBusInputContext *context)
+{
+    ibus_input_context_get_engine_async (context,
+                                         -1, /* timeout */
+                                         NULL, /* cancellable */
+                                         finish_get_engine_async,
+                                         NULL); /* user_data */
+}
+
+static void
+finish_process_key_event_async (GObject *source_object,
+                         GAsyncResult *res,
+                         gpointer user_data)
+{
+    IBusInputContext *context = IBUS_INPUT_CONTEXT (source_object);
+    GError *error = NULL;
+    gboolean processed = FALSE;
+    gboolean result = ibus_input_context_process_key_event_async_finish (context,
+                                                                         res,
+                                                                         &processed,
+                                                                         &error);
+    g_assert (result);
+    g_debug ("ibus_context_process_key_event_async_finish: OK");
+    call_next_async_function (context);
+}
+
+static void
+start_process_key_event_async (IBusInputContext *context)
+{
+    ibus_input_context_process_key_event_async (context,
+                                                0, 0, 0,
+                                                -1, /* timeout */
+                                                NULL, /* cancellable */
+                                                finish_process_key_event_async,
+                                                NULL); /* user_data */
+}
+
+static gboolean
+test_async_apis_finish (gpointer user_data)
+{
+    ibus_quit ();
+    return FALSE;
+}
+
+static void
+test_async_apis (void)
+{
+    g_debug ("start");
+    IBusInputContext *context;
+    context = ibus_bus_create_input_context (bus, "test");
+    call_basic_ipcs (context);
+
+    call_next_async_function (context);
+    ibus_main ();
+}
+
+static void
+call_next_async_function (IBusInputContext *context)
+{
+    static void (*async_functions[])(IBusInputContext *) = {
+        start_is_enabled_async,
+        start_get_engine_async,
+        start_process_key_event_async,
+    };
+    static guint index = 0;
+
+    // Use g_timeout_add to make sure test_async_apis finishes even if async_functions is empty.
+    if (index >= G_N_ELEMENTS (async_functions))
+        g_timeout_add (1, test_async_apis_finish, NULL);
+    else
+        (*async_functions[index++])(context);
+}
+
+gint
+main (gint    argc,
+      gchar **argv)
+{
+    gint result;
+    g_type_init ();
+    g_test_init (&argc, &argv, NULL);
+    ibus_init ();
+    bus = ibus_bus_new ();
+
+    g_test_add_func ("/ibus/input_context", test_input_context);
+    g_test_add_func ("/ibus/input_context_async_with_callback", test_async_apis);
+
+    result = g_test_run ();
+    g_object_unref (bus);
+
+    return result;
+}