Blob Blame History Raw
From de91361ea502c63e54ceb23389b3c88b234f40a0 Mon Sep 17 00:00:00 2001
From: Daiki Ueno <ueno@unixuser.org>
Date: Thu, 21 Oct 2010 18:48:51 +0900
Subject: [PATCH] Support surrounding-text retrieval.

This change adds a new API function ibus_engine_get_surrounding_text().
In the implementation, engines do not proactively requests surrounding-text to
target applications via the GTK+ client (IBusIMContext) every time the
function is called.  Instead, IBusIMContext retrieves surrounding-text when
certain events occur in the input context (IBusInputContext) and maintains
it within the input context.

This logic is similar to Qt's QInputContext.  The following events trigger
surrounding-text retrieval:

- focus_in
- commit_text
- show_preedit_text
- (just before) process_key_event

Also,
- destroy
resets the current surrounding-text.
---
 bus/engineproxy.c               |   34 +++++++++++
 bus/engineproxy.h               |    7 ++
 bus/inputcontext.c              |   48 ++++++++++++++++
 client/gtk2/ibusimcontext.c     |   80 ++++++++++++++++++++++++--
 ibus/engine.py                  |    6 ++
 ibus/interface/iengine.py       |    3 +
 ibus/interface/iinputcontext.py |    3 +
 src/ibusengine.c                |  119 +++++++++++++++++++++++++++++++++++++++
 src/ibusengine.h                |   21 +++++++-
 src/ibusinputcontext.c          |   43 ++++++++++++++
 src/ibusinputcontext.h          |   11 ++++
 src/ibusmarshalers.list         |    1 +
 12 files changed, 368 insertions(+), 8 deletions(-)

diff --git a/bus/engineproxy.c b/bus/engineproxy.c
index 03592d7..915f999 100644
--- a/bus/engineproxy.c
+++ b/bus/engineproxy.c
@@ -52,6 +52,8 @@ enum {
 static guint    engine_signals[LAST_SIGNAL] = { 0 };
 // static guint            engine_signals[LAST_SIGNAL] = { 0 };
 
+static IBusText *text_empty = NULL;
+
 /* functions prototype */
 static void     bus_engine_proxy_real_destroy   (BusEngineProxy         *engine);
 
@@ -302,6 +304,8 @@ bus_engine_proxy_class_init (BusEngineProxyClass *klass)
             1,
             IBUS_TYPE_PROPERTY);
 
+    text_empty = ibus_text_new_from_static_string ("");
+    g_object_ref_sink (text_empty);
 }
 
 static void
@@ -316,6 +320,8 @@ bus_engine_proxy_init (BusEngineProxy *engine)
     engine->enabled = FALSE;
     engine->desc = NULL;
     engine->keymap = NULL;
+    engine->surrounding_text = g_object_ref_sink (text_empty);
+    engine->surrounding_cursor_pos = 0;
 }
 
 static void
@@ -337,6 +343,11 @@ bus_engine_proxy_real_destroy (BusEngineProxy *engine)
         engine->keymap = NULL;
     }
 
+    if (engine->surrounding_text) {
+        g_object_unref (engine->surrounding_text);
+        engine->surrounding_text = NULL;
+    }
+
     IBUS_OBJECT_CLASS(bus_engine_proxy_parent_class)->destroy (IBUS_OBJECT (engine));
 }
 
@@ -733,6 +744,29 @@ void bus_engine_proxy_property_hide (BusEngineProxy *engine,
                      G_TYPE_INVALID);
 }
 
+void bus_engine_proxy_set_surrounding_text (BusEngineProxy *engine,
+                                            IBusText       *text,
+                                            guint           cursor_pos)
+{
+    g_assert (BUS_IS_ENGINE_PROXY (engine));
+    g_assert (text != NULL);
+
+    if (!engine->surrounding_text ||
+        g_strcmp0 (text->text, engine->surrounding_text->text) != 0 ||
+        cursor_pos != engine->surrounding_cursor_pos) {
+        if (engine->surrounding_text)
+            g_object_unref (engine->surrounding_text);
+        engine->surrounding_text = (IBusText *) g_object_ref_sink (text);
+        engine->surrounding_cursor_pos = cursor_pos;
+
+        ibus_proxy_call ((IBusProxy *) engine,
+                         "SetSurroundingText",
+                         IBUS_TYPE_TEXT, &text,
+                         G_TYPE_UINT, &cursor_pos,
+                         G_TYPE_INVALID);
+    }
+}
+
 #define DEFINE_FUNCTION(Name, name)                         \
     void                                                    \
     bus_engine_proxy_##name (BusEngineProxy *engine)        \
diff --git a/bus/engineproxy.h b/bus/engineproxy.h
index 254b00c..b0e211d 100644
--- a/bus/engineproxy.h
+++ b/bus/engineproxy.h
@@ -59,6 +59,9 @@ struct _BusEngineProxy {
     gint y;
     gint w;
     gint h;
+    /* surrounding text */
+    IBusText *surrounding_text;
+    guint     surrounding_cursor_pos;
 
     IBusEngineDesc *desc;
     IBusKeymap     *keymap;
@@ -112,5 +115,9 @@ void             bus_engine_proxy_property_show     (BusEngineProxy *engine,
 void             bus_engine_proxy_property_hide     (BusEngineProxy *engine,
                                                      const gchar    *prop_name);
 gboolean         bus_engine_proxy_is_enabled        (BusEngineProxy *engine);
+void             bus_engine_proxy_set_surrounding_text
+                                                    (BusEngineProxy *engine,
+                                                     IBusText       *text,
+                                                     guint           cursor_pos);
 G_END_DECLS
 #endif
diff --git a/bus/inputcontext.c b/bus/inputcontext.c
index b5ab201..1aa9f21 100644
--- a/bus/inputcontext.c
+++ b/bus/inputcontext.c
@@ -569,6 +569,10 @@ _ibus_introspect (BusInputContext   *context,
         "      <arg name=\"desc\" direction=\"out\" type=\"v\"/>\n"
         "    </method>\n"
         "    <method name=\"Destroy\"/>\n"
+        "    <method name=\"SetSurroundingText\">\n"
+        "      <arg name=\"text\" direction=\"in\" type=\"v\"/>\n"
+        "      <arg name=\"cursor_pos\" direction=\"in\" type=\"u\"/>\n"
+        "    </method>\n"
 
         /* signals */
         "    <signal name=\"CommitText\">\n"
@@ -1098,6 +1102,49 @@ _ic_destroy (BusInputContext  *context,
     return NULL;
 }
 
+static IBusMessage *
+_ic_set_surrounding_text (BusInputContext  *context,
+                          IBusMessage      *message,
+                          BusConnection    *connection)
+{
+    g_assert (BUS_IS_INPUT_CONTEXT (context));
+    g_assert (message != NULL);
+    g_assert (BUS_IS_CONNECTION (connection));
+
+    IBusMessage *reply;
+    IBusText *text;
+    guint cursor_pos = 0;
+    gboolean retval;
+    IBusError *error;
+
+    retval = ibus_message_get_args (message,
+                                    &error,
+                                    IBUS_TYPE_TEXT, &text,
+                                    G_TYPE_UINT, &cursor_pos,
+                                    G_TYPE_INVALID);
+
+    if (!retval) {
+        reply = ibus_message_new_error (message,
+                                        error->name,
+                                        error->message);
+        ibus_error_free (error);
+        return reply;
+    }
+
+    if ((context->capabilities & IBUS_CAP_SURROUNDING_TEXT) &&
+        context->has_focus && context->enabled && context->engine) {
+        bus_engine_proxy_set_surrounding_text (context->engine,
+                                               text,
+                                               cursor_pos);
+    }
+
+    if (g_object_is_floating (text))
+        g_object_unref (text);
+
+    reply = ibus_message_new_method_return (message);
+    return reply;
+}
+
 static gboolean
 bus_input_context_ibus_message (BusInputContext *context,
                                 BusConnection   *connection,
@@ -1132,6 +1179,7 @@ bus_input_context_ibus_message (BusInputContext *context,
         { IBUS_INTERFACE_INPUT_CONTEXT, "SetEngine",         _ic_set_engine },
         { IBUS_INTERFACE_INPUT_CONTEXT, "GetEngine",         _ic_get_engine },
         { IBUS_INTERFACE_INPUT_CONTEXT, "Destroy",           _ic_destroy },
+        { IBUS_INTERFACE_INPUT_CONTEXT, "SetSurroundingText",_ic_set_surrounding_text },
     };
 
     ibus_message_set_sender (message, bus_connection_get_unique_name (connection));
diff --git a/client/gtk2/ibusimcontext.c b/client/gtk2/ibusimcontext.c
index 0bb71b5..eaa4a54 100644
--- a/client/gtk2/ibusimcontext.c
+++ b/client/gtk2/ibusimcontext.c
@@ -113,6 +113,12 @@ static void     ibus_im_context_set_cursor_location
 static void     ibus_im_context_set_use_preedit
                                             (GtkIMContext           *context,
                                              gboolean               use_preedit);
+static void     ibus_im_context_set_surrounding
+                                            (GtkIMContext  *slave,
+                                             const gchar   *text,
+                                             gint           len,
+                                             gint           cursor_index);
+
 
 /* static methods*/
 static void     _create_input_context       (IBusIMContext      *context);
@@ -131,15 +137,16 @@ static void     _slave_preedit_start_cb     (GtkIMContext       *slave,
                                              IBusIMContext       *context);
 static void     _slave_preedit_end_cb       (GtkIMContext       *slave,
                                              IBusIMContext       *context);
-static void     _slave_retrieve_surrounding_cb
+static gboolean _slave_retrieve_surrounding_cb
                                             (GtkIMContext       *slave,
                                              IBusIMContext       *context);
-static void     _slave_delete_surrounding_cb
+static gboolean _slave_delete_surrounding_cb
                                             (GtkIMContext       *slave,
                                              gint               offset_from_cursor,
                                              guint              nchars,
                                              IBusIMContext       *context);
 static void     _create_fake_input_context  (void);
+static void     _request_surrounding_text   (IBusIMContext      *context);
 
 
 
@@ -206,6 +213,16 @@ ibus_im_context_new (void)
     return IBUS_IM_CONTEXT (obj);
 }
 
+static void
+_request_surrounding_text (IBusIMContext *context)
+{
+    if (context->enable) {
+        gboolean return_value;
+        g_signal_emit (context, _signal_retrieve_surrounding_id, 0,
+                       &return_value);
+    }
+}
+
 static gint
 _key_snooper_cb (GtkWidget   *widget,
                  GdkEventKey *event,
@@ -245,6 +262,8 @@ _key_snooper_cb (GtkWidget   *widget,
         _input_window = event->window;
     }
 
+    _request_surrounding_text (ibusimcontext);
+
     switch (event->type) {
     case GDK_KEY_RELEASE:
         retval = ibus_input_context_process_key_event (ibuscontext,
@@ -291,6 +310,7 @@ ibus_im_context_class_init     (IBusIMContextClass *klass)
     im_context_class->set_client_window = ibus_im_context_set_client_window;
     im_context_class->set_cursor_location = ibus_im_context_set_cursor_location;
     im_context_class->set_use_preedit = ibus_im_context_set_use_preedit;
+    im_context_class->set_surrounding = ibus_im_context_set_surrounding;
     gobject_class->finalize = ibus_im_context_finalize;
 
     _signal_commit_id =
@@ -488,6 +508,8 @@ ibus_im_context_filter_keypress (GtkIMContext *context,
         if (ibusimcontext->client_window == NULL && event->window != NULL)
             gtk_im_context_set_client_window ((GtkIMContext *)ibusimcontext, event->window);
 
+        _request_surrounding_text (ibusimcontext);
+
         switch (event->type) {
         case GDK_KEY_RELEASE:
             retval = ibus_input_context_process_key_event (ibusimcontext->ibuscontext,
@@ -552,6 +574,8 @@ ibus_im_context_focus_in (GtkIMContext *context)
         g_object_weak_ref ((GObject *) context, _weak_notify_cb, NULL);
         _focus_im_context = context;
     }
+
+    _request_surrounding_text (ibusimcontext);
 }
 
 static void
@@ -723,6 +747,39 @@ ibus_im_context_set_use_preedit (GtkIMContext *context, gboolean use_preedit)
 }
 
 static void
+ibus_im_context_set_surrounding (GtkIMContext  *context,
+                                 const gchar   *text,
+                                 gint           len,
+                                 gint           cursor_index)
+{
+    g_return_if_fail (context != NULL);
+    g_return_if_fail (IBUS_IS_IM_CONTEXT (context));
+    g_return_if_fail (text != NULL);
+    g_return_if_fail (strlen (text) >= len);
+    g_return_if_fail (0 <= cursor_index && cursor_index <= len);
+
+    IBusIMContext *ibusimcontext = IBUS_IM_CONTEXT (context);
+
+    if (ibusimcontext->enable && ibusimcontext->ibuscontext) {
+        IBusText *ibustext;
+        guint cursor_pos;
+        gchar *p;
+
+        p = g_strndup (text, len);
+        cursor_pos = g_utf8_strlen (p, cursor_index);
+        ibustext = ibus_text_new_from_string (p);
+        g_free (p);
+        ibus_input_context_set_surrounding_text (ibusimcontext->ibuscontext,
+                                                 ibustext,
+                                                 cursor_pos);
+    }
+    gtk_im_context_set_surrounding (ibusimcontext->slave,
+                                    text,
+                                    len,
+                                    cursor_index);
+}
+
+static void
 _bus_connected_cb (IBusBus          *bus,
                    IBusIMContext    *ibusimcontext)
 {
@@ -741,6 +798,8 @@ _ibus_context_commit_text_cb (IBusInputContext *ibuscontext,
     IDEBUG ("%s", __FUNCTION__);
 
     g_signal_emit (ibusimcontext, _signal_commit_id, 0, text->text);
+
+    _request_surrounding_text (ibusimcontext);
 }
 
 static gboolean
@@ -1013,6 +1072,8 @@ _ibus_context_show_preedit_text_cb (IBusInputContext   *ibuscontext,
     ibusimcontext->preedit_visible = TRUE;
     g_signal_emit (ibusimcontext, _signal_preedit_start_id, 0);
     g_signal_emit (ibusimcontext, _signal_preedit_changed_id, 0);
+
+    _request_surrounding_text (ibusimcontext);
 }
 
 static void
@@ -1177,17 +1238,21 @@ _slave_preedit_end_cb (GtkIMContext  *slave,
     g_signal_emit (ibusimcontext, _signal_preedit_end_id, 0);
 }
 
-static void
+static gboolean
 _slave_retrieve_surrounding_cb (GtkIMContext  *slave,
                                 IBusIMContext *ibusimcontext)
 {
+    gboolean return_value;
+
     if (ibusimcontext->enable && ibusimcontext->ibuscontext) {
-        return;
+        return FALSE;
     }
-    g_signal_emit (ibusimcontext, _signal_retrieve_surrounding_id, 0);
+    g_signal_emit (ibusimcontext, _signal_retrieve_surrounding_id, 0,
+                   &return_value);
+    return return_value;
 }
 
-static void
+static gboolean
 _slave_delete_surrounding_cb (GtkIMContext  *slave,
                               gint           offset_from_cursor,
                               guint          nchars,
@@ -1196,9 +1261,10 @@ _slave_delete_surrounding_cb (GtkIMContext  *slave,
     gboolean return_value;
 
     if (ibusimcontext->enable && ibusimcontext->ibuscontext) {
-        return;
+        return FALSE;
     }
     g_signal_emit (ibusimcontext, _signal_delete_surrounding_id, 0, offset_from_cursor, nchars, &return_value);
+    return return_value;
 }
 
 #ifdef OS_CHROMEOS
diff --git a/ibus/engine.py b/ibus/engine.py
index b1df2fe..ec42fa4 100644
--- a/ibus/engine.py
+++ b/ibus/engine.py
@@ -46,6 +46,9 @@ class EngineBase(object.Object):
     def set_cursor_location(self, x, y, w, h):
         pass
 
+    def set_surrounding_text(self, text, cursor_index):
+        pass
+
     def set_capabilities(self, cap):
         pass
 
@@ -163,6 +166,9 @@ class EngineProxy(interface.IEngine):
     def SetCursorLocation(self, x, y, w, h):
         return self.__engine.set_cursor_location(x, y, w, h)
 
+    def SetSurroundingText(self, text, cursor_index):
+        return self.__engine.set_surrounding_text(text, cursor_index)
+
     def SetCapabilities(self, caps):
         return self.__engine.set_capabilities(caps)
 
diff --git a/ibus/interface/iengine.py b/ibus/interface/iengine.py
index 4d42c2d..ee23efe 100644
--- a/ibus/interface/iengine.py
+++ b/ibus/interface/iengine.py
@@ -50,6 +50,9 @@ class IEngine(dbus.service.Object):
     @method(in_signature="iiii")
     def SetCursorLocation(self, x, y, w, h): pass
 
+    @method(in_signature="vu")
+    def SetSurroundingText(self, text, cursor_index): pass
+
     @method(in_signature="u")
     def SetCapabilities(self, cap): pass
 
diff --git a/ibus/interface/iinputcontext.py b/ibus/interface/iinputcontext.py
index 89f6dbd..2db1c9b 100644
--- a/ibus/interface/iinputcontext.py
+++ b/ibus/interface/iinputcontext.py
@@ -49,6 +49,9 @@ class IInputContext(dbus.service.Object):
     @method(in_signature="iiii")
     def SetCursorLocation(self, x, y, w, h): pass
 
+    @method(in_signature="vu")
+    def SetSurroundingText(self, text, cursor_index): pass
+
     @method()
     def FocusIn(self): pass
 
diff --git a/src/ibusengine.c b/src/ibusengine.c
index b5f53d4..da1e643 100644
--- a/src/ibusengine.c
+++ b/src/ibusengine.c
@@ -45,6 +45,7 @@ enum {
     PROPERTY_SHOW,
     PROPERTY_HIDE,
     CANDIDATE_CLICKED,
+    SET_SURROUNDING_TEXT,
     LAST_SIGNAL,
 };
 
@@ -59,11 +60,16 @@ enum {
 struct _IBusEnginePrivate {
     gchar *name;
     IBusConnection *connection;
+
+    IBusText *surrounding_text;
+    guint surrounding_cursor_pos;
 };
 typedef struct _IBusEnginePrivate IBusEnginePrivate;
 
 static guint            engine_signals[LAST_SIGNAL] = { 0 };
 
+static IBusText *text_empty = NULL;
+
 /* functions prototype */
 static void     ibus_engine_destroy         (IBusEngine         *engine);
 static void     ibus_engine_set_property    (IBusEngine         *engine,
@@ -113,6 +119,10 @@ static void     ibus_engine_property_show   (IBusEngine         *engine,
                                              const gchar        *prop_name);
 static void     ibus_engine_property_hide   (IBusEngine         *engine,
                                              const gchar        *prop_name);
+static void     ibus_engine_set_surrounding_text
+                                            (IBusEngine         *engine,
+                                             IBusText           *text,
+                                             guint               cursor_pos);
 
 
 G_DEFINE_TYPE (IBusEngine, ibus_engine, IBUS_TYPE_SERVICE)
@@ -167,6 +177,7 @@ ibus_engine_class_init (IBusEngineClass *klass)
     klass->property_hide        = ibus_engine_property_hide;
     klass->set_cursor_location  = ibus_engine_set_cursor_location;
     klass->set_capabilities     = ibus_engine_set_capabilities;
+    klass->set_surrounding_text = ibus_engine_set_surrounding_text;
 
 
     /* install properties */
@@ -544,6 +555,29 @@ ibus_engine_class_init (IBusEngineClass *klass)
             1,
             G_TYPE_STRING);
 
+    /**
+     * IBusEngine::set-surrounding-text:
+     * @engine: An IBusEngine.
+     *
+     * Emitted when a surrounding text is set.
+     * Implement the member function set_surrounding_text() in extended class to receive this signal.
+     *
+     * <note><para>Argument @user_data is ignored in this function.</para></note>
+     */
+    engine_signals[SET_SURROUNDING_TEXT] =
+        g_signal_new (I_("set-surrounding-text"),
+            G_TYPE_FROM_CLASS (gobject_class),
+            G_SIGNAL_RUN_LAST,
+            G_STRUCT_OFFSET (IBusEngineClass, set_surrounding_text),
+            NULL, NULL,
+            ibus_marshal_VOID__OBJECT_UINT,
+            G_TYPE_NONE,
+            2,
+            G_TYPE_OBJECT,
+            G_TYPE_UINT);
+
+    text_empty = ibus_text_new_from_static_string ("");
+    g_object_ref_sink (text_empty);
 }
 
 static void
@@ -554,6 +588,9 @@ ibus_engine_init (IBusEngine *engine)
 
     priv->name = NULL;
     priv->connection = NULL;
+
+    priv->surrounding_text = g_object_ref_sink (text_empty);
+    priv->surrounding_cursor_pos = 0;
 }
 
 static void
@@ -569,6 +606,11 @@ ibus_engine_destroy (IBusEngine *engine)
         priv->connection = NULL;
     }
 
+    if (priv->surrounding_text) {
+        g_object_unref (priv->surrounding_text);
+        priv->surrounding_text = NULL;
+    }
+
     IBUS_OBJECT_CLASS(ibus_engine_parent_class)->destroy (IBUS_OBJECT (engine));
 }
 
@@ -879,6 +921,33 @@ ibus_engine_ibus_message (IBusEngine     *engine,
             ibus_object_destroy ((IBusObject *) engine);
             return TRUE;
         }
+        else if (g_strcmp0 (name, "SetSurroundingText") == 0) {
+            IBusText *text;
+            guint cursor_pos;
+
+            retval = ibus_message_get_args (message,
+                                            &error,
+                                            IBUS_TYPE_TEXT, &text,
+                                            G_TYPE_UINT, &cursor_pos,
+                                            G_TYPE_INVALID);
+
+            if (!retval) {
+                reply = ibus_message_new_error_printf (message,
+                            DBUS_ERROR_INVALID_ARGS,
+                            "%s.%s: Can not match signature (u) of method",
+                            IBUS_INTERFACE_ENGINE, "SetSurroundingText");
+                ibus_error_free (error);
+            }
+            else {
+                g_signal_emit (engine, engine_signals[SET_SURROUNDING_TEXT], 0,
+                               text,
+                               cursor_pos);
+                if (g_object_is_floating (text)) {
+                    g_object_unref (text);
+                }
+                reply = ibus_message_new_method_return (message);
+            }
+        }
         else {
             reply = ibus_message_new_error_printf (message,
                         DBUS_ERROR_UNKNOWN_METHOD,
@@ -1003,6 +1072,26 @@ ibus_engine_property_hide (IBusEngine *engine, const gchar *prop_name)
 }
 
 static void
+ibus_engine_set_surrounding_text (IBusEngine *engine,
+                                  IBusText   *text,
+                                  guint       cursor_pos)
+{
+    g_assert (IBUS_IS_ENGINE (engine));
+
+    IBusEnginePrivate *priv;
+
+    priv = IBUS_ENGINE_GET_PRIVATE (engine);
+
+    if (priv->surrounding_text) {
+        g_object_unref (priv->surrounding_text);
+    }
+
+    priv->surrounding_text = (IBusText *) g_object_ref_sink (text ? text : text_empty);
+    priv->surrounding_cursor_pos = cursor_pos;
+    // g_debug ("set-surrounding-text ('%s', %d)", text->text, cursor_pos);
+}
+
+static void
 _send_signal (IBusEngine  *engine,
               const gchar *name,
               GType        first_arg_type,
@@ -1203,6 +1292,19 @@ void ibus_engine_delete_surrounding_text (IBusEngine      *engine,
                                           gint             offset_from_cursor,
                                           guint            nchars)
 {
+    g_assert (IBUS_IS_ENGINE (engine));
+
+    IBusEnginePrivate *priv;
+
+    priv = IBUS_ENGINE_GET_PRIVATE (engine);
+
+    /* Clear the current surrounding-text buffer. */
+    if (priv->surrounding_text) {
+        g_object_unref (priv->surrounding_text);
+        priv->surrounding_text = g_object_ref_sink (text_empty);
+        priv->surrounding_cursor_pos = 0;
+    }
+
     _send_signal (engine,
                   "DeleteSurroundingText",
                   G_TYPE_INT,  &offset_from_cursor,
@@ -1211,6 +1313,23 @@ void ibus_engine_delete_surrounding_text (IBusEngine      *engine,
 }
 
 void
+ibus_engine_get_surrounding_text (IBusEngine   *engine,
+                                  IBusText    **text,
+                                  guint        *cursor_pos)
+{
+    IBusEnginePrivate *priv;
+
+    g_return_if_fail (IBUS_IS_ENGINE (engine));
+    g_return_if_fail (text != NULL);
+    g_return_if_fail (cursor_pos != NULL);
+
+    priv = IBUS_ENGINE_GET_PRIVATE (engine);
+
+    *text = g_object_ref (priv->surrounding_text);
+    *cursor_pos = priv->surrounding_cursor_pos;
+}
+
+void
 ibus_engine_register_properties (IBusEngine   *engine,
                                  IBusPropList *prop_list)
 {
diff --git a/src/ibusengine.h b/src/ibusengine.h
index 95be408..2c6e1d3 100644
--- a/src/ibusengine.h
+++ b/src/ibusengine.h
@@ -124,10 +124,14 @@ struct _IBusEngineClass {
                                      guint           index,
                                      guint           button,
                                      guint           state);
+    void        (* set_surrounding_text)
+                                    (IBusEngine     *engine,
+                                     IBusText       *text,
+                                     guint           cursor_index);
 
     /*< private >*/
     /* padding */
-    gpointer pdummy[8];
+    gpointer pdummy[7];
 };
 
 GType        ibus_engine_get_type       (void);
@@ -367,6 +371,21 @@ void ibus_engine_delete_surrounding_text(IBusEngine         *engine,
                                          guint               nchars);
 
 /**
+ * ibus_engine_get_surrounding_text:
+ * @engine: An IBusEngine.
+ * @text: Location to store surrounding text.
+ * @cursor_pos: Cursor position in characters in @text.
+ *
+ * Get surrounding text.
+ *
+ * @see_also #IBusEngine::set-surrounding-text
+ */
+void ibus_engine_get_surrounding_text(IBusEngine         *engine,
+                                      IBusText          **text,
+                                      guint              *cursor_pos);
+
+
+/**
  * ibus_engine_get_name:
  * @engine: An IBusEngine.
  * @returns: Name of IBusEngine.
diff --git a/src/ibusinputcontext.c b/src/ibusinputcontext.c
index b00ad3b..158979e 100644
--- a/src/ibusinputcontext.c
+++ b/src/ibusinputcontext.c
@@ -58,12 +58,18 @@ enum {
 /* BusInputContextPriv */
 struct _IBusInputContextPrivate {
     gboolean own;
+
+    /* surrounding text */
+    IBusText *surrounding_text;
+    guint     surrounding_cursor_pos;
 };
 typedef struct _IBusInputContextPrivate IBusInputContextPrivate;
 
 static guint            context_signals[LAST_SIGNAL] = { 0 };
 // static guint            context_signals[LAST_SIGNAL] = { 0 };
 
+static IBusText *text_empty = NULL;
+
 /* functions prototype */
 static void     ibus_input_context_real_destroy (IBusInputContext       *context);
 static gboolean ibus_input_context_ibus_signal  (IBusProxy              *proxy,
@@ -474,6 +480,9 @@ ibus_input_context_class_init (IBusInputContextClass *klass)
             G_TYPE_NONE,
             1,
             IBUS_TYPE_PROPERTY);
+
+    text_empty = ibus_text_new_from_static_string ("");
+    g_object_ref_sink (text_empty);
 }
 
 static void
@@ -482,6 +491,8 @@ ibus_input_context_init (IBusInputContext *context)
     IBusInputContextPrivate *priv;
     priv = IBUS_INPUT_CONTEXT_GET_PRIVATE (context);
     priv->own = TRUE;
+    priv->surrounding_text = g_object_ref_sink (text_empty);
+    priv->surrounding_cursor_pos = 0;
 }
 
 static void
@@ -496,6 +507,11 @@ ibus_input_context_real_destroy (IBusInputContext *context)
                          G_TYPE_INVALID);
     }
 
+    if (priv->surrounding_text) {
+        g_object_unref (priv->surrounding_text);
+        priv->surrounding_text = NULL;
+    }
+
     IBUS_OBJECT_CLASS(ibus_input_context_parent_class)->destroy (IBUS_OBJECT (context));
 }
 
@@ -914,6 +930,33 @@ ibus_input_context_property_hide (IBusInputContext *context,
                      G_TYPE_INVALID);
 }
 
+void
+ibus_input_context_set_surrounding_text (IBusInputContext   *context,
+                                         IBusText           *text,
+                                         guint32             cursor_pos)
+{
+    g_assert (IBUS_IS_INPUT_CONTEXT (context));
+    g_assert (IBUS_IS_TEXT (text));
+
+    IBusInputContextPrivate *priv;
+    priv = IBUS_INPUT_CONTEXT_GET_PRIVATE (context);
+
+    if (priv->surrounding_text == NULL ||
+        g_strcmp0 (text->text, priv->surrounding_text->text) != 0 ||
+        cursor_pos != priv->surrounding_cursor_pos) {
+        if (priv->surrounding_text)
+            g_object_unref (priv->surrounding_text);
+        priv->surrounding_text = (IBusText *) g_object_ref_sink (text);
+        priv->surrounding_cursor_pos = cursor_pos;
+
+        ibus_proxy_call ((IBusProxy *) context,
+                         "SetSurroundingText",
+                         IBUS_TYPE_TEXT, &text,
+                         G_TYPE_UINT, &cursor_pos,
+                         G_TYPE_INVALID);
+    }
+}
+
 gboolean
 ibus_input_context_is_enabled (IBusInputContext *context)
 {
diff --git a/src/ibusinputcontext.h b/src/ibusinputcontext.h
index 0d508a1..7f3d3f6 100644
--- a/src/ibusinputcontext.h
+++ b/src/ibusinputcontext.h
@@ -36,6 +36,7 @@
 
 #include "ibusproxy.h"
 #include "ibusenginedesc.h"
+#include "ibustext.h"
 
 /*
  * Type macros.
@@ -267,6 +268,16 @@ IBusEngineDesc
 void         ibus_input_context_set_engine  (IBusInputContext   *context,
                                              const gchar        *name);
 
+/**
+ * ibus_input_context_set_surrounding_text:
+ * @context: An IBusInputContext.
+ * @text: An IBusText surrounding the current cursor on the application.
+ * @cursor_po: Current cursor position in characters in @text.
+*/
+void         ibus_input_context_set_surrounding_text
+                                            (IBusInputContext   *context,
+                                             IBusText           *text,
+                                             guint32             cursor_pos);
 
 G_END_DECLS
 #endif
diff --git a/src/ibusmarshalers.list b/src/ibusmarshalers.list
index 4473dab..6f80970 100644
--- a/src/ibusmarshalers.list
+++ b/src/ibusmarshalers.list
@@ -13,6 +13,7 @@ VOID:INT,INT,INT,INT
 VOID:UINT,UINT
 VOID:INT,UINT
 VOID:UINT,UINT,UINT
+VOID:OBJECT,UINT
 VOID:OBJECT,UINT,BOOL
 VOID:OBJECT,UINT,BOOL,UINT
 VOID:OBJECT,BOOL
-- 
1.7.2.1