From 27788fb3bf62411c2b13d24bac45eb060483162f Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Tue, 8 Feb 2011 18:27:00 +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 | 43 ++++++++++++ bus/engineproxy.h | 11 +++ bus/inputcontext.c | 36 ++++++++++ client/gtk2/ibusimcontext.c | 138 ++++++++++++++++++++++++++++++++++----- configure.ac | 14 ++++ ibus/engine.py | 6 ++ ibus/interface/iengine.py | 3 + ibus/interface/iinputcontext.py | 5 ++ src/ibusengine.c | 138 +++++++++++++++++++++++++++++++++++++++ src/ibusengine.h | 21 ++++++- src/ibusenginedesc.c | 132 +++++++++++++++++++++++++++++++++++++ src/ibusenginedesc.h | 11 +++ src/ibusinputcontext.c | 91 +++++++++++++++++++++++++ src/ibusinputcontext.h | 11 +++ src/ibusmarshalers.list | 1 + 15 files changed, 644 insertions(+), 17 deletions(-) diff --git a/bus/engineproxy.c b/bus/engineproxy.c index f808727..b35933f 100644 --- a/bus/engineproxy.c +++ b/bus/engineproxy.c @@ -47,6 +47,10 @@ struct _BusEngineProxy { /* a key mapping for the engine that converts keycode into keysym. the mapping is used only when use_sys_layout is FALSE. */ IBusKeymap *keymap; /* private member */ + + /* surrounding text */ + IBusText *surrounding_text; + guint surrounding_cursor_pos; }; struct _BusEngineProxyClass { @@ -83,6 +87,8 @@ enum { static guint engine_signals[LAST_SIGNAL] = { 0 }; +static IBusText *text_empty = NULL; + /* functions prototype */ static void bus_engine_proxy_set_property (BusEngineProxy *engine, guint prop_id, @@ -330,11 +336,16 @@ bus_engine_proxy_class_init (BusEngineProxyClass *class) G_TYPE_NONE, 1, IBUS_TYPE_PROPERTY); + + text_empty = ibus_text_new_from_static_string (""); + g_object_ref_sink (text_empty); } static void bus_engine_proxy_init (BusEngineProxy *engine) { + engine->surrounding_text = g_object_ref_sink (text_empty); + engine->surrounding_cursor_pos = 0; } static void @@ -393,6 +404,11 @@ bus_engine_proxy_real_destroy (IBusProxy *proxy) engine->keymap = NULL; } + if (engine->surrounding_text) { + g_object_unref (engine->surrounding_text); + engine->surrounding_text = NULL; + } + IBUS_PROXY_CLASS (bus_engine_proxy_parent_class)->destroy ((IBusProxy *)engine); } @@ -950,6 +966,33 @@ void bus_engine_proxy_property_hide (BusEngineProxy *engine, NULL); } +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) { + GVariant *variant = ibus_serializable_serialize ((IBusSerializable *)text); + 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; + + g_dbus_proxy_call ((GDBusProxy *)engine, + "SetSurroundingText", + g_variant_new ("(vu)", variant, cursor_pos), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); + } +} + /* a macro to generate a function to call a nullary D-Bus method. */ #define DEFINE_FUNCTION(Name, name) \ void \ diff --git a/bus/engineproxy.h b/bus/engineproxy.h index 2a82fc6..0680917 100644 --- a/bus/engineproxy.h +++ b/bus/engineproxy.h @@ -212,5 +212,16 @@ void bus_engine_proxy_property_hide (BusEngineProxy *engine, */ gboolean bus_engine_proxy_is_enabled (BusEngineProxy *engine); +/** + * bus_engine_proxy_set_surrounding_text: + * + * Call "SetSurroundingText" method of an engine asynchronously. + */ +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 32d51e8..20f561c 100644 --- a/bus/inputcontext.c +++ b/bus/inputcontext.c @@ -247,6 +247,11 @@ static const gchar introspection_xml[] = " " " " " " + " " + " " + " " + " " + /* signals */ " " " " @@ -961,6 +966,32 @@ _ic_get_engine (BusInputContext *context, * Handle a D-Bus method call whose destination and interface name are both "org.freedesktop.IBus.InputContext" */ static void +_ic_set_surrounding_text (BusInputContext *context, + GVariant *parameters, + GDBusMethodInvocation *invocation) +{ + GVariant *variant = NULL; + IBusText *text; + guint cursor_pos = 0; + + g_variant_get (parameters, "(vu)", &variant, &cursor_pos); + text = IBUS_TEXT (ibus_serializable_deserialize (variant)); + g_variant_unref (variant); + + 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); + + g_dbus_method_invocation_return_value (invocation, NULL); +} + +static void bus_input_context_service_method_call (IBusService *service, GDBusConnection *connection, const gchar *sender, @@ -999,6 +1030,7 @@ bus_input_context_service_method_call (IBusService *service, { "IsEnabled", _ic_is_enabled }, { "SetEngine", _ic_set_engine }, { "GetEngine", _ic_get_engine }, + { "SetSurroundingText", _ic_set_surrounding_text}, }; gint i; @@ -2059,6 +2091,10 @@ bus_input_context_set_engine (BusInputContext *context, bus_engine_proxy_set_cursor_location (context->engine, context->x, context->y, context->w, context->h); } } + bus_input_context_emit_signal (context, + "EngineChanged", + NULL, + NULL); g_signal_emit (context, context_signals[ENGINE_CHANGED], 0); diff --git a/client/gtk2/ibusimcontext.c b/client/gtk2/ibusimcontext.c index a4e46cb..d029b5c 100644 --- a/client/gtk2/ibusimcontext.c +++ b/client/gtk2/ibusimcontext.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "ibusimcontext.h" #if !GTK_CHECK_VERSION (2, 91, 0) @@ -60,7 +61,8 @@ struct _IBusIMContext { gboolean has_focus; guint32 time; - gint caps; + guint supported_caps; + guint caps; }; struct _IBusIMContextClass { @@ -111,11 +113,18 @@ 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); static void _set_cursor_location_internal (GtkIMContext *context); +static void _negotiate_capabilities (IBusIMContext *context); static void _bus_connected_cb (IBusBus *bus, IBusIMContext *context); @@ -129,14 +138,15 @@ 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 + IBusIMContext *context); +static gboolean _slave_delete_surrounding_cb (GtkIMContext *slave, - gint offset_from_cursor, - guint nchars, - IBusIMContext *context); + gint offset_from_cursor, + guint nchars, + IBusIMContext *context); +static void _request_surrounding_text (IBusIMContext *context); static void _create_fake_input_context (void); @@ -248,6 +258,17 @@ _process_key_event_done (GObject *object, } +static void +_request_surrounding_text (IBusIMContext *context) +{ + if (context->enable && + (context->caps & IBUS_CAP_SURROUNDING_TEXT)) { + gboolean return_value; + g_signal_emit (context, _signal_retrieve_surrounding_id, 0, + &return_value); + } +} + static gint _key_snooper_cb (GtkWidget *widget, GdkEventKey *event, @@ -334,6 +355,8 @@ _key_snooper_cb (GtkWidget *widget, } while (0); + _request_surrounding_text (ibusimcontext); + if (ibusimcontext != NULL) { ibusimcontext->time = event->time; } @@ -430,6 +453,7 @@ ibus_im_context_class_init (IBusIMContextClass *class) 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 = @@ -577,7 +601,12 @@ ibus_im_context_init (GObject *obj) ibusimcontext->ibuscontext = NULL; ibusimcontext->has_focus = FALSE; ibusimcontext->time = GDK_CURRENT_TIME; - ibusimcontext->caps = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS | IBUS_CAP_SURROUNDING_TEXT; +#ifdef ENABLE_SURROUNDING + ibusimcontext->supported_caps = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS | IBUS_CAP_SURROUNDING_TEXT; +#else + ibusimcontext->supported_caps = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS; +#endif + ibusimcontext->caps = ibusimcontext->supported_caps; // Create slave im context @@ -674,6 +703,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); + if (ibusimcontext != NULL) { ibusimcontext->time = event->time; } @@ -768,6 +799,8 @@ ibus_im_context_focus_in (GtkIMContext *context) (gpointer *) &_focus_im_context); _focus_im_context = context; } + + _request_surrounding_text (ibusimcontext); } static void @@ -925,6 +958,25 @@ ibus_im_context_set_cursor_location (GtkIMContext *context, GdkRectangle *area) } static void +_negotiate_capabilities (IBusIMContext *context) +{ + IBusEngineDesc *engine; + + engine = ibus_input_context_get_engine (context->ibuscontext); + if (engine) { + context->caps = context->supported_caps & + ibus_engine_desc_get_requires (engine); + ibus_input_context_set_capabilities (context->ibuscontext, + context->caps); + IDEBUG ("engine %s: supported caps = %u, engine wants = %u, caps = %u", + ibus_engine_desc_get_name (engine) + context->supported_caps, + ibus_engine_desc_get_requires (engine), + context->caps); + } +} + +static void ibus_im_context_set_use_preedit (GtkIMContext *context, gboolean use_preedit) { IDEBUG ("%s", __FUNCTION__); @@ -933,17 +985,50 @@ ibus_im_context_set_use_preedit (GtkIMContext *context, gboolean use_preedit) if(ibusimcontext->ibuscontext) { if (use_preedit) { - ibusimcontext->caps |= IBUS_CAP_PREEDIT_TEXT; + ibusimcontext->supported_caps |= IBUS_CAP_PREEDIT_TEXT; } else { - ibusimcontext->caps &= ~IBUS_CAP_PREEDIT_TEXT; + ibusimcontext->supported_caps &= ~IBUS_CAP_PREEDIT_TEXT; } - ibus_input_context_set_capabilities (ibusimcontext->ibuscontext, ibusimcontext->caps); + _negotiate_capabilities (ibusimcontext); } gtk_im_context_set_use_preedit (ibusimcontext->slave, 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) { @@ -962,6 +1047,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 @@ -1256,6 +1343,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 @@ -1321,6 +1410,14 @@ _ibus_context_destroy_cb (IBusInputContext *ibuscontext, } static void +_ibus_context_engine_changed_cb (IBusInputContext *ibuscontext, + IBusIMContext *ibusimcontext) +{ + IDEBUG ("%s", __FUNCTION__); + _negotiate_capabilities (ibusimcontext); +} + +static void _create_input_context (IBusIMContext *ibusimcontext) { IDEBUG ("%s", __FUNCTION__); @@ -1363,6 +1460,10 @@ _create_input_context (IBusIMContext *ibusimcontext) "disabled", G_CALLBACK (_ibus_context_disabled_cb), ibusimcontext); + g_signal_connect (ibusimcontext->ibuscontext, + "engine-changed", + G_CALLBACK (_ibus_context_engine_changed_cb), + ibusimcontext); g_signal_connect (ibusimcontext->ibuscontext, "destroy", G_CALLBACK (_ibus_context_destroy_cb), ibusimcontext); @@ -1419,17 +1520,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, @@ -1438,9 +1543,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/configure.ac b/configure.ac index 81efe81..e5fe925 100644 --- a/configure.ac +++ b/configure.ac @@ -364,6 +364,19 @@ AC_ARG_WITH(sync-mode-apps, AC_DEFINE_UNQUOTED(SYNC_MODE_APPS, "$SYNC_MODE_APPS", [Enbale sync mode in those applications]) +# option for enable surrounding-text +AC_ARG_ENABLE(surrounding-text, + AS_HELP_STRING([--enable-surrounding-text], + [Enable surrounding-text support]), + [enable_surrounding_text=$enableval], + [enable_surrounding_text=no] +) +if test x"$enable_surrounding_text" = x"yes"; then + AC_DEFINE(ENABLE_SURROUNDING, TRUE, [Enable surrounding-text support]) +else + enable_surrounding_text="no (disabled, use --enable-surrounding-text to enable)" +fi + # check iso-codes PKG_CHECK_MODULES(ISOCODES, [ iso-codes @@ -435,5 +448,6 @@ Build options: Enable key snooper $enable_key_snooper Sync mode regexes "$SYNC_MODE_APPS" No snooper regexes "$NO_SNOOPER_APPS" + Enable surrounding-text $enable_surrounding_text ]) 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 2386c0f..5db2012 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..bb25c5a 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 @@ -137,4 +140,6 @@ class IInputContext(dbus.service.Object): @signal(signature="v") def UpdateProperty(self, prop): pass + @signal() + def EngineChanged(self): pass diff --git a/src/ibusengine.c b/src/ibusengine.c index 519d7ca..fc129a2 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, }; @@ -58,10 +59,15 @@ enum { struct _IBusEnginePrivate { gchar *engine_name; GDBusConnection *connection; + + IBusText *surrounding_text; + guint surrounding_cursor_pos; }; 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, @@ -135,6 +141,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); static void ibus_engine_emit_signal (IBusEngine *engine, const gchar *signal_name, GVariant *parameters); @@ -185,6 +195,10 @@ static const gchar introspection_xml[] = " " " " " " + " " + " " + " " + " " /* FIXME signals */ " " " " @@ -250,6 +264,7 @@ ibus_engine_class_init (IBusEngineClass *class) class->property_hide = ibus_engine_property_hide; class->set_cursor_location = ibus_engine_set_cursor_location; class->set_capabilities = ibus_engine_set_capabilities; + class->set_surrounding_text = ibus_engine_set_surrounding_text; /* install properties */ /** @@ -616,12 +631,39 @@ ibus_engine_class_init (IBusEngineClass *class) G_TYPE_STRING); g_type_class_add_private (class, sizeof (IBusEnginePrivate)); + + /** + * 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. + * + * Argument @user_data is ignored in this function. + */ + 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 ibus_engine_init (IBusEngine *engine) { engine->priv = IBUS_ENGINE_GET_PRIVATE (engine); + + engine->priv->surrounding_text = g_object_ref_sink (text_empty); + engine->priv->surrounding_cursor_pos = 0; } static void @@ -630,6 +672,11 @@ ibus_engine_destroy (IBusEngine *engine) g_free (engine->priv->engine_name); engine->priv->engine_name = NULL; + if (engine->priv->surrounding_text) { + g_object_unref (engine->priv->surrounding_text); + engine->priv->surrounding_text = NULL; + } + IBUS_OBJECT_CLASS(ibus_engine_parent_class)->destroy (IBUS_OBJECT (engine)); } @@ -801,6 +848,25 @@ ibus_engine_service_method_call (IBusService *service, return; } + if (g_strcmp0 (method_name, "SetSurroundingText") == 0) { + GVariant *variant = NULL; + IBusText *text; + guint cursor_pos; + + g_variant_get (parameters, "(vu)", &variant, &cursor_pos); + text = IBUS_TEXT (ibus_serializable_deserialize (variant)); + g_variant_unref (variant); + + g_signal_emit (engine, engine_signals[SET_SURROUNDING_TEXT], 0, + text, + cursor_pos); + if (g_object_is_floating (text)) { + g_object_unref (text); + } + g_dbus_method_invocation_return_value (invocation, NULL); + return; + } + /* should not be reached */ g_return_if_reached (); } @@ -955,6 +1021,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 ibus_engine_emit_signal (IBusEngine *engine, const gchar *signal_name, GVariant *parameters) @@ -1138,14 +1224,66 @@ void ibus_engine_delete_surrounding_text (IBusEngine *engine, gint offset_from_cursor, guint nchars) { + IBusEnginePrivate *priv; + g_return_if_fail (IBUS_IS_ENGINE (engine)); + priv = IBUS_ENGINE_GET_PRIVATE (engine); + + /* Update surrounding-text cache. This is necessary since some + engines call ibus_engine_get_surrounding_text() immediately + after ibus_engine_delete_surrounding_text(). */ + if (priv->surrounding_text) { + IBusText *text; + glong cursor_pos, len; + + cursor_pos = priv->surrounding_cursor_pos + offset_from_cursor; + len = ibus_text_get_length (priv->surrounding_text); + if (cursor_pos >= 0 && len - cursor_pos >= nchars) { + gunichar *ucs; + + ucs = g_utf8_to_ucs4_fast (priv->surrounding_text->text, + -1, + NULL); + memmove (&ucs[cursor_pos], + &ucs[cursor_pos + nchars], + sizeof(gunichar) * (len - cursor_pos - nchars)); + ucs[len - nchars] = 0; + text = ibus_text_new_from_ucs4 (ucs); + g_free (ucs); + priv->surrounding_cursor_pos = cursor_pos; + } else { + text = text_empty; + priv->surrounding_cursor_pos = 0; + } + + g_object_unref (priv->surrounding_text); + priv->surrounding_text = g_object_ref_sink (text); + } + ibus_engine_emit_signal (engine, "DeleteSurroundingText", g_variant_new ("(iu)", offset_from_cursor, nchars)); } 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 46d0a04..a5f5aea 100644 --- a/src/ibusengine.h +++ b/src/ibusengine.h @@ -136,10 +136,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); @@ -394,6 +398,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/ibusenginedesc.c b/src/ibusenginedesc.c index ca5ef60..956ce73 100644 --- a/src/ibusenginedesc.c +++ b/src/ibusenginedesc.c @@ -22,6 +22,7 @@ #include #include "ibusenginedesc.h" #include "ibusxml.h" +#include "ibusenumtypes.h" enum { LAST_SIGNAL, @@ -39,6 +40,7 @@ enum { PROP_LAYOUT, PROP_RANK, PROP_HOTKEYS, + PROP_REQUIRES, }; @@ -54,6 +56,7 @@ struct _IBusEngineDescPrivate { gchar *layout; guint rank; gchar *hotkeys; + guint requires; }; #define IBUS_ENGINE_DESC_GET_PRIVATE(o) \ @@ -79,9 +82,20 @@ static gboolean ibus_engine_desc_copy (IBusEngineDesc *des const IBusEngineDesc *src); static gboolean ibus_engine_desc_parse_xml_node (IBusEngineDesc *desc, XMLNode *node); +static void ibus_engine_desc_output_capabilities + (guint caps, + GString *output, + gint indent); +static guint ibus_engine_desc_parse_capabilities + (XMLNode *node); G_DEFINE_TYPE (IBusEngineDesc, ibus_engine_desc, IBUS_TYPE_SERIALIZABLE) +#define DEFAULT_REQUIRES (IBUS_CAP_PREEDIT_TEXT | \ + IBUS_CAP_AUXILIARY_TEXT | \ + IBUS_CAP_LOOKUP_TABLE | \ + IBUS_CAP_FOCUS | \ + IBUS_CAP_PROPERTY) static void ibus_engine_desc_class_init (IBusEngineDescClass *class) @@ -232,6 +246,21 @@ ibus_engine_desc_class_init (IBusEngineDescClass *class) "The hotkeys of engine description", "", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + /** + * IBusEngineDesc:requires: + * + * The required capabilities of engine description + */ + g_object_class_install_property (gobject_class, + PROP_REQUIRES, + g_param_spec_uint ("requires", + "description requires", + "The required capabilities of engine description", + 0, + G_MAXUINT, + DEFAULT_REQUIRES, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void @@ -249,6 +278,7 @@ ibus_engine_desc_init (IBusEngineDesc *desc) desc->priv->layout = NULL; desc->priv->rank = 0; desc->priv->hotkeys = NULL; + desc->priv->requires = DEFAULT_REQUIRES; } static void @@ -313,6 +343,9 @@ ibus_engine_desc_set_property (IBusEngineDesc *desc, g_assert (desc->priv->hotkeys == NULL); desc->priv->hotkeys = g_value_dup_string (value); break; + case PROP_REQUIRES: + desc->priv->requires = g_value_get_uint (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (desc, prop_id, pspec); } @@ -355,6 +388,9 @@ ibus_engine_desc_get_property (IBusEngineDesc *desc, case PROP_HOTKEYS: g_value_set_string (value, ibus_engine_desc_get_hotkeys (desc)); break; + case PROP_REQUIRES: + g_value_set_uint (value, ibus_engine_desc_get_requires (desc)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (desc, prop_id, pspec); } @@ -382,9 +418,28 @@ ibus_engine_desc_serialize (IBusEngineDesc *desc, g_variant_builder_add (builder, "u", desc->priv->rank); g_variant_builder_add (builder, "s", NOTNULL (desc->priv->hotkeys)); #undef NOTNULL + + /* append extra properties */ + GVariantBuilder array; + g_variant_builder_init (&array, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&array, "{sv}", "requires", g_variant_new_uint32 (desc->priv->requires)); + g_variant_builder_add (builder, "v", g_variant_builder_end (&array)); + return TRUE; } +static gboolean +ibus_engine_desc_deserialize_property (IBusEngineDesc *desc, + const gchar *name, + GVariant *variant) +{ + if (g_strcmp0 (name, "requires") == 0) { + g_variant_get (variant, "u", &desc->priv->requires); + return TRUE; + } + return FALSE; +} + static gint ibus_engine_desc_deserialize (IBusEngineDesc *desc, GVariant *variant) @@ -405,6 +460,23 @@ ibus_engine_desc_deserialize (IBusEngineDesc *desc, g_variant_get_child (variant, retval++, "u", &desc->priv->rank); g_variant_get_child (variant, retval++, "s", &desc->priv->hotkeys); + /* extract extra properties */ + GVariantIter iter; + GVariant *child, *array; + + g_variant_get_child (variant, retval++, "v", &array); + g_variant_iter_init (&iter, array); + while ((child = g_variant_iter_next_value (&iter))) { + gchar *name; + GVariant *value; + g_variant_get (child, "{sv}", &name, &value); + if (ibus_engine_desc_deserialize_property (desc, name, value)) + retval++; + g_free (name); + g_variant_unref (value); + g_variant_unref (child); + } + return retval; } @@ -428,6 +500,7 @@ ibus_engine_desc_copy (IBusEngineDesc *dest, dest->priv->layout = g_strdup (src->priv->layout); dest->priv->rank = src->priv->rank; dest->priv->hotkeys = g_strdup (src->priv->hotkeys); + dest->priv->requires = src->priv->requires; return TRUE; } @@ -439,6 +512,52 @@ ibus_engine_desc_copy (IBusEngineDesc *dest, } \ } +static void +ibus_engine_desc_output_capabilities (guint caps, + GString *output, + gint indent) +{ + GFlagsClass *flags_class; + gint i; + + flags_class = g_type_class_ref (IBUS_TYPE_CAPABILITE); + g_return_if_fail (G_TYPE_IS_FLAGS (IBUS_TYPE_CAPABILITE)); + for (i = 0; i < flags_class->n_values; i++) { + GFlagsValue *flags_value = &flags_class->values[i]; + if (caps & flags_value->value) { + g_string_append_indent (output, indent + 1); + g_string_append_printf (output, "%s\n", + flags_value->value_nick); + } + } + g_type_class_unref (flags_class); +} + +static guint +ibus_engine_desc_parse_capabilities (XMLNode *node) +{ + GFlagsClass *flags_class; + guint caps = 0; + GList *p; + + flags_class = g_type_class_ref (IBUS_TYPE_CAPABILITE); + for (p = node->sub_nodes; p != NULL; p = p->next) { + XMLNode *sub_node = (XMLNode *) p->data; + + if (g_strcmp0 (sub_node->name, "capability") == 0) { + gint i; + for (i = 0; i < flags_class->n_values; i++) { + GFlagsValue *flags_value = &flags_class->values[i]; + if (g_strcmp0 (flags_value->value_nick, sub_node->text) == 0) + caps |= flags_value->value; + } + } + } + g_type_class_unref (flags_class); + + return caps; +} + void ibus_engine_desc_output (IBusEngineDesc *desc, GString *output, @@ -467,6 +586,13 @@ ibus_engine_desc_output (IBusEngineDesc *desc, OUTPUT_ENTRY_1(hotkeys); g_string_append_indent (output, indent + 1); g_string_append_printf (output, "%u\n", desc->priv->rank); + + g_string_append_indent (output, indent + 1); + g_string_append (output, "\n"); + ibus_engine_desc_output_capabilities (desc->priv->requires, output, + indent + 2); + g_string_append_indent (output, indent + 1); + g_string_append (output, "\n"); #undef OUTPUT_ENTRY #undef OUTPUT_ENTRY_1 g_string_append_indent (output, indent); @@ -504,6 +630,11 @@ ibus_engine_desc_parse_xml_node (IBusEngineDesc *desc, desc->priv->rank = atoi (sub_node->text); continue; } + if (g_strcmp0 (sub_node->name , "requires") == 0) { + desc->priv->requires = + ibus_engine_desc_parse_capabilities (sub_node); + continue; + } g_warning (" element contains invalidate element <%s>", sub_node->name); } return TRUE; @@ -526,6 +657,7 @@ IBUS_ENGINE_DESC_GET_PROPERTY (icon, const gchar *) IBUS_ENGINE_DESC_GET_PROPERTY (layout, const gchar *) IBUS_ENGINE_DESC_GET_PROPERTY (rank, guint) IBUS_ENGINE_DESC_GET_PROPERTY (hotkeys, const gchar *) +IBUS_ENGINE_DESC_GET_PROPERTY (requires, guint) #undef IBUS_ENGINE_DESC_GET_PROPERTY IBusEngineDesc * diff --git a/src/ibusenginedesc.h b/src/ibusenginedesc.h index 9718b15..209d460 100644 --- a/src/ibusenginedesc.h +++ b/src/ibusenginedesc.h @@ -90,6 +90,8 @@ typedef struct _IBusEngineDescClass IBusEngineDescClass; * the front. * hotkeys: One or more hotkeys for switching to this engine, separated by * semi-colon. + * requires: Capabilities this engine will utilize. The value is the + * union of the IBusCapability flags. */ struct _IBusEngineDesc { IBusSerializable parent; @@ -249,6 +251,15 @@ guint ibus_engine_desc_get_rank (IBusEngineDesc *info); const gchar *ibus_engine_desc_get_hotkeys (IBusEngineDesc *info); /** + * ibus_engine_desc_get_requires: + * @info: An IBusEngineDesc + * @returns: request capabilites property in IBusEngineDesc + * + * Return the capabilites property in IBusEngineDesc. + */ +guint ibus_engine_desc_get_requires (IBusEngineDesc *info); + +/** * ibus_engine_desc_output: * @info: An IBusEngineDesc * @output: XML-formatted Input method engine description. diff --git a/src/ibusinputcontext.c b/src/ibusinputcontext.c index f8f5848..9556df3 100644 --- a/src/ibusinputcontext.c +++ b/src/ibusinputcontext.c @@ -28,6 +28,9 @@ #include "ibuslookuptable.h" #include "ibusproplist.h" +#define IBUS_INPUT_CONTEXT_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_INPUT_CONTEXT, IBusInputContextPrivate)) + enum { ENABLED, DISABLED, @@ -49,12 +52,25 @@ enum { CURSOR_DOWN_LOOKUP_TABLE, REGISTER_PROPERTIES, UPDATE_PROPERTY, + ENGINE_CHANGED, LAST_SIGNAL, }; +/* BusInputContextPrivate */ +struct _IBusInputContextPrivate { + /* surrounding text */ + IBusText *surrounding_text; + guint surrounding_cursor_pos; +}; + +typedef struct _IBusInputContextPrivate IBusInputContextPrivate; + static guint context_signals[LAST_SIGNAL] = { 0 }; +static IBusText *text_empty = NULL; + /* functions prototype */ +static void ibus_input_context_real_destroy (IBusProxy *context); static void ibus_input_context_g_signal (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, @@ -65,8 +81,13 @@ G_DEFINE_TYPE (IBusInputContext, ibus_input_context, IBUS_TYPE_PROXY) static void ibus_input_context_class_init (IBusInputContextClass *class) { + IBusProxyClass *ibus_proxy_class = IBUS_PROXY_CLASS (class); GDBusProxyClass *g_dbus_proxy_class = G_DBUS_PROXY_CLASS (class); + g_type_class_add_private (class, sizeof (IBusInputContextPrivate)); + + ibus_proxy_class->destroy = ibus_input_context_real_destroy; + g_dbus_proxy_class->g_signal = ibus_input_context_g_signal; /* install signals */ @@ -101,6 +122,21 @@ ibus_input_context_class_init (IBusInputContextClass *class) G_TYPE_NONE, 0); /** + * IBusInputContext::engine-changed: + * @context: An IBusInputContext. + * + * Emitted when an IME is changed. + */ + context_signals[ENGINE_CHANGED] = + g_signal_new (I_("engine-changed"), + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _ibus_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** * IBusInputContext::commit-text: * @context: An IBusInputContext. * @text: Text to be committed. @@ -429,11 +465,33 @@ ibus_input_context_class_init (IBusInputContextClass *class) G_TYPE_NONE, 1, IBUS_TYPE_PROPERTY); + + text_empty = ibus_text_new_from_static_string (""); + g_object_ref_sink (text_empty); } static void ibus_input_context_init (IBusInputContext *context) { + IBusInputContextPrivate *priv; + + priv = IBUS_INPUT_CONTEXT_GET_PRIVATE (context); + priv->surrounding_text = g_object_ref_sink (text_empty); + priv->surrounding_cursor_pos = 0; +} + +static void +ibus_input_context_real_destroy (IBusProxy *context) +{ + IBusInputContextPrivate *priv; + priv = IBUS_INPUT_CONTEXT_GET_PRIVATE (IBUS_INPUT_CONTEXT (context)); + + if (priv->surrounding_text) { + g_object_unref (priv->surrounding_text); + priv->surrounding_text = NULL; + } + + IBUS_PROXY_CLASS(ibus_input_context_parent_class)->destroy (context); } static void @@ -463,6 +521,7 @@ ibus_input_context_g_signal (GDBusProxy *proxy, { "PageDownLookupTable", PAGE_DOWN_LOOKUP_TABLE }, { "CursorUpLookupTable", CURSOR_UP_LOOKUP_TABLE }, { "CursorDownLookupTable", CURSOR_DOWN_LOOKUP_TABLE }, + { "EngineChanged", ENGINE_CHANGED }, }; if (g_strcmp0 (signal_name, "CommitText") == 0) { @@ -848,6 +907,38 @@ ibus_input_context_property_hide (IBusInputContext *context, ); } +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) { + GVariant *variant = ibus_serializable_serialize ((IBusSerializable *)text); + 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; + + g_dbus_proxy_call ((GDBusProxy *) context, + "SetSurroundingText", /* method_name */ + g_variant_new ("(vu)", variant, cursor_pos), /* parameters */ + G_DBUS_CALL_FLAGS_NONE, /* flags */ + -1, /* timeout */ + NULL, /* cancellable */ + NULL, /* callback */ + NULL /* user_data */ + ); + } +} + gboolean ibus_input_context_is_enabled (IBusInputContext *context) { diff --git a/src/ibusinputcontext.h b/src/ibusinputcontext.h index 96fd00e..1f8ae86 100644 --- a/src/ibusinputcontext.h +++ b/src/ibusinputcontext.h @@ -41,6 +41,7 @@ #include "ibusproxy.h" #include "ibusenginedesc.h" +#include "ibustext.h" /* * Type macros. @@ -324,6 +325,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 5184278..5dc7fc2 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.3.2