diff options
-rw-r--r-- | bindings/vala/IBus-1.0-custom.vala | 7 | ||||
-rw-r--r-- | bindings/vala/Makefile.am | 3 | ||||
-rw-r--r-- | bindings/vala/gdk-wayland.vapi | 7 | ||||
-rw-r--r-- | bus/engineproxy.c | 53 | ||||
-rw-r--r-- | bus/engineproxy.h | 25 | ||||
-rw-r--r-- | bus/ibusimpl.c | 247 | ||||
-rw-r--r-- | bus/inputcontext.c | 399 | ||||
-rw-r--r-- | bus/inputcontext.h | 110 | ||||
-rw-r--r-- | bus/panelproxy.c | 210 | ||||
-rw-r--r-- | bus/panelproxy.h | 23 | ||||
-rw-r--r-- | data/ibus.schemas.in | 12 | ||||
-rw-r--r-- | setup/main.py | 10 | ||||
-rw-r--r-- | setup/setup.ui | 58 | ||||
-rw-r--r-- | src/ibusengine.c | 305 | ||||
-rw-r--r-- | src/ibuspanelservice.c | 318 | ||||
-rw-r--r-- | src/ibuspanelservice.h | 117 | ||||
-rw-r--r-- | src/ibusshare.h | 17 | ||||
-rw-r--r-- | src/ibusxevent.c | 375 | ||||
-rw-r--r-- | src/ibusxevent.h | 143 | ||||
-rw-r--r-- | ui/gtk3/Makefile.am | 3 | ||||
-rw-r--r-- | ui/gtk3/emojier.vala | 991 | ||||
-rw-r--r-- | ui/gtk3/emojierapp.vala | 74 | ||||
-rw-r--r-- | ui/gtk3/extension.vala | 6 | ||||
-rw-r--r-- | ui/gtk3/panel.vala | 23 | ||||
-rw-r--r-- | ui/gtk3/panelbinding.vala | 859 |
25 files changed, 3695 insertions, 700 deletions
diff --git a/bindings/vala/IBus-1.0-custom.vala b/bindings/vala/IBus-1.0-custom.vala index cf1fc3fa..7d34a8bd 100644 --- a/bindings/vala/IBus-1.0-custom.vala +++ b/bindings/vala/IBus-1.0-custom.vala @@ -6,8 +6,15 @@ namespace IBus { [CCode (cname = "ibus_text_new_from_static_string", has_construct_function = false)] public Text.from_static_string (string str); } + public class ExtensionEvent : IBus.Serializable { + [CCode (cname = "ibus_extension_event_new", has_construct_function = true)] + public ExtensionEvent (string first_property_name, ...); + } public class XEvent : IBus.Serializable { [CCode (cname = "ibus_x_event_new", has_construct_function = true)] public XEvent (string first_property_name, ...); } + public class PanelService : IBus.Service { + public void panel_extension_register_keys(string first_property_name, ...); + } } diff --git a/bindings/vala/Makefile.am b/bindings/vala/Makefile.am index fc8e2f01..e4ecab97 100644 --- a/bindings/vala/Makefile.am +++ b/bindings/vala/Makefile.am @@ -3,7 +3,7 @@ # ibus - The Input Bus # # Copyright (c) 2007-2016 Peng Huang <shawn.p.huang@gmail.com> -# Copyright (c) 2017 Takao Fujiwara <takao.fujiwara1@gmail.com> +# Copyright (c) 2017-2018 Takao Fujiwara <takao.fujiwara1@gmail.com> # Copyright (c) 2007-2017 Red Hat, Inc. # # This library is free software; you can redistribute it and/or @@ -86,6 +86,7 @@ EXTRA_DIST = \ ibus-1.0.deps \ ibus-emoji-dialog-1.0.deps \ config.vapi \ + gdk-wayland.vapi \ xi.vapi \ $(NULL) diff --git a/bindings/vala/gdk-wayland.vapi b/bindings/vala/gdk-wayland.vapi new file mode 100644 index 00000000..c65f2be4 --- /dev/null +++ b/bindings/vala/gdk-wayland.vapi @@ -0,0 +1,7 @@ +[CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "gdk/gdkwayland.h")] +namespace GdkWayland +{ + [CCode (type_id = "gdk_wayland_display_get_type ()")] + public class Display : Gdk.Display { + } +} diff --git a/bus/engineproxy.c b/bus/engineproxy.c index 175aec56..2d98995c 100644 --- a/bus/engineproxy.c +++ b/bus/engineproxy.c @@ -377,10 +377,10 @@ bus_engine_proxy_class_init (BusEngineProxyClass *class) G_SIGNAL_RUN_LAST, 0, NULL, NULL, - bus_marshal_VOID__VARIANT, + bus_marshal_VOID__OBJECT, G_TYPE_NONE, 1, - G_TYPE_VARIANT); + IBUS_TYPE_EXTENSION_EVENT); text_empty = ibus_text_new_from_static_string (""); g_object_ref_sink (text_empty); @@ -644,7 +644,16 @@ bus_engine_proxy_g_signal (GDBusProxy *proxy, } if (g_strcmp0 (signal_name, "PanelExtension") == 0) { - g_signal_emit (engine, engine_signals[PANEL_EXTENSION], 0, parameters); + GVariant *arg0 = NULL; + g_variant_get (parameters, "(v)", &arg0); + g_return_if_fail (arg0 != NULL); + + IBusExtensionEvent *event = IBUS_EXTENSION_EVENT ( + ibus_serializable_deserialize (arg0)); + g_variant_unref (arg0); + g_return_if_fail (event != NULL); + g_signal_emit (engine, engine_signals[PANEL_EXTENSION], 0, event); + _g_object_unref_if_floating (event); return; } @@ -1323,6 +1332,44 @@ bus_engine_proxy_is_enabled (BusEngineProxy *engine) return engine->enabled; } +void +bus_engine_proxy_panel_extension_received (BusEngineProxy *engine, + IBusExtensionEvent *event) +{ + GVariant *variant; + g_assert (BUS_IS_ENGINE_PROXY (engine)); + g_assert (IBUS_IS_EXTENSION_EVENT (event)); + + variant = ibus_serializable_serialize_object ( + IBUS_SERIALIZABLE (event)); + g_return_if_fail (variant != NULL); + g_dbus_proxy_call ((GDBusProxy *)engine, + "PanelExtensionReceived", + g_variant_new ("(v)", variant), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); +} + +void +bus_engine_proxy_panel_extension_register_keys (BusEngineProxy *engine, + GVariant *parameters) +{ + g_assert (BUS_IS_ENGINE_PROXY (engine)); + g_assert (parameters); + + g_dbus_proxy_call ((GDBusProxy *)engine, + "PanelExtensionRegisterKeys", + g_variant_new ("(v)", g_variant_ref (parameters)), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); +} + static gboolean initable_init (GInitable *initable, GCancellable *cancellable, diff --git a/bus/engineproxy.h b/bus/engineproxy.h index 528e61b7..a3006b47 100644 --- a/bus/engineproxy.h +++ b/bus/engineproxy.h @@ -2,7 +2,8 @@ /* vim:set et sts=4: */ /* ibus - The Input Bus * Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com> - * Copyright (C) 2008-2013 Red Hat, Inc. + * Copyright (C) 2018 Takao Fujiwara <takao.fujiwara@gmail.com> + * Copyright (C) 2008-2018 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -325,5 +326,27 @@ void bus_engine_proxy_set_content_type IBusPropList *bus_engine_proxy_get_properties (BusEngineProxy *engine); +/** + * bus_engine_proxy_panel_extension_received: + * @engine: A #BusEngineProxy. + * @event: An #IBusExtensionEvent. + * + * Send an #IBusExtensionEvent to the engine. + */ +void bus_engine_proxy_panel_extension_received + (BusEngineProxy *engine, + IBusExtensionEvent *event); + +/** + * bus_engine_proxy_panel_extension_register_keys: + * @engine: A #BusEngineProxy. + * @parameters: A #GVariant array which includes the name and shortcut keys. + * + * Send shortcut keys to the engine to enable the extension. + */ +void bus_engine_proxy_panel_extension_register_keys + (BusEngineProxy *engine, + GVariant *parameters); + G_END_DECLS #endif diff --git a/bus/ibusimpl.c b/bus/ibusimpl.c index a4ce3d9d..ec1caea8 100644 --- a/bus/ibusimpl.c +++ b/bus/ibusimpl.c @@ -74,7 +74,8 @@ struct _BusIBusImpl { BusInputContext *focused_context; BusPanelProxy *panel; - BusPanelProxy *extension; + BusPanelProxy *emoji_extension; + gboolean enable_emoji_extension; /* a default keymap of ibus-daemon (usually "us") which is used only * when use_sys_layout is FALSE. */ @@ -83,6 +84,7 @@ struct _BusIBusImpl { gboolean use_global_engine; gchar *global_engine_name; gchar *global_previous_engine_name; + GVariant *extension_register_keys; }; struct _BusIBusImplClass { @@ -294,40 +296,158 @@ _panel_destroy_cb (BusPanelProxy *panel, if (ibus->panel == panel) ibus->panel = NULL; - else if (ibus->extension == panel) - ibus->extension = NULL; + else if (ibus->emoji_extension == panel) + ibus->emoji_extension = NULL; else g_return_if_reached (); g_object_unref (panel); } static void -bus_ibus_impl_panel_extension_received (BusIBusImpl *ibus, - GVariant *parameters) +bus_ibus_impl_set_panel_extension_mode (BusIBusImpl *ibus, + IBusExtensionEvent *event) { - if (!ibus->extension) { + gboolean is_extension = FALSE; + g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); + g_return_if_fail (IBUS_IS_EXTENSION_EVENT (event)); + + if (!ibus->emoji_extension) { g_warning ("Panel extension is not running."); return; } - g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); - g_return_if_fail (BUS_IS_PANEL_PROXY (ibus->extension)); + g_return_if_fail (BUS_IS_PANEL_PROXY (ibus->emoji_extension)); + + ibus->enable_emoji_extension = ibus_extension_event_is_enabled (event); + is_extension = ibus_extension_event_is_extension (event); + if (ibus->focused_context != NULL) { + if (ibus->enable_emoji_extension) { + bus_input_context_set_emoji_extension (ibus->focused_context, + ibus->emoji_extension); + } else { + bus_input_context_set_emoji_extension (ibus->focused_context, NULL); + } + if (is_extension) + bus_input_context_panel_extension_received (ibus->focused_context, + event); + } + if (is_extension) + return; /* Use the DBus method because it seems any DBus signal, * g_dbus_message_new_signal(), cannot be reached to the server. */ - g_dbus_proxy_call (G_DBUS_PROXY (ibus->extension), - "PanelExtensionReceived", - parameters, - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, NULL, NULL); + bus_panel_proxy_panel_extension_received (ibus->emoji_extension, + event); +} + +static void +bus_ibus_impl_set_panel_extension_keys (BusIBusImpl *ibus, + GVariant *parameters) +{ + BusEngineProxy *engine = NULL; + + g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); + g_return_if_fail (parameters); + + if (!ibus->emoji_extension) { + g_warning ("Panel extension is not running."); + return; + } + + if (ibus->extension_register_keys) + g_variant_unref (ibus->extension_register_keys); + ibus->extension_register_keys = g_variant_ref_sink (parameters); + if (ibus->focused_context != NULL) { + engine = bus_input_context_get_engine (ibus->focused_context); + } + if (!engine) + return; + bus_engine_proxy_panel_extension_register_keys (engine, parameters); } static void -_panel_panel_extension_cb (BusPanelProxy *panel, - GVariant *parameters, - BusIBusImpl *ibus) +_panel_panel_extension_cb (BusPanelProxy *panel, + IBusExtensionEvent *event, + BusIBusImpl *ibus) { - bus_ibus_impl_panel_extension_received (ibus, parameters); + bus_ibus_impl_set_panel_extension_mode (ibus, event); +} + +static void +_panel_panel_extension_register_keys_cb (BusInputContext *context, + GVariant *parameters, + BusIBusImpl *ibus) +{ + bus_ibus_impl_set_panel_extension_keys (ibus, parameters); +} + +static void +_panel_update_preedit_text_received_cb (BusPanelProxy *panel, + IBusText *text, + guint cursor_pos, + gboolean visible, + BusIBusImpl *ibus) +{ + g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); + + if (!ibus->focused_context) + return; + bus_input_context_update_preedit_text (ibus->focused_context, + text, cursor_pos, visible, IBUS_ENGINE_PREEDIT_CLEAR, FALSE); +} + +static void +_panel_update_lookup_table_received_cb (BusPanelProxy *panel, + IBusLookupTable *table, + gboolean visible, + BusIBusImpl *ibus) +{ + g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); + g_return_if_fail (IBUS_IS_LOOKUP_TABLE (table)); + + if (!ibus->focused_context) + return; + /* Call bus_input_context_update_lookup_table() instead of + * bus_panel_proxy_update_lookup_table() for panel extensions because + * bus_input_context_page_up() can call bus_panel_proxy_page_up_received(). + */ + bus_input_context_update_lookup_table ( + ibus->focused_context, table, visible, TRUE); +} + +static void +_panel_update_auxiliary_text_received_cb (BusPanelProxy *panel, + IBusText *text, + gboolean visible, + BusIBusImpl *ibus) +{ + g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); + g_return_if_fail (IBUS_IS_TEXT (text)); + + if (!ibus->panel) + return; + bus_panel_proxy_update_auxiliary_text ( + ibus->panel, text, visible); +} + +static void +_panel_show_lookup_table_received_cb (BusPanelProxy *panel, + BusIBusImpl *ibus) +{ + g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); + + if (ibus->panel) + bus_panel_proxy_show_lookup_table (ibus->panel); +} + +static void +_panel_hide_lookup_table_received_cb (BusPanelProxy *panel, + BusIBusImpl *ibus) +{ + g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); + + if (ibus->panel) + bus_panel_proxy_hide_lookup_table (ibus->panel); } static void @@ -361,8 +481,8 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus, if (!g_strcmp0 (name, IBUS_SERVICE_PANEL)) panel_type = PANEL_TYPE_PANEL; - else if (!g_strcmp0 (name, IBUS_SERVICE_PANEL_EXTENSION)) - panel_type = PANEL_TYPE_EXTENSION; + else if (!g_strcmp0 (name, IBUS_SERVICE_PANEL_EXTENSION_EMOJI)) + panel_type = PANEL_TYPE_EXTENSION_EMOJI; if (panel_type != PANEL_TYPE_NONE) { if (g_strcmp0 (new_name, "") != 0) { @@ -370,7 +490,7 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus, BusConnection *connection; BusInputContext *context = NULL; BusPanelProxy **panel = (panel_type == PANEL_TYPE_PANEL) ? - &ibus->panel : &ibus->extension; + &ibus->panel : &ibus->emoji_extension; if (*panel != NULL) { ibus_proxy_destroy ((IBusProxy *)(*panel)); @@ -383,6 +503,8 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus, g_return_if_fail (connection != NULL); *panel = bus_panel_proxy_new (connection, panel_type); + if (panel_type == PANEL_TYPE_EXTENSION_EMOJI) + ibus->enable_emoji_extension = FALSE; g_signal_connect (*panel, "destroy", @@ -392,6 +514,26 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus, "panel-extension", G_CALLBACK (_panel_panel_extension_cb), ibus); + g_signal_connect (*panel, + "panel-extension-register-keys", + G_CALLBACK ( + _panel_panel_extension_register_keys_cb), + ibus); + g_signal_connect ( + *panel, + "update-preedit-text-received", + G_CALLBACK (_panel_update_preedit_text_received_cb), + ibus); + g_signal_connect ( + *panel, + "update-lookup-table-received", + G_CALLBACK (_panel_update_lookup_table_received_cb), + ibus); + g_signal_connect ( + *panel, + "update-auxiliary-text-received", + G_CALLBACK (_panel_update_auxiliary_text_received_cb), + ibus); if (ibus->focused_context != NULL) { context = ibus->focused_context; @@ -450,7 +592,7 @@ bus_ibus_impl_init (BusIBusImpl *ibus) ibus->contexts = NULL; ibus->focused_context = NULL; ibus->panel = NULL; - ibus->extension = NULL; + ibus->emoji_extension = NULL; ibus->keymap = ibus_keymap_get ("us"); @@ -650,11 +792,11 @@ bus_ibus_impl_set_context_engine_from_desc (BusIBusImpl *ibus, } static void -_context_panel_extension_cb (BusInputContext *context, - GVariant *parameters, - BusIBusImpl *ibus) +_context_panel_extension_cb (BusInputContext *context, + IBusExtensionEvent *event, + BusIBusImpl *ibus) { - bus_ibus_impl_panel_extension_received (ibus, parameters); + bus_ibus_impl_set_panel_extension_mode (ibus, event); } const static struct { @@ -694,13 +836,18 @@ bus_ibus_impl_set_focused_context (BusIBusImpl *ibus, if (engine) { g_object_ref (engine); bus_input_context_set_engine (ibus->focused_context, NULL); + bus_input_context_set_emoji_extension (ibus->focused_context, + NULL); } } if (ibus->panel != NULL) bus_panel_proxy_focus_out (ibus->panel, ibus->focused_context); - if (ibus->extension != NULL) - bus_panel_proxy_focus_out (ibus->extension, ibus->focused_context); + if (ibus->emoji_extension != NULL) { + bus_panel_proxy_focus_out (ibus->emoji_extension, + ibus->focused_context); + } + bus_input_context_set_emoji_extension (ibus->focused_context, NULL); bus_input_context_get_content_type (ibus->focused_context, &purpose, &hints); @@ -724,6 +871,12 @@ bus_ibus_impl_set_focused_context (BusIBusImpl *ibus, if (engine != NULL) { bus_input_context_set_engine (context, engine); bus_input_context_enable (context); + if (ibus->enable_emoji_extension) { + bus_input_context_set_emoji_extension (context, + ibus->emoji_extension); + } else { + bus_input_context_set_emoji_extension (context, NULL); + } } for (i = 0; i < G_N_ELEMENTS(context_signals); i++) { g_signal_connect (ibus->focused_context, @@ -734,8 +887,8 @@ bus_ibus_impl_set_focused_context (BusIBusImpl *ibus, if (ibus->panel != NULL) bus_panel_proxy_focus_in (ibus->panel, context); - if (ibus->extension != NULL) - bus_panel_proxy_focus_in (ibus->extension, context); + if (ibus->emoji_extension != NULL) + bus_panel_proxy_focus_in (ibus->emoji_extension, context); } if (engine != NULL) @@ -751,6 +904,12 @@ bus_ibus_impl_set_global_engine (BusIBusImpl *ibus, if (ibus->focused_context) { bus_input_context_set_engine (ibus->focused_context, engine); + if (ibus->enable_emoji_extension) { + bus_input_context_set_emoji_extension (ibus->focused_context, + ibus->emoji_extension); + } else { + bus_input_context_set_emoji_extension (ibus->focused_context, NULL); + } } else if (ibus->fake_context) { bus_input_context_set_engine (ibus->fake_context, engine); } @@ -927,9 +1086,9 @@ _context_destroy_cb (BusInputContext *context, bus_input_context_get_capabilities (context) & IBUS_CAP_FOCUS) { bus_panel_proxy_destroy_context (ibus->panel, context); } - if (ibus->extension && + if (ibus->emoji_extension && bus_input_context_get_capabilities (context) & IBUS_CAP_FOCUS) { - bus_panel_proxy_destroy_context (ibus->extension, context); + bus_panel_proxy_destroy_context (ibus->emoji_extension, context); } ibus->contexts = g_list_remove (ibus->contexts, context); @@ -1489,6 +1648,7 @@ _ibus_set_global_engine_ready_cb (BusInputContext *context, else { g_dbus_method_invocation_return_value (data->invocation, NULL); + BusEngineProxy *engine = bus_input_context_get_engine (context); if (ibus->use_global_engine && (context != ibus->focused_context)) { /* context and ibus->focused_context don't match. This means that * the focus is moved before _ibus_set_global_engine() asynchronous @@ -1496,14 +1656,28 @@ _ibus_set_global_engine_ready_cb (BusInputContext *context, * being focused hasn't been updated. Update the engine here so that * subsequent _ibus_get_global_engine() call could return a * consistent engine name. */ - BusEngineProxy *engine = bus_input_context_get_engine (context); if (engine && ibus->focused_context != NULL) { g_object_ref (engine); bus_input_context_set_engine (context, NULL); + bus_input_context_set_emoji_extension (context, NULL); bus_input_context_set_engine (ibus->focused_context, engine); + if (ibus->enable_emoji_extension) { + bus_input_context_set_emoji_extension ( + ibus->focused_context, + ibus->emoji_extension); + } else { + bus_input_context_set_emoji_extension ( + ibus->focused_context, + NULL); + } g_object_unref (engine); } } + if (engine && ibus->extension_register_keys) { + bus_engine_proxy_panel_extension_register_keys ( + engine, + ibus->extension_register_keys); + } } g_object_unref (ibus); @@ -2013,11 +2187,12 @@ bus_ibus_impl_registry_destroy (BusIBusImpl *ibus) g_list_free_full (ibus->components, g_object_unref); ibus->components = NULL; - g_hash_table_destroy (ibus->engine_table); - ibus->engine_table = NULL; + g_clear_pointer (&ibus->engine_table, g_hash_table_destroy); - ibus_object_destroy (IBUS_OBJECT (ibus->registry)); - ibus->registry = NULL; + g_clear_pointer (&ibus->registry, ibus_object_destroy); + + if (ibus->extension_register_keys) + g_clear_pointer (&ibus->extension_register_keys, g_variant_unref); } static gint diff --git a/bus/inputcontext.c b/bus/inputcontext.c index dfb98c36..bf9eafcf 100644 --- a/bus/inputcontext.c +++ b/bus/inputcontext.c @@ -94,6 +94,9 @@ struct _BusInputContext { /* content-type (primary purpose and hints) */ guint purpose; guint hints; + + BusPanelProxy *emoji_extension; + gboolean is_extension_lookup_table; }; struct _BusInputContextClass { @@ -162,16 +165,12 @@ static gboolean bus_input_context_service_set_property GError **error); static void bus_input_context_unset_engine (BusInputContext *context); -static void bus_input_context_update_preedit_text - (BusInputContext *context, - IBusText *text, - guint cursor_pos, - gboolean visible, - guint mode); static void bus_input_context_show_preedit_text - (BusInputContext *context); + (BusInputContext *context, + gboolean is_extension); static void bus_input_context_hide_preedit_text - (BusInputContext *context); + (BusInputContext *context, + gboolean is_extension); static void bus_input_context_update_auxiliary_text (BusInputContext *context, IBusText *text, @@ -180,10 +179,6 @@ static void bus_input_context_show_auxiliary_text (BusInputContext *context); static void bus_input_context_hide_auxiliary_text (BusInputContext *context); -static void bus_input_context_update_lookup_table - (BusInputContext *context, - IBusLookupTable *table, - gboolean visible); static void bus_input_context_show_lookup_table (BusInputContext *context); static void bus_input_context_hide_lookup_table @@ -605,10 +600,10 @@ bus_input_context_class_init (BusInputContextClass *class) G_SIGNAL_RUN_LAST, 0, NULL, NULL, - bus_marshal_VOID__VARIANT, + bus_marshal_VOID__OBJECT, G_TYPE_NONE, 1, - G_TYPE_VARIANT); + IBUS_TYPE_EXTENSION_EVENT); text_empty = ibus_text_new_from_string (""); g_object_ref_sink (text_empty); @@ -760,28 +755,85 @@ bus_input_context_property_changed (BusInputContext *context, error); } + +/** + * _panel_process_key_event_cb: + * + * A GAsyncReadyCallback function to be called when + * bus_panel_proxy_process_key_event() is finished. + */ +static void +_panel_process_key_event_cb (GObject *source, + GAsyncResult *res, + GDBusMethodInvocation *invocation) +{ + GError *error = NULL; + GVariant *value = g_dbus_proxy_call_finish ((GDBusProxy *)source, + res, + &error); + if (value != NULL) { + g_dbus_method_invocation_return_value (invocation, value); + g_variant_unref (value); + } + else { + g_dbus_method_invocation_return_gerror (invocation, error); + g_error_free (error); + } +} + +typedef struct _ProcessKeyEventData ProcessKeyEventData; +struct _ProcessKeyEventData { + GDBusMethodInvocation *invocation; + BusInputContext *context; + guint keyval; + guint keycode; + guint modifiers; +}; + /** * _ic_process_key_event_reply_cb: * - * A GAsyncReadyCallback function to be called when bus_engine_proxy_process_key_event() is finished. + * A GAsyncReadyCallback function to be called when + * bus_engine_proxy_process_key_event() is finished. */ static void _ic_process_key_event_reply_cb (GObject *source, GAsyncResult *res, - GDBusMethodInvocation *invocation) + ProcessKeyEventData *data) { + GDBusMethodInvocation *invocation = data->invocation; + BusInputContext *context = data->context; + guint keyval = data->keyval; + guint keycode = data->keycode; + guint modifiers = data->modifiers; GError *error = NULL; GVariant *value = g_dbus_proxy_call_finish ((GDBusProxy *)source, res, &error); + if (value != NULL) { - g_dbus_method_invocation_return_value (invocation, value); + gboolean retval = FALSE; + g_variant_get (value, "(b)", &retval); + if (context->emoji_extension && !retval) { + bus_panel_proxy_process_key_event (context->emoji_extension, + keyval, + keycode, + modifiers, + (GAsyncReadyCallback) + _panel_process_key_event_cb, + invocation); + } else { + g_dbus_method_invocation_return_value (invocation, value); + } g_variant_unref (value); } else { g_dbus_method_invocation_return_gerror (invocation, error); g_error_free (error); } + + g_object_unref (context); + g_slice_free (ProcessKeyEventData, data); } /** @@ -840,12 +892,19 @@ _ic_process_key_event (BusInputContext *context, /* ignore key events, if it is a fake input context */ if (context->has_focus && context->engine && context->fake == FALSE) { + ProcessKeyEventData *data = g_slice_new0 (ProcessKeyEventData); + data->invocation = invocation; + data->context = g_object_ref (context); + data->keyval = keyval; + data->keycode = keycode; + data->modifiers = modifiers; bus_engine_proxy_process_key_event (context->engine, keyval, keycode, modifiers, - (GAsyncReadyCallback) _ic_process_key_event_reply_cb, - invocation); + (GAsyncReadyCallback) + _ic_process_key_event_reply_cb, + data); } else { g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE)); @@ -880,6 +939,13 @@ _ic_set_cursor_location (BusInputContext *context, context->y, context->w, context->h); + if (context->emoji_extension) { + bus_panel_proxy_set_cursor_location (context->emoji_extension, + context->x, + context->y, + context->w, + context->h); + } } } @@ -912,6 +978,14 @@ _ic_set_cursor_location_relative (BusInputContext *context, y, w, h); + if (context->emoji_extension) { + bus_panel_proxy_set_cursor_location_relative ( + context->emoji_extension, + x, + y, + w, + h); + } } } @@ -1394,7 +1468,7 @@ bus_input_context_clear_preedit_text (BusInputContext *context) /* always clear preedit text */ bus_input_context_update_preedit_text (context, - text_empty, 0, FALSE, IBUS_ENGINE_PREEDIT_CLEAR); + text_empty, 0, FALSE, IBUS_ENGINE_PREEDIT_CLEAR, TRUE); } void @@ -1407,7 +1481,10 @@ bus_input_context_focus_out (BusInputContext *context) bus_input_context_clear_preedit_text (context); bus_input_context_update_auxiliary_text (context, text_empty, FALSE); - bus_input_context_update_lookup_table (context, lookup_table_empty, FALSE); + bus_input_context_update_lookup_table (context, + lookup_table_empty, + FALSE, + FALSE); bus_input_context_register_properties (context, props_empty); if (context->engine) { @@ -1427,7 +1504,12 @@ bus_input_context_focus_out (BusInputContext *context) { \ g_assert (BUS_IS_INPUT_CONTEXT (context)); \ \ - if (context->has_focus && context->engine) { \ + if (context->is_extension_lookup_table && \ + context->emoji_extension) { \ + bus_panel_proxy_##name##_lookup_table (context->emoji_extension); \ + return; \ + } \ + if (context->has_focus && context->engine) { \ bus_engine_proxy_##name (context->engine); \ } \ } @@ -1447,6 +1529,14 @@ bus_input_context_candidate_clicked (BusInputContext *context, { g_assert (BUS_IS_INPUT_CONTEXT (context)); + if (context->is_extension_lookup_table && context->emoji_extension) { + bus_panel_proxy_candidate_clicked_lookup_table ( + context->emoji_extension, + index, + button, + state); + return; + } if (context->engine) { bus_engine_proxy_candidate_clicked (context->engine, index, @@ -1468,60 +1558,32 @@ bus_input_context_property_activate (BusInputContext *context, } /** - * bus_input_context_update_preedit_text: - * - * Update a preedit text. Send D-Bus signal to update status of client or send glib signal to the panel, depending on capabilities of the client. - */ -static void -bus_input_context_update_preedit_text (BusInputContext *context, - IBusText *text, - guint cursor_pos, - gboolean visible, - guint mode) -{ - g_assert (BUS_IS_INPUT_CONTEXT (context)); - - if (context->preedit_text) { - g_object_unref (context->preedit_text); - } - - context->preedit_text = (IBusText *) g_object_ref_sink (text ? text : text_empty); - context->preedit_cursor_pos = cursor_pos; - context->preedit_visible = visible; - context->preedit_mode = mode; - - if (PREEDIT_CONDITION) { - GVariant *variant = ibus_serializable_serialize ((IBusSerializable *)context->preedit_text); - bus_input_context_emit_signal (context, - "UpdatePreeditText", - g_variant_new ("(vub)", variant, context->preedit_cursor_pos, context->preedit_visible), - NULL); - } - else { - g_signal_emit (context, - context_signals[UPDATE_PREEDIT_TEXT], - 0, - context->preedit_text, - context->preedit_cursor_pos, - context->preedit_visible); - } -} - -/** * bus_input_context_show_preedit_text: * * Show a preedit text. Send D-Bus signal to update status of client or send glib signal to the panel, depending on capabilities of the client. */ static void -bus_input_context_show_preedit_text (BusInputContext *context) +bus_input_context_show_preedit_text (BusInputContext *context, + gboolean is_extension) { g_assert (BUS_IS_INPUT_CONTEXT (context)); - if (context->preedit_visible) { + if (context->preedit_visible) return; - } + if (!is_extension && context->emoji_extension) + return; + + if (!is_extension) + context->preedit_visible = TRUE; - context->preedit_visible = TRUE; + if (context->emoji_extension && !is_extension) { + /* Do not use HIDE_PREEDIT_TEXT signal below but call + * bus_panel_proxy_hide_preedit_text() directly for the extension only + * but not for the normal panel. + */ + bus_panel_proxy_show_preedit_text (context->emoji_extension); + return; + } if (PREEDIT_CONDITION) { bus_input_context_emit_signal (context, @@ -1542,15 +1604,25 @@ bus_input_context_show_preedit_text (BusInputContext *context) * Hide a preedit text. Send D-Bus signal to update status of client or send glib signal to the panel, depending on capabilities of the client. */ static void -bus_input_context_hide_preedit_text (BusInputContext *context) +bus_input_context_hide_preedit_text (BusInputContext *context, + gboolean is_extension) { g_assert (BUS_IS_INPUT_CONTEXT (context)); - if (!context->preedit_visible) { + if (!is_extension && !context->preedit_visible) return; - } - context->preedit_visible = FALSE; + if (!is_extension) + context->preedit_visible = FALSE; + + if (context->emoji_extension && !is_extension) { + /* Do not use HIDE_PREEDIT_TEXT signal below but call + * bus_panel_proxy_hide_preedit_text() directly for the extension only + * but not for the normal panel. + */ + bus_panel_proxy_hide_preedit_text (context->emoji_extension); + return; + } if (PREEDIT_CONDITION) { bus_input_context_emit_signal (context, @@ -1658,19 +1730,15 @@ bus_input_context_hide_auxiliary_text (BusInputContext *context) } } -/** - * bus_input_context_update_lookup_table: - * - * Update contents in the lookup table. - * Send D-Bus signal to update status of client or send glib signal to the panel, depending on capabilities of the client. - */ -static void +void bus_input_context_update_lookup_table (BusInputContext *context, IBusLookupTable *table, - gboolean visible) + gboolean visible, + gboolean is_extension) { g_assert (BUS_IS_INPUT_CONTEXT (context)); + context->is_extension_lookup_table = is_extension; if (context->lookup_table) { g_object_unref (context->lookup_table); } @@ -2035,7 +2103,9 @@ _engine_update_preedit_text_cb (BusEngineProxy *engine, g_assert (context->engine == engine); - bus_input_context_update_preedit_text (context, text, cursor_pos, visible, mode); + bus_input_context_update_preedit_text (context, text, + cursor_pos, visible, mode, + TRUE); } /** @@ -2075,7 +2145,7 @@ _engine_update_lookup_table_cb (BusEngineProxy *engine, g_assert (context->engine == engine); - bus_input_context_update_lookup_table (context, table, visible); + bus_input_context_update_lookup_table (context, table, visible, FALSE); } /** @@ -2123,11 +2193,35 @@ _engine_update_property_cb (BusEngineProxy *engine, * from the engine object. */ static void -_engine_panel_extension_cb (BusEngineProxy *engine, - GVariant *parameters, - BusInputContext *context) +_engine_panel_extension_cb (BusEngineProxy *engine, + IBusExtensionEvent *event, + BusInputContext *context) { - g_signal_emit (context, context_signals[PANEL_EXTENSION], 0, parameters); + g_signal_emit (context, context_signals[PANEL_EXTENSION], 0, event); +} + +static void +_engine_show_preedit_text_cb (BusEngineProxy *engine, + BusInputContext *context) +{ + g_assert (BUS_IS_ENGINE_PROXY (engine)); + g_assert (BUS_IS_INPUT_CONTEXT (context)); + + g_assert (context->engine == engine); + + bus_input_context_show_preedit_text (context, FALSE); +} + +static void +_engine_hide_preedit_text_cb (BusEngineProxy *engine, + BusInputContext *context) +{ + g_assert (BUS_IS_ENGINE_PROXY (engine)); + g_assert (BUS_IS_INPUT_CONTEXT (context)); + + g_assert (context->engine == engine); + + bus_input_context_hide_preedit_text (context, FALSE); } #define DEFINE_FUNCTION(name) \ @@ -2143,8 +2237,6 @@ _engine_panel_extension_cb (BusEngineProxy *engine, bus_input_context_##name (context); \ } -DEFINE_FUNCTION (show_preedit_text) -DEFINE_FUNCTION (hide_preedit_text) DEFINE_FUNCTION (show_auxiliary_text) DEFINE_FUNCTION (hide_auxiliary_text) DEFINE_FUNCTION (show_lookup_table) @@ -2239,7 +2331,10 @@ bus_input_context_disable (BusInputContext *context) bus_input_context_clear_preedit_text (context); bus_input_context_update_auxiliary_text (context, text_empty, FALSE); - bus_input_context_update_lookup_table (context, lookup_table_empty, FALSE); + bus_input_context_update_lookup_table (context, + lookup_table_empty, + FALSE, + FALSE); bus_input_context_register_properties (context, props_empty); if (context->engine) { @@ -2283,7 +2378,10 @@ bus_input_context_unset_engine (BusInputContext *context) bus_input_context_clear_preedit_text (context); bus_input_context_update_auxiliary_text (context, text_empty, FALSE); - bus_input_context_update_lookup_table (context, lookup_table_empty, FALSE); + bus_input_context_update_lookup_table (context, + lookup_table_empty, + FALSE, + FALSE); bus_input_context_register_properties (context, props_empty); if (context->engine) { @@ -2639,17 +2737,128 @@ bus_input_context_set_content_type (BusInputContext *context, } void -bus_input_context_commit_text (BusInputContext *context, - IBusText *text) +bus_input_context_commit_text_use_extension (BusInputContext *context, + IBusText *text, + gboolean use_extension) { g_assert (BUS_IS_INPUT_CONTEXT (context)); if (text == text_empty || text == NULL) return; - GVariant *variant = ibus_serializable_serialize ((IBusSerializable *)text); - bus_input_context_emit_signal (context, - "CommitText", - g_variant_new ("(v)", variant), - NULL); + if (use_extension && context->emoji_extension) { + bus_panel_proxy_commit_text_received (context->emoji_extension, text); + } else { + GVariant *variant = ibus_serializable_serialize ( + (IBusSerializable *)text); + bus_input_context_emit_signal (context, + "CommitText", + g_variant_new ("(v)", variant), + NULL); + } +} + +void +bus_input_context_commit_text (BusInputContext *context, + IBusText *text) +{ + bus_input_context_commit_text_use_extension (context, text, TRUE); +} + +void +bus_input_context_update_preedit_text (BusInputContext *context, + IBusText *text, + guint cursor_pos, + gboolean visible, + guint mode, + gboolean use_extension) +{ + gboolean extension_visible = FALSE; + g_assert (BUS_IS_INPUT_CONTEXT (context)); + + if (context->preedit_text) { + g_object_unref (context->preedit_text); + } + + context->preedit_text = (IBusText *) g_object_ref_sink (text ? text : + text_empty); + context->preedit_cursor_pos = cursor_pos; + if (use_extension) + context->preedit_visible = visible; + if (use_extension) + context->preedit_mode = mode; + extension_visible = context->preedit_visible | + (context->emoji_extension != NULL); + + if (use_extension && context->emoji_extension) { + bus_panel_proxy_update_preedit_text (context->emoji_extension, + context->preedit_text, + context->preedit_cursor_pos, + context->preedit_visible); + } else if (PREEDIT_CONDITION) { + GVariant *variant = ibus_serializable_serialize ( + (IBusSerializable *)context->preedit_text); + bus_input_context_emit_signal (context, + "UpdatePreeditText", + g_variant_new ( + "(vub)", + variant, + context->preedit_cursor_pos, + extension_visible), + NULL); + } else { + g_signal_emit (context, + context_signals[UPDATE_PREEDIT_TEXT], + 0, + context->preedit_text, + context->preedit_cursor_pos, + extension_visible); + } +} + +void +bus_input_context_set_emoji_extension (BusInputContext *context, + BusPanelProxy *emoji_extension) +{ + g_assert (BUS_IS_INPUT_CONTEXT (context)); + + if (context->emoji_extension) + g_object_unref (context->emoji_extension); + context->emoji_extension = emoji_extension; + if (emoji_extension) { + g_object_ref (context->emoji_extension); + if (!context->connection) + return; + bus_input_context_show_preedit_text (context, TRUE); + bus_panel_proxy_set_cursor_location (context->emoji_extension, + context->x, + context->y, + context->w, + context->h); + } else { + if (!context->connection) + return; + /* https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/113 + * Cannot use bus_input_context_hide_preedit_text () yet. + */ + if (!context->preedit_visible) { + bus_input_context_update_preedit_text (context, + text_empty, + 0, + FALSE, + IBUS_ENGINE_PREEDIT_CLEAR, + FALSE); + } + } +} + +void +bus_input_context_panel_extension_received (BusInputContext *context, + IBusExtensionEvent *event) +{ + g_assert (BUS_IS_INPUT_CONTEXT (context)); + + if (!context->engine) + return; + bus_engine_proxy_panel_extension_received (context->engine, event); } diff --git a/bus/inputcontext.h b/bus/inputcontext.h index 7674abd8..a46d5c06 100644 --- a/bus/inputcontext.h +++ b/bus/inputcontext.h @@ -28,6 +28,11 @@ #include "connection.h" #include "factoryproxy.h" +#ifndef __BUS_PANEL_PROXY_DEFINED +#define __BUS_PANEL_PROXY_DEFINED +typedef struct _BusPanelProxy BusPanelProxy; +#endif + /* * Type macros. */ @@ -63,6 +68,7 @@ BusInputContext *bus_input_context_new (BusConnection *connection, /** * bus_input_context_focus_in: + * @context: A #BusInputContext. * * Give a focus to the context. Call FocusIn, Enable, SetCapabilities, * and SetCursorLocation methods of the engine for the context, @@ -73,6 +79,7 @@ void bus_input_context_focus_in (BusInputContext *context); /** * bus_input_context_focus_out: + * @context: A #BusInputContext. * * Remove a focus from the context. Call FocusOut method of the engine for * the context. @@ -83,6 +90,7 @@ void bus_input_context_focus_out /** * bus_input_context_has_focus: + * @context: A #BusInputContext. * @returns: context->has_focus. */ gboolean bus_input_context_has_focus @@ -90,6 +98,7 @@ gboolean bus_input_context_has_focus /** * bus_input_context_enable: + * @context: A #BusInputContext. * * Enable the current engine for the context. Request an engine (if needed), * call FocusIn, Enable, SetCapabilities, and SetCursorLocation methods @@ -100,6 +109,7 @@ void bus_input_context_enable (BusInputContext *context); /** * bus_input_context_disable: + * @context: A #BusInputContext. * * Disable the current engine for the context. Request an engine (if needed), * call FocusIn, Enable, SetCapabilities, and SetCursorLocation methods @@ -110,6 +120,7 @@ void bus_input_context_disable (BusInputContext *context); /** * bus_input_context_page_up: + * @context: A #BusInputContext. * * Call page_up method of the current engine proxy. */ @@ -117,6 +128,7 @@ void bus_input_context_page_up (BusInputContext *context); /** * bus_input_context_page_down: + * @context: A #BusInputContext. * * Call page_down method of the current engine proxy. */ @@ -125,6 +137,7 @@ void bus_input_context_page_down /** * bus_input_context_cursor_up: + * @context: A #BusInputContext. * * Call cursor_up method of the current engine proxy. */ @@ -133,6 +146,7 @@ void bus_input_context_cursor_up /** * bus_input_context_cursor_down: + * @context: A #BusInputContext. * * Call cursor_down method of the current engine proxy. */ @@ -141,6 +155,10 @@ void bus_input_context_cursor_down /** * bus_input_context_candidate_clicked: + * @context: A #BusInputContext. + * @index: An index. + * @button: A button number. + * @state: A button state. * * Call candidate_clicked method of the current engine proxy. */ @@ -152,6 +170,8 @@ void bus_input_context_candidate_clicked /** * bus_input_context_set_engine: + * @context: A #BusInputContext. + * @engine: A #BusEngineProxy. * * Use the engine on the context. */ @@ -161,12 +181,14 @@ void bus_input_context_set_engine /** * bus_input_context_set_engine_by_desc: + * @context: A #BusInputContext. * @desc: the engine to use on the context. * @timeout: timeout (in ms) for D-Bus calls. * @callback: a function to be called when bus_input_context_set_engine_by_desc * is finished. if NULL, the default callback * function, which just calls * bus_input_context_set_engine_by_desc_finish, is used. + * @user_data: an argument of @callback. * * Create a new BusEngineProxy object and use it on the context. */ @@ -181,6 +203,9 @@ void bus_input_context_set_engine_by_desc /** * bus_input_context_set_engine_by_desc_finish: + * @context: A #BusInputContext. + * @res: A #GAsyncResult. + * @error: A #GError. * * A function to be called by the GAsyncReadyCallback function for * bus_input_context_set_engine_by_desc. @@ -192,6 +217,7 @@ gboolean bus_input_context_set_engine_by_desc_finish /** * bus_input_context_get_engine: + * @context: A #BusInputContext. * * Get a BusEngineProxy object of the current engine. */ @@ -200,6 +226,7 @@ BusEngineProxy *bus_input_context_get_engine /** * bus_input_context_get_engine_desc: + * @context: A #BusInputContext. * * Get an IBusEngineDesc object of the current engine. */ @@ -208,6 +235,9 @@ IBusEngineDesc *bus_input_context_get_engine_desc /** * bus_input_context_property_activate: + * @context: A #BusInputContext. + * @prop_name: A property name. + * @prop_state: A property state. * * Call property_activate method of the current engine proxy. */ @@ -219,6 +249,7 @@ void bus_input_context_property_activate /** * bus_input_context_get_capabilities: + * @context: A #BusInputContext. * @returns: context->capabilities. */ guint bus_input_context_get_capabilities @@ -226,6 +257,8 @@ guint bus_input_context_get_capabilities /** * bus_input_context_set_capabilities: + * @context: A #BusInputContext. + * @capabilities: capabilities. * * Call set_capabilities method of the current engine proxy. */ @@ -236,6 +269,7 @@ void bus_input_context_set_capabilities /** * bus_input_context_get_client: + * @context: A #BusInputContext. * @returns: context->client. */ const gchar *bus_input_context_get_client @@ -243,6 +277,7 @@ const gchar *bus_input_context_get_client /** * bus_input_context_get_content_type: + * @context: A #BusInputContext. * @purpose: Input purpose. * @hints: Input hints. */ @@ -253,6 +288,7 @@ void bus_input_context_get_content_type /** * bus_input_context_set_content_type: + * @context: A #BusInputContext. * @purpose: Input purpose. * @hints: Input hints. */ @@ -263,11 +299,83 @@ void bus_input_context_set_content_type /** * bus_input_context_commit_text: - * @text: a commited text. + * @context: A #BusInputContext. + * @text: A committed text. */ void bus_input_context_commit_text (BusInputContext *context, IBusText *text); +/** + * bus_input_context_commit_text: + * @context: A #BusInputContext. + * @text: A committed text. + * @use_extension: Use an extension if it's %TRUE and the extension is + * available. + */ +void bus_input_context_commit_text_use_extension + (BusInputContext *context, + IBusText *text, + gboolean use_extension); + +/** + * bus_input_context_set_emoji_extension: + * @context: A #BusInputContext. + * @extension: A #BusPanelProxy. + */ +void bus_input_context_set_emoji_extension + (BusInputContext *context, + BusPanelProxy *extension); + +/** + * bus_input_context_update_preedit_text: + * @context: A #BusInputContext. + * @text: An #IBusText. + * @cursor_pos: The cursor position. + * @visible: %TRUE if the preedit is visible. Otherwise %FALSE. + * @mode: The preedit commit mode. + * @use_extension: %TRUE if preedit text is sent to the extesion at first. + * + * Update a preedit text. Send D-Bus signal to update status of client or + * send glib signal to the panel, depending on capabilities of the client. + */ +void bus_input_context_update_preedit_text + (BusInputContext *context, + IBusText *text, + guint cursor_pos, + gboolean visible, + guint mode, + gboolean + use_extension); + +/** + * bus_input_context_update_lookup_table: + * @context: A #BusInputContext. + * @table: An #IBusTable. + * @visible: %TRUE if the lookup table is visible. Otherwise %FALSE. + * @is_extension: %TRUE if the lookup table is created by panel extensions. + * + * Update contents in the lookup table. + * Send D-Bus signal to update status of client or send glib signal to the + * panel, depending on capabilities of the client. + */ +void bus_input_context_update_lookup_table + (BusInputContext *context, + IBusLookupTable *table, + gboolean visible, + gboolean + is_extension); + + +/** + * bus_input_context_panel_extension_received: + * @context: A #BusInputContext. + * @event: An #IBusExtensionEvent. + * + * Send An #IBusExtensionEvent callback from an extension. + */ +void bus_input_context_panel_extension_received + (BusInputContext *context, + IBusExtensionEvent *event); G_END_DECLS #endif diff --git a/bus/panelproxy.c b/bus/panelproxy.c index c3908fcf..1c0fcca2 100644 --- a/bus/panelproxy.c +++ b/bus/panelproxy.c @@ -52,6 +52,10 @@ enum { PROPERTY_HIDE, COMMIT_TEXT, PANEL_EXTENSION, + PANEL_EXTENSION_REGISTER_KEYS, + UPDATE_PREEDIT_TEXT_RECEIVED, + UPDATE_LOOKUP_TABLE_RECEIVED, + UPDATE_AUXILIARY_TEXT_RECEIVED, LAST_SIGNAL, }; @@ -125,8 +129,8 @@ bus_panel_proxy_new (BusConnection *connection, case PANEL_TYPE_PANEL: path = IBUS_PATH_PANEL; break; - case PANEL_TYPE_EXTENSION: - path = IBUS_PATH_PANEL_EXTENSION; + case PANEL_TYPE_EXTENSION_EMOJI: + path = IBUS_PATH_PANEL_EXTENSION_EMOJI; break; default: g_return_val_if_reached (NULL); @@ -257,9 +261,53 @@ bus_panel_proxy_class_init (BusPanelProxyClass *class) G_SIGNAL_RUN_LAST, 0, NULL, NULL, + bus_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + IBUS_TYPE_EXTENSION_EVENT); + + panel_signals[PANEL_EXTENSION_REGISTER_KEYS] = + g_signal_new (I_("panel-extension-register-keys"), + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, bus_marshal_VOID__VARIANT, G_TYPE_NONE, 1, G_TYPE_VARIANT); + + panel_signals[UPDATE_PREEDIT_TEXT_RECEIVED] = + g_signal_new (I_("update-preedit-text-received"), + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + bus_marshal_VOID__OBJECT_UINT_BOOLEAN, + G_TYPE_NONE, 3, + IBUS_TYPE_TEXT, + G_TYPE_UINT, + G_TYPE_BOOLEAN); + + panel_signals[UPDATE_LOOKUP_TABLE_RECEIVED] = + g_signal_new (I_("update-lookup-table-received"), + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + bus_marshal_VOID__OBJECT_BOOLEAN, + G_TYPE_NONE, 2, + IBUS_TYPE_LOOKUP_TABLE, + G_TYPE_BOOLEAN); + + panel_signals[UPDATE_AUXILIARY_TEXT_RECEIVED] = + g_signal_new (I_("update-auxiliary-text-received"), + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + bus_marshal_VOID__OBJECT_BOOLEAN, + G_TYPE_NONE, 2, + IBUS_TYPE_TEXT, + G_TYPE_BOOLEAN); } static void @@ -355,23 +403,83 @@ bus_panel_proxy_g_signal (GDBusProxy *proxy, if (g_strcmp0 ("CommitText", signal_name) == 0) { GVariant *arg0 = NULL; - g_variant_get (parameters, "(v)", &arg0); - g_return_if_fail (arg0 != NULL); + g_variant_get (parameters, "(v)", &arg0); + g_return_if_fail (arg0); IBusText *text = IBUS_TEXT (ibus_serializable_deserialize (arg0)); g_variant_unref (arg0); - g_return_if_fail (text != NULL); + g_return_if_fail (text); g_signal_emit (panel, panel_signals[COMMIT_TEXT], 0, text); _g_object_unref_if_floating (text); return; } if (g_strcmp0 ("PanelExtension", signal_name) == 0) { - if (panel->panel_type != PANEL_TYPE_PANEL) { - g_warning ("Wrong signal"); - return; - } - g_signal_emit (panel, panel_signals[PANEL_EXTENSION], 0, parameters); + GVariant *arg0 = NULL; + + g_variant_get (parameters, "(v)", &arg0); + g_return_if_fail (arg0); + IBusExtensionEvent *event = IBUS_EXTENSION_EVENT ( + ibus_serializable_deserialize (arg0)); + g_variant_unref (arg0); + g_return_if_fail (event); + g_signal_emit (panel, panel_signals[PANEL_EXTENSION], 0, event); + _g_object_unref_if_floating (event); + return; + } + + if (g_strcmp0 ("PanelExtensionRegisterKeys", signal_name) == 0) { + g_signal_emit (panel, panel_signals[PANEL_EXTENSION_REGISTER_KEYS], 0, + parameters); + return; + } + + if (g_strcmp0 ("UpdatePreeditTextReceived", signal_name) == 0) { + GVariant *variant = NULL; + guint cursor_pos = 0; + gboolean visible = FALSE; + IBusText *text = NULL; + + g_variant_get (parameters, "(vub)", &variant, &cursor_pos, &visible); + g_return_if_fail (variant); + text = (IBusText *) ibus_serializable_deserialize (variant); + g_variant_unref (variant); + g_return_if_fail (text); + g_signal_emit (panel, panel_signals[UPDATE_PREEDIT_TEXT_RECEIVED], 0, + text, cursor_pos, visible); + _g_object_unref_if_floating (text); + return; + } + + if (g_strcmp0 ("UpdateLookupTableReceived", signal_name) == 0) { + GVariant *variant = NULL; + gboolean visible = FALSE; + IBusLookupTable *table = NULL; + + g_variant_get (parameters, "(vb)", &variant, &visible); + g_return_if_fail (variant); + table = (IBusLookupTable *) ibus_serializable_deserialize (variant); + g_variant_unref (variant); + g_return_if_fail (table); + g_signal_emit (panel, panel_signals[UPDATE_LOOKUP_TABLE_RECEIVED], 0, + table, visible); + _g_object_unref_if_floating (table); + return; + } + + if (g_strcmp0 ("UpdateAuxiliaryTextReceived", signal_name) == 0) { + GVariant *variant = NULL; + gboolean visible = FALSE; + IBusText *text = NULL; + + g_variant_get (parameters, "(vb)", &variant, &visible); + g_return_if_fail (variant); + text = (IBusText *) ibus_serializable_deserialize (variant); + g_variant_unref (variant); + g_return_if_fail (text); + g_signal_emit (panel, panel_signals[UPDATE_AUXILIARY_TEXT_RECEIVED], 0, + text, visible); + _g_object_unref_if_floating (text); return; } @@ -552,12 +660,17 @@ static void bus_panel_proxy_commit_text (BusPanelProxy *panel, IBusText *text) { + gboolean use_extension = TRUE; g_assert (BUS_IS_PANEL_PROXY (panel)); g_assert (text != NULL); - if (panel->focused_context) { - bus_input_context_commit_text (panel->focused_context, text); - } + if (!panel->focused_context) + return; + if (panel->panel_type != PANEL_TYPE_PANEL) + use_extension = FALSE; + bus_input_context_commit_text_use_extension (panel->focused_context, + text, + use_extension); } #define DEFINE_FUNCTION(Name, name) \ @@ -877,3 +990,74 @@ bus_panel_proxy_get_panel_type (BusPanelProxy *panel) g_assert (BUS_IS_PANEL_PROXY (panel)); return panel->panel_type; } + +void +bus_panel_proxy_panel_extension_received (BusPanelProxy *panel, + IBusExtensionEvent *event) +{ + GVariant *data; + + g_assert (BUS_IS_PANEL_PROXY (panel)); + g_assert (event); + + data = ibus_serializable_serialize (IBUS_SERIALIZABLE (event)); + g_return_if_fail (data); + g_dbus_proxy_call ((GDBusProxy *)panel, + "PanelExtensionReceived", + g_variant_new ("(v)", data), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); +} + +void +bus_panel_proxy_process_key_event (BusPanelProxy *panel, + guint keyval, + guint keycode, + guint state, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_assert (BUS_IS_PANEL_PROXY (panel)); + + g_dbus_proxy_call ((GDBusProxy *)panel, + "ProcessKeyEvent", + g_variant_new ("(uuu)", keyval, keycode, state), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + callback, + user_data); +} + +void +bus_panel_proxy_commit_text_received (BusPanelProxy *panel, + IBusText *text) +{ + GVariant *variant; + + g_assert (BUS_IS_PANEL_PROXY (panel)); + g_assert (IBUS_IS_TEXT (text)); + + variant = ibus_serializable_serialize (IBUS_SERIALIZABLE (text)); + g_dbus_proxy_call ((GDBusProxy *)panel, + "CommitTextReceived", + g_variant_new ("(v)", variant), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); +} + +void +bus_panel_proxy_candidate_clicked_lookup_table (BusPanelProxy *panel, + guint index, + guint button, + guint state) +{ + gboolean use_extension = TRUE; + g_assert (BUS_IS_PANEL_PROXY (panel)); + + g_dbus_proxy_call ((GDBusProxy *)panel, + "CandidateClickedLookupTable", + g_variant_new ("(uuu)", index, button, state), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); +} diff --git a/bus/panelproxy.h b/bus/panelproxy.h index b5a7af17..4d8afb98 100644 --- a/bus/panelproxy.h +++ b/bus/panelproxy.h @@ -55,7 +55,7 @@ typedef enum { PANEL_TYPE_NONE, PANEL_TYPE_PANEL, - PANEL_TYPE_EXTENSION + PANEL_TYPE_EXTENSION_EMOJI } PanelType; typedef struct _BusPanelProxy BusPanelProxy; @@ -135,6 +135,27 @@ void bus_panel_proxy_set_content_type guint hints); PanelType bus_panel_proxy_get_panel_type (BusPanelProxy *panel); +void bus_panel_proxy_panel_extension_received + (BusPanelProxy *panel, + IBusExtensionEvent + *event); +void bus_panel_proxy_process_key_event + (BusPanelProxy *panel, + guint keyval, + guint keycode, + guint state, + GAsyncReadyCallback + callback, + gpointer user_data); +void bus_panel_proxy_commit_text_received + (BusPanelProxy *panel, + IBusText *text); +void bus_panel_proxy_candidate_clicked_lookup_table + (BusPanelProxy *panel, + guint index, + guint button, + guint state); + G_END_DECLS #endif diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in index 3c6b6f69..f4a019d0 100644 --- a/data/ibus.schemas.in +++ b/data/ibus.schemas.in @@ -354,6 +354,18 @@ </locale> </schema> <schema> + <key>/schemas/desktop/ibus/panel/emoji/unicode-hotkey</key> + <applyto>/desktop/ibus/panel/emoji/unicode-hotkey</applyto> + <owner>ibus</owner> + <type>list</type> + <list_type>string</list_type> + <default>[<Control><Shift>u]</default> + <locale name="C"> + <short>Unicode shortcut keys for gtk_accelerator_parse</short> + <long>The shortcut keys for turning Unicode typing on or off</long> + </locale> + </schema> + <schema> <key>/schemas/desktop/ibus/panel/emoji/hotkey</key> <applyto>/desktop/ibus/panel/emoji/hotkey</applyto> <owner>ibus</owner> diff --git a/setup/main.py b/setup/main.py index f0eee996..f6adb098 100644 --- a/setup/main.py +++ b/setup/main.py @@ -4,7 +4,7 @@ # ibus - The Input Bus # # Copyright (c) 2007-2016 Peng Huang <shawn.p.huang@gmail.com> -# Copyright (c) 2010-2017 Takao Fujiwara <takao.fujiwara1@gmail.com> +# Copyright (c) 2010-2018 Takao Fujiwara <takao.fujiwara1@gmail.com> # Copyright (c) 2007-2016 Red Hat, Inc. # # This library is free software; you can redistribute it and/or @@ -123,10 +123,15 @@ class Setup(object): name = 'emoji' label = 'emoji_dialog' self.__init_hotkey(name, label) + name = 'unicode' + label = 'unicode_dialog' + self.__init_hotkey(name, label) def __init_hotkey(self, name, label, comment=None): if name == 'emoji': shortcuts = self.__settings_emoji.get_strv('hotkey') + elif name == 'unicode': + shortcuts = self.__settings_emoji.get_strv('unicode-hotkey') else: shortcuts = self.__settings_hotkey.get_strv(name) button = self.__builder.get_object("button_%s" % label) @@ -139,6 +144,9 @@ class Setup(object): if name == 'emoji': button.connect("clicked", self.__shortcut_button_clicked_cb, 'hotkey', 'panel/' + name, label, entry) + elif name == 'unicode': + button.connect("clicked", self.__shortcut_button_clicked_cb, + 'unicode-hotkey', 'panel/emoji', label, entry) else: button.connect("clicked", self.__shortcut_button_clicked_cb, name, "general/hotkey", label, entry) diff --git a/setup/setup.ui b/setup/setup.ui index e64b1046..f1beb1de 100644 --- a/setup/setup.ui +++ b/setup/setup.ui @@ -870,9 +870,9 @@ <object class="GtkLabel" id="label_emoji1"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="tooltip_text" translatable="yes">The shortcut keys for showing emoji dialog</property> + <property name="tooltip_text" translatable="yes">The shortcut keys to enable conversions of emoji annotations or Unicode names</property> <property name="halign">start</property> - <property name="label" translatable="yes">Emoji choice:</property> + <property name="label" translatable="yes">Emoji annotation:</property> </object> <packing> <property name="left_attach">0</property> @@ -920,6 +920,60 @@ <property name="top_attach">0</property> </packing> </child> + <child> + <object class="GtkLabel" id="label_unicode1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="tooltip_text" translatable="yes">The shortcut keys to enable Unicode code point conversions</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Unicode code point:</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="hbox_unicode1"> + <property name="orientation">horizontal</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <property name="hexpand">true</property> + <child> + <object class="GtkEntry" id="entry_unicode_dialog"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">False</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="button_unicode_dialog"> + <property name="label" translatable="yes">...</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="use_underline">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">1</property> + </packing> + </child> </object> </child> <child type="label"> diff --git a/src/ibusengine.c b/src/ibusengine.c index fd61102a..a3ccd7dd 100644 --- a/src/ibusengine.c +++ b/src/ibusengine.c @@ -64,8 +64,6 @@ enum { }; -typedef struct _IBusEngineKeybinding IBusEngineKeybinding; - /* IBusEnginePriv */ struct _IBusEnginePrivate { gchar *engine_name; @@ -81,14 +79,11 @@ struct _IBusEnginePrivate { guint content_purpose; guint content_hints; - GSettings *settings_emoji; - IBusEngineKeybinding **emoji_keybindings; + GHashTable *extension_keybindings; + gboolean enable_extension; + gchar *current_extension_name; }; -struct _IBusEngineKeybinding { - guint keyval; - IBusModifierType modifiers; -}; static guint engine_signals[LAST_SIGNAL] = { 0 }; @@ -191,10 +186,6 @@ static void ibus_engine_dbus_property_changed const gchar *property_name, GVariant *value); static void ibus_engine_keybinding_free (IBusEngine *engine); -static void settings_emoji_hotkey_changed_cb - (GSettings *settings, - const gchar *key, - gpointer data); G_DEFINE_TYPE (IBusEngine, ibus_engine, IBUS_TYPE_SERVICE) @@ -253,6 +244,12 @@ static const gchar introspection_xml[] = " <arg direction='in' type='u' name='cursor_pos' />" " <arg direction='in' type='u' name='anchor_pos' />" " </method>" + " <method name='PanelExtensionReceived'>" + " <arg direction='in' type='v' name='event' />" + " </method>" + " <method name='PanelExtensionRegisterKeys'>" + " <arg direction='in' type='v' name='data' />" + " </method>" /* FIXME signals */ " <signal name='CommitText'>" " <arg type='v' name='text' />" @@ -309,16 +306,22 @@ ibus_engine_class_init (IBusEngineClass *class) GObjectClass *gobject_class = G_OBJECT_CLASS (class); IBusObjectClass *ibus_object_class = IBUS_OBJECT_CLASS (class); - gobject_class->set_property = (GObjectSetPropertyFunc) ibus_engine_set_property; - gobject_class->get_property = (GObjectGetPropertyFunc) ibus_engine_get_property; + gobject_class->set_property = + (GObjectSetPropertyFunc) ibus_engine_set_property; + gobject_class->get_property = + (GObjectGetPropertyFunc) ibus_engine_get_property; ibus_object_class->destroy = (IBusObjectDestroyFunc) ibus_engine_destroy; - IBUS_SERVICE_CLASS (class)->service_method_call = ibus_engine_service_method_call; - IBUS_SERVICE_CLASS (class)->service_get_property = ibus_engine_service_get_property; - IBUS_SERVICE_CLASS (class)->service_set_property = ibus_engine_service_set_property; + IBUS_SERVICE_CLASS (class)->service_method_call = + ibus_engine_service_method_call; + IBUS_SERVICE_CLASS (class)->service_get_property = + ibus_engine_service_get_property; + IBUS_SERVICE_CLASS (class)->service_set_property = + ibus_engine_service_set_property; - ibus_service_class_add_interfaces (IBUS_SERVICE_CLASS (class), introspection_xml); + ibus_service_class_add_interfaces (IBUS_SERVICE_CLASS (class), + introspection_xml); class->process_key_event = ibus_engine_process_key_event; class->focus_in = ibus_engine_focus_in; @@ -839,26 +842,25 @@ ibus_engine_init (IBusEngine *engine) { IBusEnginePrivate *priv; engine->priv = priv = IBUS_ENGINE_GET_PRIVATE (engine); - priv->surrounding_text = g_object_ref_sink (text_empty); - priv->settings_emoji = - g_settings_new ("org.freedesktop.ibus.panel.emoji"); - settings_emoji_hotkey_changed_cb (priv->settings_emoji, "hotkey", engine); - g_signal_connect (priv->settings_emoji, "changed::hotkey", - G_CALLBACK (settings_emoji_hotkey_changed_cb), engine); + priv->extension_keybindings = g_hash_table_new_full ( + g_str_hash, + g_str_equal, + g_free, + g_free); } static void ibus_engine_destroy (IBusEngine *engine) { - g_free (engine->priv->engine_name); - engine->priv->engine_name = NULL; + IBusEnginePrivate *priv = engine->priv; - if (engine->priv->surrounding_text) { - g_object_unref (engine->priv->surrounding_text); - engine->priv->surrounding_text = NULL; - } - ibus_engine_keybinding_free (engine); + g_clear_pointer (&priv->engine_name, g_free); + g_clear_pointer (&priv->current_extension_name, g_free); + if (priv->surrounding_text) + g_clear_object (&priv->surrounding_text); + if (priv->extension_keybindings) + g_clear_pointer (&priv->extension_keybindings, g_hash_table_destroy); IBUS_OBJECT_CLASS(ibus_engine_parent_class)->destroy (IBUS_OBJECT (engine)); } @@ -895,19 +897,38 @@ ibus_engine_get_property (IBusEngine *engine, } static void -ibus_engine_panel_extension (IBusEngine *engine) +ibus_engine_panel_extension (IBusEngine *engine, + const gchar *name) { - IBusXEvent *xevent = ibus_x_event_new ( - "event-type", IBUS_X_EVENT_KEY_PRESS, - "purpose", "emoji", + IBusEnginePrivate *priv; + IBusExtensionEvent *event; + GVariant *data; + + g_assert (IBUS_IS_ENGINE (engine)); + g_assert (name); + + priv = engine->priv; + if (!g_strcmp0 (name, priv->current_extension_name)) + priv->enable_extension = !priv->enable_extension; + else + priv->enable_extension = TRUE; + if (priv->enable_extension) { + g_free (priv->current_extension_name); + priv->current_extension_name = g_strdup (name); + } + event = ibus_extension_event_new ( + "name", name, + "is-enabled", priv->enable_extension, NULL); - GVariant *data = ibus_serializable_serialize_object ( - IBUS_SERIALIZABLE (xevent)); + g_assert (IBUS_IS_EXTENSION_EVENT (event)); + data = ibus_serializable_serialize_object ( + IBUS_SERIALIZABLE (event)); g_assert (data != NULL); ibus_engine_emit_signal (engine, "PanelExtension", g_variant_new ("(v)", data)); + g_object_unref (event); } static gboolean @@ -917,7 +938,8 @@ ibus_engine_filter_key_event (IBusEngine *engine, guint state) { IBusEnginePrivate *priv; - int i; + GList *names, *n; + IBusProcessKeyEventData *keys; guint modifiers; if ((state & IBUS_RELEASE_MASK) != 0) @@ -925,22 +947,29 @@ ibus_engine_filter_key_event (IBusEngine *engine, g_return_val_if_fail (IBUS_IS_ENGINE (engine), FALSE); priv = engine->priv; - if (!priv->emoji_keybindings) - return FALSE; - modifiers = state & IBUS_MODIFIER_FILTER; if (keyval >= IBUS_KEY_A && keyval <= IBUS_KEY_Z && (modifiers & IBUS_SHIFT_MASK) != 0) { keyval = keyval - IBUS_KEY_A + IBUS_KEY_a; } - for (i = 0; priv->emoji_keybindings[i]; i++) { - IBusEngineKeybinding *binding = priv->emoji_keybindings[i]; - if (binding->keyval == keyval && - binding->modifiers == modifiers) { - ibus_engine_panel_extension (engine); - return TRUE; + names = g_hash_table_get_keys (priv->extension_keybindings); + if (!names) + return FALSE; + for (n = names; n; n = n->next) { + const gchar *name = (const gchar *)n->data; + keys = g_hash_table_lookup (priv->extension_keybindings, name); + for (; keys; keys++) { + if (keys->keyval == 0 && keys->keycode == 0 && keys->state == 0) + break; + if (keys->keyval == keyval && + keys->state == modifiers && + (keys->keycode == 0 || keys->keycode == keycode)) { + ibus_engine_panel_extension (engine, name); + return TRUE; + } } } + g_list_free (names); return FALSE; } @@ -954,6 +983,97 @@ ibus_engine_service_authorized_method (IBusService *service, } static void +ibus_engine_service_panel_extension_register_keys (IBusEngine *engine, + GVariant *parameters, + GDBusMethodInvocation + *invocation) +{ + IBusEnginePrivate *priv = engine->priv; + GVariant *v1 = NULL; + GVariant *v2 = NULL; + GVariant *v3 = NULL; + GVariant *vkeys = NULL; + GVariantIter *iter1 = NULL; + GVariantIter *iter2 = NULL; + const gchar *name = NULL; + guint failure_id = 0; + + g_variant_get (parameters, "(v)", &v1); + if (v1) + g_variant_get (v1, "(v)", &v2); + else + failure_id = 1; + if (v2) + g_variant_get (v2, "a{sv}", &iter1); + else + failure_id = 2; + if (iter1) { + while (g_variant_iter_loop (iter1, "{&sv}", &name, &vkeys)) { + if (vkeys) + g_variant_get (vkeys, "av", &iter2); + if (name && iter2) { + IBusProcessKeyEventData *keys = NULL; + gint num = 0; + while (g_variant_iter_loop (iter2, "v", &v3)) { + if (v3) { + guint keyval = 0; + guint keycode = 0; + guint state = 0; + g_variant_get (v3, "(iii)", + &keyval, &keycode, &state); + if (!keys) + keys = g_new0 (IBusProcessKeyEventData, 2); + else + keys = g_renew (IBusProcessKeyEventData, + keys, + num + 2); + keys[num].keyval = keyval; + keys[num].keycode = keycode; + keys[num].state = state; + keys[num + 1].keyval = 0; + keys[num + 1].keycode = 0; + keys[num + 1].state = 0; + g_clear_pointer (&v3, g_variant_unref); + num++; + } else { + failure_id = 5; + } + } + if (num > 0) { + g_hash_table_replace (priv->extension_keybindings, + g_strdup (name), + keys); + } else { + g_hash_table_remove (priv->extension_keybindings, name); + } + g_clear_pointer (&iter2, g_variant_iter_free); + } else { + failure_id = 4; + } + g_clear_pointer (&vkeys, g_variant_unref); + name = NULL; + } + g_variant_iter_free (iter1); + } else { + failure_id = 3; + } + if (failure_id == 0) { + g_dbus_method_invocation_return_value (invocation, NULL); + } else { + g_dbus_method_invocation_return_error ( + invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "PanelExtensionRegisterKeys method gives NULL: %d", + failure_id); + } + if (v2) + g_variant_unref (v2); + if (v1) + g_variant_unref (v1); +} + +static void ibus_engine_service_method_call (IBusService *service, GDBusConnection *connection, const gchar *sender, @@ -964,6 +1084,7 @@ ibus_engine_service_method_call (IBusService *service, GDBusMethodInvocation *invocation) { IBusEngine *engine = IBUS_ENGINE (service); + IBusEnginePrivate *priv = engine->priv; if (g_strcmp0 (interface_name, IBUS_INTERFACE_ENGINE) != 0) { IBUS_SERVICE_CLASS (ibus_engine_parent_class)-> @@ -1002,6 +1123,33 @@ ibus_engine_service_method_call (IBusService *service, g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", retval)); return; } + if (g_strcmp0 (method_name, "PanelExtensionReceived") == 0) { + GVariant *arg0 = NULL; + IBusExtensionEvent *event = NULL; + + g_variant_get (parameters, "(v)", &arg0); + if (arg0) { + event = (IBusExtensionEvent *)ibus_serializable_deserialize_object ( + arg0); + } + if (!event) { + g_dbus_method_invocation_return_error ( + invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "PanelExtensionReceived method gives NULL"); + return; + } + priv->enable_extension = ibus_extension_event_is_enabled (event); + g_dbus_method_invocation_return_value (invocation, NULL); + return; + } + if (g_strcmp0 (method_name, "PanelExtensionRegisterKeys") == 0) { + ibus_engine_service_panel_extension_register_keys (engine, + parameters, + invocation); + return; + } static const struct { gchar *member; @@ -1441,73 +1589,10 @@ static void ibus_engine_keybinding_free (IBusEngine *engine) { IBusEnginePrivate *priv; - int i; g_return_if_fail (IBUS_IS_ENGINE (engine)); priv = engine->priv; - if (priv->emoji_keybindings) { - for (i = 0; priv->emoji_keybindings[i]; i++) - g_slice_free (IBusEngineKeybinding, priv->emoji_keybindings[i]); - g_clear_pointer (&priv->emoji_keybindings, g_free); - } -} - -static IBusEngineKeybinding * -ibus_engine_keybinding_new (IBusEngine *engine, - const gchar *accelerator) -{ - guint keyval = 0U; - IBusModifierType modifiers = 0; - IBusEngineKeybinding *binding = NULL; - - ibus_accelerator_parse (accelerator, &keyval, &modifiers); - if (keyval == 0U && modifiers == 0) { - g_warning ("Failed to parse shortcut key '%s'", accelerator); - return NULL; - } - if (modifiers & IBUS_SUPER_MASK) { - modifiers^=IBUS_SUPER_MASK; - modifiers|=IBUS_MOD4_MASK; - } - - binding = g_slice_new0 (IBusEngineKeybinding); - binding->keyval = keyval; - binding->modifiers = modifiers; - return binding; -} - -static void -settings_emoji_hotkey_changed_cb (GSettings *settings, - const gchar *key, - gpointer data) -{ - IBusEngine *engine; - IBusEnginePrivate *priv; - gchar **accelerators; - int i, j, length; - g_return_if_fail (IBUS_IS_ENGINE (data)); - engine = IBUS_ENGINE (data); - priv = engine->priv; - - if (g_strcmp0 (key, "hotkey") != 0) - return; - accelerators = g_settings_get_strv (settings, key); - length = g_strv_length (accelerators); - ibus_engine_keybinding_free (engine); - if (length == 0) { - g_strfreev (accelerators); - return; - } - priv->emoji_keybindings = g_new0 (IBusEngineKeybinding*, length + 1); - for (i = 0, j = 0; i < length; i++) { - IBusEngineKeybinding *binding = - ibus_engine_keybinding_new (engine, accelerators[i]); - if (!binding) - continue; - priv->emoji_keybindings[j++] = binding; - } - g_strfreev (accelerators); } IBusEngine * diff --git a/src/ibuspanelservice.c b/src/ibuspanelservice.c index f37b91c3..71028ebf 100644 --- a/src/ibuspanelservice.c +++ b/src/ibuspanelservice.c @@ -57,6 +57,9 @@ enum { DESTROY_CONTEXT, SET_CONTENT_TYPE, PANEL_EXTENSION_RECEIVED, + PROCESS_KEY_EVENT, + COMMIT_TEXT_RECEIVED, + CANDIDATE_CLICKED_LOOKUP_TABLE, LAST_SIGNAL, }; @@ -153,7 +156,7 @@ static void ibus_panel_service_set_content_type guint hints); static void ibus_panel_service_panel_extension_received (IBusPanelService *panel, - GVariant *data); + IBusExtensionEvent *event); G_DEFINE_TYPE (IBusPanelService, ibus_panel_service, IBUS_TYPE_SERVICE) @@ -184,6 +187,11 @@ static const gchar introspection_xml[] = " <method name='CursorDownLookupTable' />" " <method name='PageUpLookupTable' />" " <method name='PageDownLookupTable' />" + " <method name='CandidateClickedLookupTable'>" + " <arg direction='in' type='u' name='index' />" + " <arg direction='in' type='u' name='button' />" + " <arg direction='in' type='u' name='state' />" + " </method>" " <method name='RegisterProperties'>" " <arg direction='in' type='v' name='props' />" " </method>" @@ -221,7 +229,16 @@ static const gchar introspection_xml[] = " <arg direction='in' type='u' name='hints' />" " </method>" " <method name='PanelExtensionReceived'>" - " <arg direction='in' type='v' name='data' />" + " <arg direction='in' type='v' name='event' />" + " </method>" + " <method name='ProcessKeyEvent'>" + " <arg direction='in' type='u' name='keyval' />" + " <arg direction='in' type='u' name='keycode' />" + " <arg direction='in' type='u' name='state' />" + " <arg direction='out' type='b' />" + " </method>" + " <method name='CommitTextReceived'>" + " <arg direction='in' type='v' name='text' />" " </method>" /* Signals */ " <signal name='CursorUp' />" @@ -247,7 +264,23 @@ static const gchar introspection_xml[] = " <arg type='v' name='text' />" " </signal>" " <signal name='PanelExtension'>" + " <arg type='v' name='event' />" + " </signal>" + " <method name='PanelExtensionRegisterKeys'>" " <arg type='v' name='data' />" + " </method>" + " <signal name='UpdatePreeditTextReceived'>" + " <arg type='v' name='text' />" + " <arg type='u' name='cursor_pos' />" + " <arg type='b' name='visible' />" + " </signal>" + " <signal name='UpdateAuxiliaryTextReceived'>" + " <arg type='v' name='text' />" + " <arg type='b' name='visible' />" + " </signal>" + " <signal name='UpdateLookupTableReceived'>" + " <arg type='v' name='table' />" + " <arg type='b' name='visible' />" " </signal>" " </interface>" "</node>"; @@ -927,10 +960,81 @@ ibus_panel_service_class_init (IBusPanelServiceClass *class) G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (IBusPanelServiceClass, panel_extension_received), NULL, NULL, - _ibus_marshal_VOID__VARIANT, + _ibus_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + IBUS_TYPE_EXTENSION_EVENT); + + /** + * IBusPanelService::process-key-event: + * @panel: An #IBusPanelService + * @keyval: Key symbol of the key press. + * @keycode: KeyCode of the key press. + * @state: Key modifier flags. + * + * Emitted when a key event is received. + * Implement the member function IBusPanelServiceClass::process_key_event + * in extended class to receive this signal. + * Both the key symbol and keycode are passed to the member function. + * See ibus_input_context_process_key_event() for further explanation of + * key symbol, keycode and which to use. + * + * Returns: %TRUE for successfully process the key; %FALSE otherwise. + * See also: ibus_input_context_process_key_event(). + * + * <note><para>Argument @user_data is ignored in this function.</para> + * </note> + */ + panel_signals[PROCESS_KEY_EVENT] = + g_signal_new (I_("process-key-event"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (IBusPanelServiceClass, process_key_event), + g_signal_accumulator_true_handled, NULL, + _ibus_marshal_BOOL__UINT_UINT_UINT, + G_TYPE_BOOLEAN, + 3, + G_TYPE_UINT, + G_TYPE_UINT, + G_TYPE_UINT); + + /** + * IBusPanelService::commit-text-received: + * @panel: An #IBusPanelService + * @text: A #IBusText + * + * Emitted when the client application get the ::commit-text-received. + * Implement the member function + * IBusPanelServiceClass::commit_text_received in extended class to + * receive this signal. + * + * <note><para>Argument @user_data is ignored in this function.</para> + * </note> + */ + panel_signals[COMMIT_TEXT_RECEIVED] = + g_signal_new (I_("commit-text-received"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (IBusPanelServiceClass, commit_text_received), + NULL, NULL, + _ibus_marshal_VOID__OBJECT, G_TYPE_NONE, 1, - G_TYPE_VARIANT); + IBUS_TYPE_TEXT); + + panel_signals[CANDIDATE_CLICKED_LOOKUP_TABLE] = + g_signal_new (I_("candidate-clicked-lookup-table"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (IBusPanelServiceClass, + candidate_clicked_lookup_table), + NULL, NULL, + _ibus_marshal_VOID__UINT_UINT_UINT, + G_TYPE_NONE, + 3, + G_TYPE_UINT, + G_TYPE_UINT, + G_TYPE_UINT); } static void @@ -1129,9 +1233,14 @@ ibus_panel_service_service_method_call (IBusService *service, } if (g_strcmp0 (method_name, "PanelExtensionReceived") == 0) { - GVariant *variant = NULL; - g_variant_get (parameters, "(v)", &variant); - if (variant == NULL) { + GVariant *arg0 = NULL; + IBusExtensionEvent *event = NULL; + g_variant_get (parameters, "(v)", &arg0); + if (arg0) { + event = IBUS_EXTENSION_EVENT (ibus_serializable_deserialize (arg0)); + g_variant_unref (arg0); + } + if (!event) { g_dbus_method_invocation_return_error ( invocation, G_DBUS_ERROR, @@ -1140,11 +1249,63 @@ ibus_panel_service_service_method_call (IBusService *service, return; } g_signal_emit (panel, panel_signals[PANEL_EXTENSION_RECEIVED], 0, - variant); - g_variant_unref (variant); + event); + _g_object_unref_if_floating (event); g_dbus_method_invocation_return_value (invocation, NULL); return; } + if (g_strcmp0 (method_name, "ProcessKeyEvent") == 0) { + guint keyval, keycode, state; + gboolean retval = FALSE; + + g_variant_get (parameters, "(uuu)", &keyval, &keycode, &state); + g_signal_emit (panel, + panel_signals[PROCESS_KEY_EVENT], + 0, + keyval, + keycode, + state, + &retval); + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(b)", retval)); + return; + } + if (g_strcmp0 (method_name, "CommitTextReceived") == 0) { + GVariant *arg0 = NULL; + IBusText *text = NULL; + + g_variant_get (parameters, "(v)", &arg0); + if (arg0) { + text = (IBusText *) ibus_serializable_deserialize (arg0); + g_variant_unref (arg0); + } + if (!text) { + g_dbus_method_invocation_return_error ( + invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "CommitTextReceived method gives NULL"); + return; + } + g_signal_emit (panel, + panel_signals[COMMIT_TEXT_RECEIVED], + 0, + text); + _g_object_unref_if_floating (text); + return; + } + if (g_strcmp0 (method_name, "CandidateClickedLookupTable") == 0) { + guint index = 0; + guint button = 0; + guint state = 0; + g_variant_get (parameters, "(uuu)", &index, &button, &state); + g_signal_emit (panel, + panel_signals[CANDIDATE_CLICKED_LOOKUP_TABLE], + 0, + index, button, state); + return; + } + const static struct { const gchar *name; @@ -1318,8 +1479,8 @@ ibus_panel_service_set_content_type (IBusPanelService *panel, } static void -ibus_panel_service_panel_extension_received (IBusPanelService *panel, - GVariant *data) +ibus_panel_service_panel_extension_received (IBusPanelService *panel, + IBusExtensionEvent *event) { ibus_panel_service_not_implemented(panel); } @@ -1396,10 +1557,11 @@ void ibus_panel_service_commit_text (IBusPanelService *panel, IBusText *text) { + GVariant *variant; g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel)); g_return_if_fail (IBUS_IS_TEXT (text)); - GVariant *variant = ibus_serializable_serialize ((IBusSerializable *)text); + variant = ibus_serializable_serialize ((IBusSerializable *)text); ibus_service_emit_signal ((IBusService *) panel, NULL, IBUS_INTERFACE_PANEL, @@ -1413,18 +1575,144 @@ ibus_panel_service_commit_text (IBusPanelService *panel, } void -ibus_panel_service_panel_extension (IBusPanelService *panel, - GVariant *variant) +ibus_panel_service_panel_extension (IBusPanelService *panel, + IBusExtensionEvent *event) { + GVariant *variant; g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel)); - g_return_if_fail (variant); + g_return_if_fail (IBUS_IS_EXTENSION_EVENT (event)); + variant = ibus_serializable_serialize ((IBusSerializable *)event); ibus_service_emit_signal ((IBusService *) panel, NULL, IBUS_INTERFACE_PANEL, "PanelExtension", g_variant_new ("(v)", variant), NULL); + + if (g_object_is_floating (event)) { + g_object_unref (event); + } +} + +void +ibus_panel_service_panel_extension_register_keys (IBusPanelService *panel, + const gchar + *first_property_name, + ...) +{ + GVariantBuilder builder; + GVariantBuilder child; + const gchar *name; + va_list var_args; + IBusProcessKeyEventData *keys; + + g_return_if_fail (first_property_name); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + name = first_property_name; + + va_start (var_args, first_property_name); + do { + keys = va_arg (var_args, IBusProcessKeyEventData *); + g_return_if_fail (keys != NULL); + g_variant_builder_init (&child, G_VARIANT_TYPE ("av")); + for (; keys; keys++) { + if (keys->keyval == 0 && keys->keycode == 0 && keys->state == 0) + break; + g_variant_builder_add (&child, "v", + g_variant_new ("(iii)", + keys->keyval, + keys->keycode, + keys->state)); + } + g_variant_builder_add (&builder, "{sv}", + g_strdup (name), g_variant_builder_end (&child)); + } while ((name = va_arg (var_args, const gchar *))); + va_end (var_args); + + ibus_service_emit_signal ((IBusService *) panel, + NULL, + IBUS_INTERFACE_PANEL, + "PanelExtensionRegisterKeys", + g_variant_new ("(v)", + g_variant_builder_end (&builder)), + NULL); +} + +void +ibus_panel_service_update_preedit_text_received (IBusPanelService *panel, + IBusText *text, + guint cursor_pos, + gboolean visible) +{ + GVariant *variant; + + g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel)); + g_return_if_fail (IBUS_IS_TEXT (text)); + + variant = ibus_serializable_serialize ((IBusSerializable *)text); + g_return_if_fail (variant); + ibus_service_emit_signal ((IBusService *) panel, + NULL, + IBUS_INTERFACE_PANEL, + "UpdatePreeditTextReceived", + g_variant_new ("(vub)", + variant, cursor_pos, visible), + NULL); + + if (g_object_is_floating (text)) { + g_object_unref (text); + } +} + +void +ibus_panel_service_update_auxiliary_text_received (IBusPanelService *panel, + IBusText *text, + gboolean visible) +{ + GVariant *variant; + g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel)); + g_return_if_fail (IBUS_IS_TEXT (text)); + + variant = ibus_serializable_serialize ((IBusSerializable *)text); + g_return_if_fail (variant); + ibus_service_emit_signal ((IBusService *) panel, + NULL, + IBUS_INTERFACE_PANEL, + "UpdateAuxiliaryTextReceived", + g_variant_new ("(vb)", + variant, visible), + NULL); + + if (g_object_is_floating (text)) { + g_object_unref (text); + } +} + +void +ibus_panel_service_update_lookup_table_received (IBusPanelService *panel, + IBusLookupTable *table, + gboolean visible) +{ + GVariant *variant; + + g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel)); + g_return_if_fail (IBUS_IS_LOOKUP_TABLE (table)); + + variant = ibus_serializable_serialize ((IBusSerializable *)table); + g_return_if_fail (variant); + ibus_service_emit_signal ((IBusService *) panel, + NULL, + IBUS_INTERFACE_PANEL, + "UpdateLookupTableReceived", + g_variant_new ("(vb)", + variant, visible), + NULL); + + if (g_object_is_floating (table)) { + g_object_unref (table); + } } #define DEFINE_FUNC(name, Name) \ diff --git a/src/ibuspanelservice.h b/src/ibuspanelservice.h index 60ef842b..d91f2309 100644 --- a/src/ibuspanelservice.h +++ b/src/ibuspanelservice.h @@ -38,6 +38,7 @@ #include "ibuslookuptable.h" #include "ibusservice.h" #include "ibusproplist.h" +#include "ibusxevent.h" /* * Type macros. @@ -130,11 +131,24 @@ struct _IBusPanelServiceClass { gint h); void (* panel_extension_received) (IBusPanelService *panel, - GVariant *data); + IBusExtensionEvent *event); + gboolean (* process_key_event) + (IBusPanelService *panel, + guint keyval, + guint keycode, + guint state); + void (* commit_text_received) + (IBusPanelService *panel, + IBusText *text); + void (* candidate_clicked_lookup_table) + (IBusPanelService *panel, + guint index, + guint button, + guint state); /*< private >*/ /* padding */ - gpointer pdummy[5]; // We can add 8 pointers without breaking the ABI. + gpointer pdummy[2]; // We can add 8 pointers without breaking the ABI. }; GType ibus_panel_service_get_type (void); @@ -248,12 +262,105 @@ void ibus_panel_service_commit_text (IBusPanelService *panel, /** * ibus_panel_service_panel_extension: * @panel: An #IBusPanelService - * @data: (transfer full): A #GVariant data which is sent to a panel extension. + * @event: (transfer full): A #PanelExtensionEvent which is sent to a + * panel extension. * + * Enable or disable a panel extension with #IBusExtensionEvent. * Notify that a data is sent * by sending a "PanelExtension" message to IBus panel extension service. */ -void ibus_panel_service_panel_extension (IBusPanelService *panel, - GVariant *data); +void ibus_panel_service_panel_extension (IBusPanelService *panel, + IBusExtensionEvent *event); + +/** + * ibus_panel_service_panel_extension_register_keys: + * @panel: An #IBusPanelService + * @first_property_name: the first name of the shortcut keys. This is %NULL + " terminated. + * + * Register shortcut keys to enable panel extensions with #IBusExtensionEvent. + * Notify that a data is sent + * by sending a "PanelExtensionRegisterKeys" message to IBus panel extension + * service. Seems Vala does not support uint[][3] and use + * IBusProcessKeyEventData[]. E.g. + * IBusProcessKeyEventData[] keys = {{ + * IBUS_KEY_e, 0, IBUS_SHIFT_MASK | IBUS_SUPER_MASK }}; + * ibus_panel_service_panel_extension_register_keys(panel, "emoji", keys, NULL); + */ +void ibus_panel_service_panel_extension_register_keys + (IBusPanelService *panel, + const gchar *first_property_name, + ...); + +/** + * ibus_panel_service_update_preedit_text_received: + * @panel: An #IBusPanelService + * @text: Update content. + * @cursor_pos: Current position of cursor + * @visible: Whether the pre-edit buffer is visible. + * + * Notify that the preedit is updated by the panel extension + * + * (Note: The table object will be released, if it is floating. + * If caller want to keep the object, caller should make the object + * sink by g_object_ref_sink.) + */ +void ibus_panel_service_update_preedit_text_received + (IBusPanelService *panel, + IBusText *text, + guint cursor_pos, + gboolean visible); + +/** + * ibus_panel_service_show_preedit_text_received: + * @panel: An IBusPanelService + * + * Notify that the preedit is shown by the panel extension + */ +void ibus_panel_service_show_preedit_text_received + (IBusPanelService *panel); + +/** + * ibus_panel_service_hide_preedit_text_received: + * @panel: An IBusPanelService + * + * Notify that the preedit is hidden by the panel extension + */ +void ibus_panel_service_hide_preedit_text_received + (IBusPanelService *panel); + +/** + * ibus_panel_service_update_auxiliary_text_received: + * @panel: An #IBusPanelService + * @text: An #IBusText + * @visible: Whether the auxilirary text is visible. + * + * Notify that the auxilirary is updated by the panel extension. + * + * (Note: The table object will be released, if it is floating. + * If caller want to keep the object, caller should make the object + * sink by g_object_ref_sink.) + */ +void ibus_panel_service_update_auxiliary_text_received + (IBusPanelService *panel, + IBusText *text, + gboolean visible); + +/** + * ibus_panel_service_update_lookup_table_received: + * @panel: An #IBusPanelService + * @table: An #IBusLookupTable + * @visible: Whether the lookup table is visible. + * + * Notify that the lookup table is updated by the panel extension. + * + * (Note: The table object will be released, if it is floating. + * If caller want to keep the object, caller should make the object + * sink by g_object_ref_sink.) + */ +void ibus_panel_service_update_lookup_table_received + (IBusPanelService *panel, + IBusLookupTable *table, + gboolean visible); G_END_DECLS #endif diff --git a/src/ibusshare.h b/src/ibusshare.h index 757d915b..4f5a306b 100644 --- a/src/ibusshare.h +++ b/src/ibusshare.h @@ -74,6 +74,15 @@ #define IBUS_SERVICE_PANEL_EXTENSION "org.freedesktop.IBus.Panel.Extension" /** + * IBUS_SERVICE_PANEL_EXTENSION_EMOJI: + * + * Address of IBus panel extension service for emoji. + * This service provides emoji, Unicode code point, Unicode name features. + */ +#define IBUS_SERVICE_PANEL_EXTENSION_EMOJI \ + "org.freedesktop.IBus.Panel.Extension.Emoji" + +/** * IBUS_SERVICE_CONFIG: * * Address of IBus config service. @@ -109,11 +118,13 @@ #define IBUS_PATH_PANEL "/org/freedesktop/IBus/Panel" /** - * IBUS_PATH_PANEL_EXTENSION: + * IBUS_PATH_PANEL_EXTENSION_EMOJI: * - * D-Bus path for IBus panel. + * D-Bus path for IBus extension panel for emoji. + * This service provides emoji, Unicode code point, Unicode name features. */ -#define IBUS_PATH_PANEL_EXTENSION "/org/freedesktop/IBus/Panel/Extension" +#define IBUS_PATH_PANEL_EXTENSION_EMOJI \ + "/org/freedesktop/IBus/Panel/Extension/Emoji" /** * IBUS_PATH_CONFIG: diff --git a/src/ibusxevent.c b/src/ibusxevent.c index dea80272..287bb99b 100644 --- a/src/ibusxevent.c +++ b/src/ibusxevent.c @@ -22,13 +22,23 @@ #include "ibusinternal.h" #include "ibusxevent.h" +#define IBUS_EXTENSION_EVENT_VERSION 1 +#define IBUS_EXTENSION_EVENT_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), \ + IBUS_TYPE_EXTENSION_EVENT, \ + IBusExtensionEventPrivate)) + #define IBUS_X_EVENT_VERSION 1 -#define IBUS_X_EVENT_GET_PRIVATE(o) \ +#define IBUS_X_EVENT_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_X_EVENT, IBusXEventPrivate)) enum { PROP_0, PROP_VERSION, + PROP_NAME, + PROP_IS_ENABLED, + PROP_IS_EXTENSION, + PROP_PARAMS, PROP_EVENT_TYPE, PROP_WINDOW, PROP_SEND_EVENT, @@ -52,6 +62,14 @@ enum { }; +struct _IBusExtensionEventPrivate { + guint version; + gchar *name; + gboolean is_enabled; + gboolean is_extension; + gchar *params; +}; + struct _IBusXEventPrivate { guint version; guint32 time; @@ -73,25 +91,347 @@ struct _IBusXEventPrivate { }; /* functions prototype */ -static void ibus_x_event_destroy (IBusXEvent *event); -static void ibus_x_event_set_property (IBusXEvent *event, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static void ibus_x_event_get_property (IBusXEvent *event, - guint prop_id, - GValue *value, - GParamSpec *pspec); -static gboolean ibus_x_event_serialize (IBusXEvent *event, - GVariantBuilder *builder); -static gint ibus_x_event_deserialize (IBusXEvent *event, - GVariant *variant); -static gboolean ibus_x_event_copy (IBusXEvent *dest, - const IBusXEvent *src); - +static void ibus_extension_event_destroy (IBusExtensionEvent *event); +static void ibus_extension_event_set_property (IBusExtensionEvent *event, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void ibus_extension_event_get_property (IBusExtensionEvent *event, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static gboolean ibus_extension_event_serialize (IBusExtensionEvent *event, + GVariantBuilder + *builder); +static gint ibus_extension_event_deserialize (IBusExtensionEvent *event, + GVariant + *variant); +static gboolean ibus_extension_event_copy (IBusExtensionEvent + *dest, + const IBusExtensionEvent + *src); +static void ibus_x_event_destroy (IBusXEvent *event); +static void ibus_x_event_set_property (IBusXEvent *event, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void ibus_x_event_get_property (IBusXEvent *event, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static gboolean ibus_x_event_serialize (IBusXEvent *event, + GVariantBuilder + *builder); +static gint ibus_x_event_deserialize (IBusXEvent *event, + GVariant + *variant); +static gboolean ibus_x_event_copy (IBusXEvent *dest, + const IBusXEvent *src); + +G_DEFINE_TYPE (IBusExtensionEvent, ibus_extension_event, IBUS_TYPE_SERIALIZABLE) G_DEFINE_TYPE (IBusXEvent, ibus_x_event, IBUS_TYPE_SERIALIZABLE) static void +ibus_extension_event_class_init (IBusExtensionEventClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + IBusObjectClass *object_class = IBUS_OBJECT_CLASS (class); + IBusSerializableClass *serializable_class = IBUS_SERIALIZABLE_CLASS (class); + + gobject_class->set_property = + (GObjectSetPropertyFunc) ibus_extension_event_set_property; + gobject_class->get_property = + (GObjectGetPropertyFunc) ibus_extension_event_get_property; + + object_class->destroy = + (IBusObjectDestroyFunc) ibus_extension_event_destroy; + + serializable_class->serialize = + (IBusSerializableSerializeFunc) ibus_extension_event_serialize; + serializable_class->deserialize = + (IBusSerializableDeserializeFunc) ibus_extension_event_deserialize; + serializable_class->copy = + (IBusSerializableCopyFunc) ibus_extension_event_copy; + + /* install properties */ + /** + * IBusExtensionEvent:version: + * + * Version of the #IBusExtensionEvent. + */ + g_object_class_install_property (gobject_class, + PROP_VERSION, + g_param_spec_uint ("version", + "version", + "version", + 0, + G_MAXUINT32, + IBUS_EXTENSION_EVENT_VERSION, + G_PARAM_READABLE)); + + /** + * IBusExtensionEvent:name: + * + * Name of the extension in the #IBusExtensionEvent. + */ + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", + "name", + "name of the extension", + "", + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + /** + * IBusExtensionEvent:is-enabled: + * + * %TRUE if the extension is enabled in the #IBusExtensionEvent. + */ + g_object_class_install_property (gobject_class, + PROP_IS_ENABLED, + g_param_spec_boolean ("is-enabled", + "is enabled", + "if the extension is enabled", + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + /** + * IBusExtensionEvent:is-extension: + * + * %TRUE if the #IBusExtensionEvent is called by an extension. + * %FALSE if the #IBusExtensionEvent is called by an active engine or + * panel. + * If this value is %TRUE, the event is send to ibus-daemon, an active + * engine. If it's %FALSE, the event is sned to ibus-daemon, panels. + */ + g_object_class_install_property (gobject_class, + PROP_IS_EXTENSION, + g_param_spec_boolean ("is-extension", + "is extension", + "if the event is called by an extension", + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + /** + * IBusExtensionEvent:params: + * + * Parameters to enable the extension in the #IBusExtensionEvent. + */ + g_object_class_install_property (gobject_class, + PROP_PARAMS, + g_param_spec_string ("params", + "params", + "Parameters to enable the extension", + "", + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_type_class_add_private (class, sizeof (IBusExtensionEventPrivate)); +} + +static void +ibus_extension_event_init (IBusExtensionEvent *event) +{ + event->priv = IBUS_EXTENSION_EVENT_GET_PRIVATE (event); + event->priv->version = IBUS_EXTENSION_EVENT_VERSION; +} + +static void +ibus_extension_event_destroy (IBusExtensionEvent *event) +{ + g_clear_pointer (&event->priv->name, g_free); + + IBUS_OBJECT_CLASS(ibus_extension_event_parent_class)-> + destroy (IBUS_OBJECT (event)); +} + +static void +ibus_extension_event_set_property (IBusExtensionEvent *event, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + IBusExtensionEventPrivate *priv = event->priv; + + switch (prop_id) { + case PROP_NAME: + g_free (priv->name); + priv->name = g_value_dup_string (value); + break; + case PROP_IS_ENABLED: + priv->is_enabled = g_value_get_boolean (value); + break; + case PROP_IS_EXTENSION: + priv->is_extension = g_value_get_boolean (value); + break; + case PROP_PARAMS: + priv->params = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (event, prop_id, pspec); + } +} + +static void +ibus_extension_event_get_property (IBusExtensionEvent *event, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + IBusExtensionEventPrivate *priv = event->priv; + switch (prop_id) { + case PROP_VERSION: + g_value_set_uint (value, priv->version); + break; + case PROP_NAME: + g_value_set_string (value, priv->name); + break; + case PROP_IS_ENABLED: + g_value_set_boolean (value, priv->is_enabled); + break; + case PROP_IS_EXTENSION: + g_value_set_boolean (value, priv->is_extension); + break; + case PROP_PARAMS: + g_value_set_string (value, priv->params); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (event, prop_id, pspec); + } +} + +static gboolean +ibus_extension_event_serialize (IBusExtensionEvent *event, + GVariantBuilder *builder) +{ + gboolean retval; + IBusExtensionEventPrivate *priv; + + retval = IBUS_SERIALIZABLE_CLASS (ibus_extension_event_parent_class)-> + serialize ((IBusSerializable *)event, builder); + g_return_val_if_fail (retval, FALSE); + /* End dict iter */ + + priv = event->priv; +#define NOTNULL(s) ((s) != NULL ? (s) : "") + /* If you will add a new property, you can append it at the end and + * you should not change the serialized order of name, longname, + * description, ... because the order is also used in other applications + * likes ibus-qt. */ + g_variant_builder_add (builder, "u", priv->version); + g_variant_builder_add (builder, "s", NOTNULL (priv->name)); + g_variant_builder_add (builder, "b", priv->is_enabled); + g_variant_builder_add (builder, "b", priv->is_extension); + g_variant_builder_add (builder, "s", NOTNULL (priv->params)); +#undef NOTNULL + + return TRUE; +} + +static gint +ibus_extension_event_deserialize (IBusExtensionEvent *event, + GVariant *variant) +{ + gint retval; + IBusExtensionEventPrivate *priv; + + retval = IBUS_SERIALIZABLE_CLASS (ibus_extension_event_parent_class)-> + deserialize ((IBusSerializable *)event, variant); + g_return_val_if_fail (retval, 0); + + priv = event->priv; + /* If you will add a new property, you can append it at the end and + * you should not change the serialized order of name, longname, + * description, ... because the order is also used in other applications + * likes ibus-qt. */ + g_variant_get_child (variant, retval++, "u", &priv->version); + ibus_g_variant_get_child_string (variant, retval++, + &priv->name); + g_variant_get_child (variant, retval++, "b", &priv->is_enabled); + g_variant_get_child (variant, retval++, "b", &priv->is_extension); + ibus_g_variant_get_child_string (variant, retval++, + &priv->params); + + return retval; +} + +static gboolean +ibus_extension_event_copy (IBusExtensionEvent *dest, + const IBusExtensionEvent *src) +{ + gboolean retval; + IBusExtensionEventPrivate *dest_priv = dest->priv; + IBusExtensionEventPrivate *src_priv = src->priv; + + retval = IBUS_SERIALIZABLE_CLASS (ibus_extension_event_parent_class)-> + copy ((IBusSerializable *)dest, (IBusSerializable *)src); + g_return_val_if_fail (retval, FALSE); + + dest_priv->version = src_priv->version; + dest_priv->name = g_strdup (src_priv->name); + dest_priv->is_enabled = src_priv->is_enabled; + dest_priv->is_extension = src_priv->is_extension; + dest_priv->params = g_strdup (src_priv->params); + return TRUE; +} + +IBusExtensionEvent * +ibus_extension_event_new (const gchar *first_property_name, + ...) +{ + va_list var_args; + IBusExtensionEvent *event; + + va_start (var_args, first_property_name); + event = (IBusExtensionEvent *) g_object_new_valist ( + IBUS_TYPE_EXTENSION_EVENT, + first_property_name, + var_args); + va_end (var_args); + g_assert (event->priv->version != 0); + return event; +} + +guint +ibus_extension_event_get_version (IBusExtensionEvent *event) +{ + g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), 0); + return event->priv->version; +} + +const gchar * +ibus_extension_event_get_name (IBusExtensionEvent *event) +{ + g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), ""); + return event->priv->name; +} + +gboolean +ibus_extension_event_is_enabled (IBusExtensionEvent *event) +{ + g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), FALSE); + return event->priv->is_enabled; +} + +gboolean +ibus_extension_event_is_extension (IBusExtensionEvent *event) +{ + g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), FALSE); + return event->priv->is_extension; +} + +const gchar * +ibus_extension_event_get_params (IBusExtensionEvent *event) +{ + g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), ""); + return event->priv->params; +} + + +static void ibus_x_event_class_init (IBusXEventClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); @@ -454,6 +794,7 @@ static void ibus_x_event_destroy (IBusXEvent *event) { g_clear_pointer (&event->priv->string, g_free); + g_clear_pointer (&event->priv->purpose, g_free); IBUS_OBJECT_CLASS(ibus_x_event_parent_class)->destroy (IBUS_OBJECT (event)); } diff --git a/src/ibusxevent.h b/src/ibusxevent.h index f35f14e4..d44cc8f4 100644 --- a/src/ibusxevent.h +++ b/src/ibusxevent.h @@ -29,8 +29,8 @@ /** * SECTION: ibusxevent - * @short_description: XEvent wrapper object - * @title: IBusXEvent + * @short_description: Extension Event wrapper object + * @title: IBusExtensionEvent * @stability: Unstable * * An IBusXEvent provides a wrapper of XEvent. @@ -45,25 +45,150 @@ */ /* define GOBJECT macros */ -#define IBUS_TYPE_X_EVENT \ +#define IBUS_TYPE_EXTENSION_EVENT \ + (ibus_extension_event_get_type ()) +#define IBUS_EXTENSION_EVENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + IBUS_TYPE_EXTENSION_EVENT, \ + IBusExtensionEvent)) +#define IBUS_EXTENSION_EVENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + IBUS_TYPE_EXTENSION_EVENT, \ + IBusExtensionEventClass)) +#define IBUS_IS_EXTENSION_EVENT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IBUS_TYPE_EXTENSION_EVENT)) +#define IBUS_IS_EXTENSION_EVENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), IBUS_TYPE_EXTENSION_EVENT)) +#define IBUS_EXTENSION_EVENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + IBUS_TYPE_EXTENSION_EVENT, \ + IBusExtensionEventClass)) + +#define IBUS_TYPE_X_EVENT \ (ibus_x_event_get_type ()) -#define IBUS_X_EVENT(obj) \ +#define IBUS_X_EVENT(obj) \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), IBUS_TYPE_X_EVENT, IBusXEvent)) -#define IBUS_X_EVENT_CLASS(klass) \ +#define IBUS_X_EVENT_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST ((klass), IBUS_TYPE_X_EVENT, IBusXEventClass)) -#define IBUS_IS_X_EVENT(obj) \ +#define IBUS_IS_X_EVENT(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IBUS_TYPE_X_EVENT)) -#define IBUS_IS_X_EVENT_CLASS(klass) \ +#define IBUS_IS_X_EVENT_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE ((klass), IBUS_TYPE_X_EVENT)) -#define IBUS_X_EVENT_GET_CLASS(obj) \ +#define IBUS_X_EVENT_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), IBUS_TYPE_X_EVENT, IBusXEventClass)) G_BEGIN_DECLS +typedef struct _IBusProcessKeyEventData IBusProcessKeyEventData; +typedef struct _IBusExtensionEvent IBusExtensionEvent; +typedef struct _IBusExtensionEventClass IBusExtensionEventClass; +typedef struct _IBusExtensionEventPrivate IBusExtensionEventPrivate; typedef struct _IBusXEvent IBusXEvent; typedef struct _IBusXEventClass IBusXEventClass; typedef struct _IBusXEventPrivate IBusXEventPrivate; +/** + * IBusProcessKeyEventData: + * + * IBuProcessKeyEventData properties. + */ +struct _IBusProcessKeyEventData { + /*< public >*/ + guint keyval; + guint keycode; + guint state; +}; + +/** + * IBusExtensionEvent: + * + * IBusExtensionEvent properties. + */ +struct _IBusExtensionEvent { + /*< private >*/ + IBusSerializable parent; + IBusExtensionEventPrivate *priv; + + /* instance members */ + /*< public >*/ +}; + +struct _IBusExtensionEventClass { + /*< private >*/ + IBusSerializableClass parent; + + /* class members */ + /*< public >*/ + + /*< private >*/ + /* padding */ + gpointer pdummy[10]; +}; + + +GType ibus_extension_event_get_type (void); + +/** + * ibus_extension_event_new: + * @first_property_name: Name of the first property. + * @...: the NULL-terminated arguments of the properties and values. + * + * Create a new #IBusExtensionEvent. + * + * Returns: A newly allocated #IBusExtensionEvent. E.g. + * ibus_extension_event_new ("name", "emoji", "is-enabled", TRUE, NULL); + */ +IBusExtensionEvent *ibus_extension_event_new (const gchar + *first_property_name, + ...); + +/** + * ibus_extension_event_get_version: + * @event: An #IBusExtensionEvent. + * + * Returns: Version of #IBusExtensionEvent + */ +guint ibus_extension_event_get_version (IBusExtensionEvent *event); + +/** + * ibus_extension_event_get_purpose: + * @event: An #IBusExtensionEvent. + * + * Returns: name of the extension for #IBusXEvent + */ +const gchar * ibus_extension_event_get_name (IBusExtensionEvent *event); + +/** + * ibus_extension_event_is_enabled: + * @event: An #IBusExtensionEvent. + * + * Returns: %TRUE if the extension is enabled for #IBusExtensionEvent + */ +gboolean ibus_extension_event_is_enabled (IBusExtensionEvent *event); + +/** + * ibus_extension_event_is_extension: + * @event: An #IBusExtensionEvent. + * + * Returns: %TRUE if the #IBusExtensionEvent is called by an extension. + * %FALSE if the #IBusExtensionEvent is called by an active engine or + * panel. + * If this value is %TRUE, the event is send to ibus-daemon, an active + * engine. If it's %FALSE, the event is sned to ibus-daemon, panels. + */ +gboolean ibus_extension_event_is_extension + (IBusExtensionEvent *event); + +/** + * ibus_extension_event_get_params: + * @event: An #IBusExtensionEvent. + * + * Returns: Parameters to enable the extension for #IBusXEvent + */ +const gchar * ibus_extension_event_get_params (IBusExtensionEvent *event); + + + typedef enum { IBUS_X_EVENT_NOTHING = -1, IBUS_X_EVENT_KEY_PRESS = 0, @@ -76,7 +201,7 @@ typedef enum { * IBusXEvent: * @type: event type * - * IBusEngine properties. + * IBusXEvent properties. */ struct _IBusXEvent { /*< private >*/ diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am index bf9f98d7..aaba7a4d 100644 --- a/ui/gtk3/Makefile.am +++ b/ui/gtk3/Makefile.am @@ -78,6 +78,7 @@ AM_VALAFLAGS = \ --pkg=ibus-1.0 \ --pkg=config \ --pkg=xi \ + --pkg=gdk-wayland \ --target-glib="$(VALA_TARGET_GLIB_VERSION)" \ $(NULL) @@ -176,6 +177,7 @@ ibus_ui_emojier_VALASOURCES = \ emojier.vala \ iconwidget.vala \ separator.vala \ + pango.vala \ $(NULL) ibus_ui_emojier_SOURCES = \ $(ibus_ui_emojier_VALASOURCES:.vala=.c) \ @@ -213,6 +215,7 @@ ibus_extension_gtk3_VALASOURCES = \ iconwidget.vala \ keybindingmanager.vala \ panelbinding.vala \ + pango.vala \ $(NULL) ibus_extension_gtk3_SOURCES = \ $(ibus_extension_gtk3_VALASOURCES:.vala=.c) \ diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala index 0c0865f1..cd98c9d7 100644 --- a/ui/gtk3/emojier.vala +++ b/ui/gtk3/emojier.vala @@ -226,43 +226,6 @@ public class IBusEmojier : Gtk.ApplicationWindow { } } } - private class ETitleLabelBox : Gtk.HeaderBar { - private Gtk.Label m_lang_label; - private Gtk.Label m_title_label; - - public ETitleLabelBox(string title) { - GLib.Object( - name : "IBusEmojierTitleLabelBox", - show_close_button: true, - decoration_layout: ":close", - title: title - ); - var vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); - set_custom_title(vbox); - m_title_label = new Gtk.Label(title); - m_title_label.get_style_context().add_class(Gtk.STYLE_CLASS_TITLE); - vbox.pack_start(m_title_label, true, false, 0); - m_lang_label = new Gtk.Label(null); - m_lang_label.get_style_context().add_class( - Gtk.STYLE_CLASS_SUBTITLE); - vbox.pack_start(m_lang_label, true, false, 0); - - var menu = new GLib.Menu(); - menu.append(_("Show emoji variants"), "win.variant"); - var menu_button = new Gtk.MenuButton(); - menu_button.set_direction(Gtk.ArrowType.NONE); - menu_button.set_valign(Gtk.Align.CENTER); - menu_button.set_menu_model(menu); - menu_button.set_tooltip_text(_("Menu")); - pack_end(menu_button); - } - public new void set_title(string title) { - m_title_label.set_text(title); - } - public void set_lang_label(string str) { - m_lang_label.set_text(str); - } - } private class LoadProgressObject : GLib.Object { public LoadProgressObject() { } @@ -275,6 +238,8 @@ public class IBusEmojier : Gtk.ApplicationWindow { BACKWARD, } + public const uint BUTTON_CLOSE_BUTTON = 1000; + private const uint EMOJI_GRID_PAGE = 10; private const string EMOJI_CATEGORY_FAVORITES = N_("Favorites"); private const string EMOJI_CATEGORY_OTHERS = N_("Others"); @@ -313,11 +278,19 @@ public class IBusEmojier : Gtk.ApplicationWindow { private static bool m_show_unicode = false; private static LoadProgressObject m_unicode_progress_object; private static bool m_loaded_unicode = false; + private static string m_warning_message = ""; private ThemedRGBA m_rgba; private Gtk.Box m_vbox; - private ETitleLabelBox m_title; private EEntry m_entry; + /* If emojier is emoji category list or Unicode category list, + * m_annotation is "" and preedit is also "". + * If emojier is candidate mode, m_annotation is an annotation and + * get_current_candidate() returns the current emoji. + * But the current preedit can be "" in candidate mode in case that + * Unicode candidate window has U+0000. + */ + private string m_annotation = ""; private string? m_backward; private int m_backward_index = -1; private EScrolledWindow? m_scrolled_window = null; @@ -326,8 +299,20 @@ public class IBusEmojier : Gtk.ApplicationWindow { private string m_input_context_path = ""; private GLib.MainLoop? m_loop; private string? m_result; - private string? m_unicode_point = null; + /* If m_candidate_panel_is_visible is true, emojier is candidate mode and + * the emoji lookup window is visible. + * If m_candidate_panel_is_visible is false, the emoji lookup window is + * not visible but the mode is not clear. + */ private bool m_candidate_panel_is_visible; + /* If m_candidate_panel_mode is true, emojier is candidate mode and + * it does not depend on whether the window is visible or not. + * I.E. the first candidate does not show the lookup window and the + * second one shows the window. + * If m_candidate_panel_mode is false, emojier is emoji category list or + * Unicode category list. + */ + private bool m_candidate_panel_mode; private int m_category_active_index = -1; private IBus.LookupTable m_lookup_table; private Gtk.Label[] m_candidates; @@ -337,23 +322,18 @@ public class IBusEmojier : Gtk.ApplicationWindow { protected static double m_mouse_x; protected static double m_mouse_y; private Gtk.ProgressBar m_unicode_progress_bar; + private uint m_unicode_progress_id; private Gtk.Label m_unicode_percent_label; private double m_unicode_percent; + private Gdk.Rectangle m_cursor_location; + private bool m_is_up_side_down = false; + private uint m_redraw_window_id; public signal void candidate_clicked(uint index, uint button, uint state); public IBusEmojier() { GLib.Object( - type : Gtk.WindowType.TOPLEVEL, - events : Gdk.EventMask.KEY_PRESS_MASK | - Gdk.EventMask.KEY_RELEASE_MASK | - Gdk.EventMask.BUTTON_PRESS_MASK | - Gdk.EventMask.BUTTON_RELEASE_MASK, - window_position : Gtk.WindowPosition.CENTER, - icon_name: "ibus-setup", - accept_focus : true, - resizable : true, - focus_visible : true + type : Gtk.WindowType.POPUP ); // GLib.ActionEntry accepts const variables only. @@ -363,6 +343,9 @@ public class IBusEmojier : Gtk.ApplicationWindow { new GLib.Variant.boolean(m_show_emoji_variant)); action.activate.connect(check_action_variant_cb); add_action(action); + action = new GLib.SimpleAction("close", null); + action.activate.connect(action_close_cb); + add_action(action); if (m_current_lang_id == null) m_current_lang_id = "en"; if (m_emoji_font_family == null) @@ -448,14 +431,12 @@ public class IBusEmojier : Gtk.ApplicationWindow { css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); - m_title = new ETitleLabelBox(_("Emoji Choice")); - set_titlebar(m_title); m_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); add(m_vbox); m_entry = new EEntry(); m_entry.set_placeholder_text(_("Type annotation or choose emoji")); - m_vbox.add(m_entry); + //m_vbox.add(m_entry); m_entry.changed.connect(() => { update_candidate_window(); }); @@ -480,10 +461,16 @@ public class IBusEmojier : Gtk.ApplicationWindow { m_loop.quit(); }); + size_allocate.connect((w, a) => { + adjust_window_position(); + }); + candidate_clicked.connect((i, b, s) => { - candidate_panel_select_index(i); + if (m_input_context_path != "") + candidate_panel_select_index(i, b); }); + if (m_annotation_to_emojis_dict == null) { reload_emoji_dict(); } @@ -814,6 +801,12 @@ public class IBusEmojier : Gtk.ApplicationWindow { private void remove_all_children() { + if (m_list_box != null) { + foreach (Gtk.Widget w in m_list_box.get_children()) { + w.destroy(); + } + m_list_box = null; + } foreach (Gtk.Widget w in m_vbox.get_children()) { if (w.name == "IBusEmojierEntry" || w.name == "IBusEmojierTitleLabelBox") { @@ -824,15 +817,40 @@ public class IBusEmojier : Gtk.ApplicationWindow { } + private void clamp_page() { + Gtk.ListBoxRow row; + if (m_category_active_index >= 0) { + row = m_list_box.get_row_at_index(m_category_active_index); + m_list_box.select_row(row); + } else { + row = m_list_box.get_row_at_index(0); + } + Gtk.Allocation alloc = { 0, 0, 0, 0 }; + row.get_allocation(out alloc); + var adjustment = m_scrolled_window.get_vadjustment(); + adjustment.clamp_page(alloc.y, alloc.y + alloc.height); + return_val_if_fail(m_category_active_index >= 0, false); + m_lookup_table.set_cursor_pos((uint)m_category_active_index); + } + + private void show_category_list() { + // Do not call remove_all_children() to work adjustment.clamp_page() + // with PageUp/Down. + // After show_candidate_panel() is called, m_category_active_index + // is saved for Escape key but m_list_box is null by + // remove_all_children(). + if (m_category_active_index >= 0 && m_list_box != null) { + var row = m_list_box.get_row_at_index(m_category_active_index); + m_list_box.select_row(row); + return; + } + if (m_category_active_index < 0) + m_category_active_index = 0; remove_all_children(); m_scrolled_window = new EScrolledWindow(); set_fixed_size(); - m_title.set_title(_("Emoji Choice")); - string language = - IBus.get_language_name(m_current_lang_id); - m_title.set_lang_label(language); m_vbox.add(m_scrolled_window); Gtk.Viewport viewport = new Gtk.Viewport(null, null); m_scrolled_window.add(viewport); @@ -842,53 +860,21 @@ public class IBusEmojier : Gtk.ApplicationWindow { Gtk.Adjustment adjustment = m_scrolled_window.get_vadjustment(); m_list_box.set_adjustment(adjustment); m_list_box.row_activated.connect((box, gtkrow) => { - m_category_active_index = -1; + m_category_active_index = gtkrow.get_index(); EBoxRow row = gtkrow as EBoxRow; show_emoji_for_category(row.text); + show_all(); }); - uint n = 0; - if (m_favorites.length > 0) { - EBoxRow row = new EBoxRow(EMOJI_CATEGORY_FAVORITES); - EPaddedLabelBox widget = - new EPaddedLabelBox(_(EMOJI_CATEGORY_FAVORITES), - Gtk.Align.CENTER); - row.add(widget); - m_list_box.add(row); - if (n++ == m_category_active_index) - m_list_box.select_row(row); - } - GLib.List<unowned string> categories = - m_category_to_emojis_dict.get_keys(); - // FIXME: How to cast GLib.CompareFunc<string> to strcmp? - categories.sort((a, b) => { - if (a == EMOJI_CATEGORY_OTHERS && b != EMOJI_CATEGORY_OTHERS) - return 1; - else if (a != EMOJI_CATEGORY_OTHERS && b == EMOJI_CATEGORY_OTHERS) - return -1; - return GLib.strcmp(_(a), _(b)); - }); - foreach (unowned string category in categories) { - // "Others" category includes next unicode chars and fonts do not support - // the base and varints yet. - if (category == EMOJI_CATEGORY_OTHERS) - continue; + uint ncandidates = m_lookup_table.get_number_of_candidates(); + for (uint i = 0; i < ncandidates; i++) { + string category = m_lookup_table.get_candidate(i).text; EBoxRow row = new EBoxRow(category); EPaddedLabelBox widget = new EPaddedLabelBox(_(category), Gtk.Align.CENTER); row.add(widget); m_list_box.add(row); - if (n++ == m_category_active_index) - m_list_box.select_row(row); - } - if (m_unicode_block_list.length() > 0) { - EBoxRow row = new EBoxRow(EMOJI_CATEGORY_UNICODE); - EPaddedLabelBox widget = - new EPaddedLabelBox(_(EMOJI_CATEGORY_UNICODE), - Gtk.Align.CENTER); - row.add(widget); - m_list_box.add(row); - if (n++ == m_category_active_index) + if (i == m_category_active_index) m_list_box.select_row(row); } @@ -903,6 +889,7 @@ public class IBusEmojier : Gtk.ApplicationWindow { private void show_emoji_for_category(string category) { if (category == EMOJI_CATEGORY_FAVORITES) { m_lookup_table.clear(); + m_candidate_panel_mode = true; foreach (unowned string favorate in m_favorites) { IBus.Text text = new IBus.Text.from_string(favorate); m_lookup_table.append_candidate(text); @@ -911,25 +898,26 @@ public class IBusEmojier : Gtk.ApplicationWindow { } else if (category == EMOJI_CATEGORY_UNICODE) { m_category_active_index = -1; m_show_unicode = true; - show_unicode_blocks(); + update_unicode_blocks(); return; } else { unowned GLib.SList<unowned string> emojis = m_category_to_emojis_dict.lookup(category); m_lookup_table.clear(); + m_candidate_panel_mode = true; foreach (unowned string emoji in emojis) { IBus.Text text = new IBus.Text.from_string(emoji); m_lookup_table.append_candidate(text); } m_backward = category; } + m_annotation = m_lookup_table.get_candidate(0).text; // Restore the cursor position before the special table of // emoji variants is shown. if (m_backward_index >= 0) { m_lookup_table.set_cursor_pos((uint)m_backward_index); m_backward_index = -1; } - show_candidate_panel(); } @@ -940,18 +928,28 @@ public class IBusEmojier : Gtk.ApplicationWindow { IBus.Text text = new IBus.Text.from_string(emoji); m_lookup_table.append_candidate(text); } - show_candidate_panel(); } private void show_unicode_blocks() { + // Do not call remove_all_children() to work adjustment.clamp_page() + // with PageUp/Down. + // After show_candidate_panel() is called, m_category_active_index + // is saved for Escape key but m_list_box is null by + // remove_all_children(). + if (m_category_active_index >= 0 && m_list_box != null) { + var row = m_list_box.get_row_at_index(m_category_active_index); + m_list_box.select_row(row); + return; + } + if (m_category_active_index < 0) + m_category_active_index = 0; m_show_unicode = true; if (m_default_window_width == 0 && m_default_window_height == 0) get_size(out m_default_window_width, out m_default_window_height); remove_all_children(); set_fixed_size(); - m_title.set_title(_("Unicode Choice")); EPaddedLabelBox label = new EPaddedLabelBox(_("Bring back emoji choice"), Gtk.Align.CENTER, @@ -964,10 +962,10 @@ public class IBusEmojier : Gtk.ApplicationWindow { m_category_active_index = -1; m_show_unicode = false; hide_candidate_panel(); + show_all(); return true; }); m_scrolled_window = new EScrolledWindow(); - m_title.set_lang_label(""); m_vbox.add(m_scrolled_window); Gtk.Viewport viewport = new Gtk.Viewport(null, null); m_scrolled_window.add(viewport); @@ -977,9 +975,10 @@ public class IBusEmojier : Gtk.ApplicationWindow { Gtk.Adjustment adjustment = m_scrolled_window.get_vadjustment(); m_list_box.set_adjustment(adjustment); m_list_box.row_activated.connect((box, gtkrow) => { - m_category_active_index = -1; + m_category_active_index = gtkrow.get_index(); EBoxRow row = gtkrow as EBoxRow; show_unicode_for_block(row.text); + show_candidate_panel(); }); uint n = 0; @@ -1007,44 +1006,18 @@ public class IBusEmojier : Gtk.ApplicationWindow { m_list_box.unselect_all(); m_list_box.invalidate_filter(); m_list_box.set_selection_mode(Gtk.SelectionMode.SINGLE); + Gtk.ListBoxRow row = m_list_box.get_row_at_index((int)n - 1); + + // If clamp_page() would be called without the allocation signal, + // the jumping page could be failed when returns from + // show_unicode_for_block() with Escape key. + row.size_allocate.connect((w, a) => { + clamp_page(); + }); } + private void show_unicode_for_block(string block_name) { - if (!m_loaded_unicode) { - remove_all_children(); - set_fixed_size(); - m_unicode_progress_bar = new Gtk.ProgressBar(); - m_unicode_progress_bar.set_ellipsize(Pango.EllipsizeMode.MIDDLE); - m_unicode_progress_bar.set_halign(Gtk.Align.CENTER); - m_unicode_progress_bar.set_valign(Gtk.Align.CENTER); - m_vbox.add(m_unicode_progress_bar); - m_unicode_progress_bar.show(); - var hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5); - hbox.set_halign(Gtk.Align.CENTER); - hbox.set_valign(Gtk.Align.CENTER); - m_vbox.add(hbox); - var label = new Gtk.Label(_("Loading a Unicode dictionary:")); - hbox.pack_start(label, false, true, 0); - m_unicode_percent_label = new Gtk.Label(""); - hbox.pack_start(m_unicode_percent_label, false, true, 0); - hbox.show_all(); - - m_unicode_progress_object.deserialize_unicode.connect((i, n) => { - m_unicode_percent = (double)i / n; - }); - GLib.Timeout.add(100, () => { - m_unicode_progress_bar.set_fraction(m_unicode_percent); - m_unicode_percent_label.set_text( - "%.0f%%\n".printf(m_unicode_percent * 100)); - m_unicode_progress_bar.show(); - m_unicode_percent_label.show(); - if (m_loaded_unicode) { - show_unicode_for_block(block_name); - } - return !m_loaded_unicode; - }); - return; - } unichar start = 0; unichar end = 0; foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) { @@ -1055,6 +1028,7 @@ public class IBusEmojier : Gtk.ApplicationWindow { } } m_lookup_table.clear(); + m_candidate_panel_mode = true; for (unichar ch = start; ch < end; ch++) { unowned IBus.UnicodeData? data = m_unicode_to_data_dict.lookup(ch); @@ -1064,7 +1038,7 @@ public class IBusEmojier : Gtk.ApplicationWindow { m_lookup_table.append_candidate(text); } m_backward = block_name; - show_candidate_panel(); + m_annotation = m_lookup_table.get_candidate(0).text; } @@ -1091,6 +1065,41 @@ public class IBusEmojier : Gtk.ApplicationWindow { prev_button.set_relief(Gtk.ReliefStyle.NONE); prev_button.set_tooltip_text(_("Page Up")); + var menu = new GLib.Menu(); + menu.append(_("Show emoji variants"), "win.variant"); + menu.append(_("Close"), "win.close"); + var menu_button = new Gtk.MenuButton(); + menu_button.set_direction(Gtk.ArrowType.NONE); + menu_button.set_valign(Gtk.Align.CENTER); + menu_button.set_menu_model(menu); + menu_button.set_relief(Gtk.ReliefStyle.NONE); + menu_button.set_tooltip_text(_("Menu")); + + IBus.Text text = this.get_title_text(); + Pango.AttrList attrs = get_pango_attr_list_from_ibus_text(text); + Gtk.Label title_label = new Gtk.Label(text.get_text()); + title_label.set_attributes(attrs); + + Gtk.Button? warning_button = null; + if (m_warning_message != "") { + warning_button = new Gtk.Button(); + warning_button.set_tooltip_text( + _("Click to view a warning message")); + warning_button.set_image(new Gtk.Image.from_icon_name( + "dialog-warning", + Gtk.IconSize.MENU)); + warning_button.set_relief(Gtk.ReliefStyle.NONE); + warning_button.clicked.connect(() => { + Gtk.Label warning_label = new Gtk.Label(m_warning_message); + warning_label.set_line_wrap(true); + warning_label.set_max_width_chars(40); + Gtk.Popover popover = new Gtk.Popover(warning_button); + popover.add(warning_label); + popover.show_all(); + popover.popup(); + }); + } + Gtk.Box buttons_hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0); Gtk.Label state_label = new Gtk.Label(null); state_label.set_size_request(10, -1); @@ -1099,14 +1108,55 @@ public class IBusEmojier : Gtk.ApplicationWindow { buttons_hbox.pack_start(state_label, false, true, 0); buttons_hbox.pack_start(prev_button, false, false, 0); buttons_hbox.pack_start(next_button, false, false, 0); + buttons_hbox.pack_start(title_label, false, false, 0); + if (warning_button != null) + buttons_hbox.pack_start(warning_button, false, false, 0); + buttons_hbox.pack_end(menu_button, false, false, 0); m_vbox.pack_start(buttons_hbox, false, false, 0); buttons_hbox.show_all(); } - private bool check_unicode_point() { - string annotation = m_entry.get_text(); - m_unicode_point = null; + private void show_unicode_progress_bar() { + m_unicode_progress_bar = new Gtk.ProgressBar(); + m_unicode_progress_bar.set_ellipsize(Pango.EllipsizeMode.MIDDLE); + m_unicode_progress_bar.set_halign(Gtk.Align.CENTER); + m_unicode_progress_bar.set_valign(Gtk.Align.CENTER); + m_vbox.add(m_unicode_progress_bar); + m_unicode_progress_bar.show(); + var hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5); + hbox.set_halign(Gtk.Align.CENTER); + hbox.set_valign(Gtk.Align.CENTER); + m_vbox.add(hbox); + var label = new Gtk.Label(_("Loading a Unicode dictionary:")); + hbox.pack_start(label, false, true, 0); + m_unicode_percent_label = new Gtk.Label(""); + hbox.pack_start(m_unicode_percent_label, false, true, 0); + hbox.show_all(); + + m_unicode_progress_object.deserialize_unicode.connect((i, n) => { + m_unicode_percent = (double)i / n; + }); + if (m_unicode_progress_id > 0) { + GLib.Source.remove(m_unicode_progress_id); + } + m_unicode_progress_id = GLib.Timeout.add(100, () => { + m_unicode_progress_id = 0; + m_unicode_progress_bar.set_fraction(m_unicode_percent); + m_unicode_percent_label.set_text( + "%.0f%%\n".printf(m_unicode_percent * 100)); + m_unicode_progress_bar.show(); + m_unicode_percent_label.show(); + if (m_loaded_unicode) { + show_candidate_panel(); + } + return !m_loaded_unicode; + }); + } + + + private static string? check_unicode_point(string annotation) { + string unicode_point = null; // Add "0x" because uint64.ascii_strtoull() is not accessible // and need to use uint64.parse() var buff = new GLib.StringBuilder("0x"); @@ -1114,33 +1164,31 @@ public class IBusEmojier : Gtk.ApplicationWindow { for (int i = 0; i < annotation.char_count(); i++) { unichar ch = annotation.get_char(i); if (ch == 0) - return false; + return null; if (ch.isspace()) { unichar code = (unichar)uint64.parse(buff.str); buff.assign("0x"); if (!code.validate()) - return false; + return null; retval.append(code.to_string()); continue; } if (!ch.isxdigit()) - return false; + return null; buff.append_unichar(ch); } unichar code = (unichar)uint64.parse(buff.str); if (!code.validate()) - return false; + return null; retval.append(code.to_string()); - m_unicode_point = retval.str; - if (m_unicode_point == null) - return true; - IBus.Text text = new IBus.Text.from_string(m_unicode_point); - m_lookup_table.append_candidate(text); - return true; + unicode_point = retval.str; + if (unicode_point == null) + return null; + return unicode_point; } - private GLib.SList<string>? + private static GLib.SList<string>? lookup_emojis_from_annotation(string annotation) { GLib.SList<string>? total_emojis = null; unowned GLib.SList<string>? sub_emojis = null; @@ -1221,19 +1269,19 @@ public class IBusEmojier : Gtk.ApplicationWindow { return total_emojis; } + private void update_candidate_window() { - string annotation = m_entry.get_text(); + string annotation = m_annotation; if (annotation.length == 0) { - hide_candidate_panel(); m_backward = null; return; } + m_lookup_table.clear(); + m_category_active_index = -1; if (annotation.length > m_emoji_max_seq_len) { - hide_candidate_panel(); return; } - // Call check_unicode_point() to get m_unicode_point - check_unicode_point(); + string? unicode_point = check_unicode_point(annotation); GLib.SList<string>? total_emojis = lookup_emojis_from_annotation(annotation); if (total_emojis == null) { @@ -1246,18 +1294,75 @@ public class IBusEmojier : Gtk.ApplicationWindow { annotation = annotation.down(); total_emojis = lookup_emojis_from_annotation(annotation); } - if (total_emojis == null && m_unicode_point == null) { - hide_candidate_panel(); + if (total_emojis == null && unicode_point == null) { return; } - m_lookup_table.clear(); - // Call check_unicode_point() to update m_lookup_table - check_unicode_point(); + if (unicode_point != null) { + IBus.Text text = new IBus.Text.from_string(unicode_point); + m_lookup_table.append_candidate(text); + } foreach (unowned string emoji in total_emojis) { IBus.Text text = new IBus.Text.from_string(emoji); m_lookup_table.append_candidate(text); } - show_candidate_panel(); + m_candidate_panel_is_visible = + (m_lookup_table.get_number_of_candidates() > 0) ? true : false; + m_candidate_panel_mode = true; + } + + + private void update_category_list() { + // Always update m_lookup_table even if the contents are same + // because m_category_active_index needs to be kept after + // bring back this API from show_emoji_for_category(). + reset_window_mode(); + m_lookup_table.clear(); + IBus.Text text; + if (m_favorites.length > 0) { + text = new IBus.Text.from_string(EMOJI_CATEGORY_FAVORITES); + m_lookup_table.append_candidate(text); + } + GLib.List<unowned string> categories = + m_category_to_emojis_dict.get_keys(); + // FIXME: How to cast GLib.CompareFunc<string> to strcmp? + categories.sort((a, b) => { + if (a == EMOJI_CATEGORY_OTHERS && b != EMOJI_CATEGORY_OTHERS) + return 1; + else if (a != EMOJI_CATEGORY_OTHERS && b == EMOJI_CATEGORY_OTHERS) + return -1; + return GLib.strcmp(_(a), _(b)); + }); + foreach (unowned string category in categories) { + // "Others" category includes next unicode chars and fonts do not + // support the base and varints yet. + if (category == EMOJI_CATEGORY_OTHERS) + continue; + text = new IBus.Text.from_string(category); + m_lookup_table.append_candidate(text); + } + if (m_unicode_block_list.length() > 0) { + text = new IBus.Text.from_string(EMOJI_CATEGORY_UNICODE); + m_lookup_table.append_candidate(text); + } + // Do not set m_category_active_index to 0 here so that + // show_category_list() handles it. + } + + + private void update_unicode_blocks() { + // Always update m_lookup_table even if the contents are same + // because m_category_active_index needs to be kept after + // bring back this API from show_emoji_for_category(). + reset_window_mode(); + m_lookup_table.clear(); + m_show_unicode = true; + foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) { + string name = block.get_name(); + IBus.Text text = new IBus.Text.from_string(name); + m_lookup_table.append_candidate(text); + } + // Do not set m_category_active_index to 0 here so that + // show_unicode_blocks() handles it. } @@ -1283,27 +1388,27 @@ public class IBusEmojier : Gtk.ApplicationWindow { uint page_size = m_lookup_table.get_page_size(); uint ncandidates = m_lookup_table.get_number_of_candidates(); uint cursor = m_lookup_table.get_cursor_pos(); - uint page_start_pos = cursor / page_size * page_size; uint page_end_pos = uint.min(page_start_pos + page_size, ncandidates); + Gtk.Button? backward_button = null; if (m_backward != null) { - string backward_desc = - "%s (%u / %u)".printf(_(m_backward), cursor, ncandidates - 1); + string backward_desc = _(m_backward); EPaddedLabelBox label = new EPaddedLabelBox(backward_desc, Gtk.Align.CENTER, TravelDirection.BACKWARD); - Gtk.Button button = new Gtk.Button(); - button.add(label); - m_vbox.add(button); - button.show_all(); - button.button_press_event.connect((w, e) => { + backward_button = new Gtk.Button(); + backward_button.add(label); + backward_button.button_press_event.connect((w, e) => { // Bring back to emoji candidate panel in case // m_show_emoji_variant is enabled and shows variants. - if (m_backward_index >= 0 && m_backward != null) + if (m_backward_index >= 0 && m_backward != null) { show_emoji_for_category(m_backward); - else + show_candidate_panel(); + } else { hide_candidate_panel(); + show_all(); + } return true; }); } @@ -1385,34 +1490,60 @@ public class IBusEmojier : Gtk.ApplicationWindow { } if (n > 0) { m_candidate_panel_is_visible = true; - show_arrow_buttons(); - m_vbox.add(grid); - grid.show_all(); - string text = m_lookup_table.get_candidate(cursor).text; - unowned IBus.EmojiData? data = m_emoji_to_data_dict.lookup(text); - if (data != null) { - show_emoji_description(data, text); - return; + if (!m_is_up_side_down) { + show_arrow_buttons(); + if (backward_button != null) { + m_vbox.add(backward_button); + backward_button.show_all(); + } + m_vbox.add(grid); + grid.show_all(); + show_description(); + if (!m_loaded_unicode) + show_unicode_progress_bar(); } - if (text.char_count() <= 1) { - unichar code = text.get_char(); - unowned IBus.UnicodeData? udata = - m_unicode_to_data_dict.lookup(code); - if (udata != null) { - show_unicode_description(udata, text); - return; + if (m_is_up_side_down) { + if (!m_loaded_unicode) + show_unicode_progress_bar(); + show_description(); + m_vbox.add(grid); + grid.show_all(); + if (backward_button != null) { + m_vbox.add(backward_button); + backward_button.show_all(); } + show_arrow_buttons(); } - EPaddedLabelBox widget = new EPaddedLabelBox( - _("Description: %s").printf(_("None")), - Gtk.Align.START); - m_vbox.add(widget); - widget.show_all(); - show_code_point_description(text); } } + private void show_description() { + uint cursor = m_lookup_table.get_cursor_pos(); + string text = m_lookup_table.get_candidate(cursor).text; + unowned IBus.EmojiData? data = m_emoji_to_data_dict.lookup(text); + if (data != null) { + show_emoji_description(data, text); + return; + } + if (text.char_count() <= 1) { + unichar code = text.get_char(); + unowned IBus.UnicodeData? udata = + m_unicode_to_data_dict.lookup(code); + if (udata != null) { + show_unicode_description(udata, text); + return; + } + } + EPaddedLabelBox widget = new EPaddedLabelBox( + _("Description: %s").printf(_("None")), + Gtk.Align.START); + m_vbox.add(widget); + widget.show_all(); + show_code_point_description(text); + } + + private void show_emoji_description(IBus.EmojiData data, string text) { unowned string description = data.get_description(); @@ -1473,14 +1604,17 @@ public class IBusEmojier : Gtk.ApplicationWindow { private void hide_candidate_panel() { + hide(); m_enter_notify_enable = true; - m_candidate_panel_is_visible = false; - if (m_loop.is_running()) { - if (m_show_unicode) - show_unicode_blocks(); - else - show_category_list(); - } + m_annotation = ""; + // Call remove_all_children() instead of show_category_list() + // so that show_category_list do not remove children with + // PageUp/PageDown. + remove_all_children(); + if (m_show_unicode) + update_unicode_blocks(); + else + update_category_list(); } @@ -1498,20 +1632,34 @@ public class IBusEmojier : Gtk.ApplicationWindow { } - private void candidate_panel_select_index(uint index) { + private void candidate_panel_select_index(uint index, + uint button) { + if (button == BUTTON_CLOSE_BUTTON) { + hide(); + if (m_candidate_panel_mode && + m_lookup_table.get_number_of_candidates() > 0) { + // Call remove_all_children() instead of show_category_list() + // so that show_category_list do not remove children with + // PageUp/PageDown. + remove_all_children(); + } + m_result = ""; + return; + } string text = m_lookup_table.get_candidate(index).text; unowned GLib.SList<string>? emojis = m_emoji_to_emoji_variants_dict.lookup(text); if (m_show_emoji_variant && emojis != null && m_backward_index < 0) { show_emoji_variants(emojis); + show_all(); } else { m_result = text; - m_loop.quit(); - hide_candidate_panel(); + hide(); } } + private void candidate_panel_cursor_down() { enter_notify_disable_with_timer(); uint ncandidates = m_lookup_table.get_number_of_candidates(); @@ -1523,7 +1671,6 @@ public class IBusEmojier : Gtk.ApplicationWindow { } else { m_lookup_table.set_cursor_pos(0); } - show_candidate_panel(); } @@ -1541,7 +1688,6 @@ public class IBusEmojier : Gtk.ApplicationWindow { } else { m_lookup_table.set_cursor_pos(0); } - show_candidate_panel(); } @@ -1558,7 +1704,9 @@ public class IBusEmojier : Gtk.ApplicationWindow { return page_num; } + private bool category_list_cursor_move(uint keyval) { + return_val_if_fail (m_list_box != null, false); GLib.List<weak Gtk.Widget> list = m_list_box.get_children(); int length = (int)list.length(); if (length == 0) @@ -1600,32 +1748,37 @@ public class IBusEmojier : Gtk.ApplicationWindow { var row = m_list_box.get_selected_row(); if (row != null) m_list_box.unselect_row(row); - if (m_category_active_index >= 0) { - row = m_list_box.get_row_at_index(m_category_active_index); - m_list_box.select_row(row); - } else { - row = m_list_box.get_row_at_index(0); - } - Gtk.Allocation alloc = { 0, 0, 0, 0 }; - row.get_allocation(out alloc); - var adjustment = m_scrolled_window.get_vadjustment(); - adjustment.clamp_page(alloc.y, alloc.y + alloc.height); + clamp_page (); return true; } - private bool key_press_cursor_horizontal(uint keyval, - uint modifiers) { + public bool has_variants(uint index) { + if (index >= m_lookup_table.get_number_of_candidates()) + return false; + string text = m_lookup_table.get_candidate(index).text; + unowned GLib.SList<string>? emojis = + m_emoji_to_emoji_variants_dict.lookup(text); + if (m_show_emoji_variant && emojis != null && + m_backward_index < 0) { + show_emoji_variants(emojis); + return true; + } + return false; + } + + + public bool key_press_cursor_horizontal(uint keyval, + uint modifiers) { assert (keyval == Gdk.Key.Left || keyval == Gdk.Key.Right); - uint ncandidates = m_lookup_table.get_number_of_candidates(); - if (m_candidate_panel_is_visible && ncandidates > 1) { + if (m_candidate_panel_mode && + m_lookup_table.get_number_of_candidates() > 0) { enter_notify_disable_with_timer(); if (keyval == Gdk.Key.Left) m_lookup_table.cursor_up(); else if (keyval == Gdk.Key.Right) m_lookup_table.cursor_down(); - show_candidate_panel(); } else if (m_entry.get_text().length > 0) { int step = 0; if (keyval == Gdk.Key.Left) @@ -1650,8 +1803,8 @@ public class IBusEmojier : Gtk.ApplicationWindow { } - private bool key_press_cursor_vertical(uint keyval, - uint modifiers) { + public bool key_press_cursor_vertical(uint keyval, + uint modifiers) { assert (keyval == Gdk.Key.Down || keyval == Gdk.Key.Up || keyval == Gdk.Key.Page_Down || keyval == Gdk.Key.Page_Up); @@ -1661,8 +1814,8 @@ public class IBusEmojier : Gtk.ApplicationWindow { else if (keyval == Gdk.Key.Up) keyval = Gdk.Key.Page_Up; } - uint ncandidates = m_lookup_table.get_number_of_candidates(); - if (m_candidate_panel_is_visible && ncandidates > 1) { + if ((m_candidate_panel_is_visible || m_annotation.length > 0) + && m_lookup_table.get_number_of_candidates() > 0) { switch (keyval) { case Gdk.Key.Down: candidate_panel_cursor_down(); @@ -1673,12 +1826,10 @@ public class IBusEmojier : Gtk.ApplicationWindow { case Gdk.Key.Page_Down: enter_notify_disable_with_timer(); m_lookup_table.page_down(); - show_candidate_panel(); break; case Gdk.Key.Page_Up: enter_notify_disable_with_timer(); m_lookup_table.page_up(); - show_candidate_panel(); break; } } else { @@ -1688,19 +1839,18 @@ public class IBusEmojier : Gtk.ApplicationWindow { } - private bool key_press_cursor_home_end(uint keyval, - uint modifiers) { + public bool key_press_cursor_home_end(uint keyval, + uint modifiers) { assert (keyval == Gdk.Key.Home || keyval == Gdk.Key.End); uint ncandidates = m_lookup_table.get_number_of_candidates(); - if (m_candidate_panel_is_visible && ncandidates > 1) { + if (m_candidate_panel_mode && ncandidates > 0) { enter_notify_disable_with_timer(); if (keyval == Gdk.Key.Home) { m_lookup_table.set_cursor_pos(0); } else if (keyval == Gdk.Key.End) { m_lookup_table.set_cursor_pos(ncandidates - 1); } - show_candidate_panel(); return true; } if (m_entry.get_text().length > 0) { @@ -1717,44 +1867,41 @@ public class IBusEmojier : Gtk.ApplicationWindow { ? true : false); return true; } - if (!m_candidate_panel_is_visible) - return category_list_cursor_move(keyval); - return false; + return category_list_cursor_move(keyval); } - private bool key_press_escape() { + public bool key_press_escape() { if (m_show_unicode) { - if (m_candidate_panel_is_visible) { - m_candidate_panel_is_visible = false; - show_unicode_blocks(); - return true; - } else { + if (!m_candidate_panel_is_visible) { m_show_unicode = false; m_category_active_index = -1; - hide_candidate_panel(); - return true; } + hide_candidate_panel(); + return true; } else if (m_backward_index >= 0 && m_backward != null) { show_emoji_for_category(m_backward); return true; - } else if (m_candidate_panel_is_visible) { - hide_candidate_panel(); - return true; - } else if (m_entry.get_text().length == 0) { - m_loop.quit(); + } else if (m_candidate_panel_is_visible && m_backward != null) { hide_candidate_panel(); return true; } - m_entry.delete_text(0, -1); - return true; + hide(); + if (m_candidate_panel_mode && + m_lookup_table.get_number_of_candidates() > 0) { + // Call remove_all_children() instead of show_category_list() + // so that show_category_list do not remove children with + // PageUp/PageDown. + remove_all_children(); + } + return false; } - private bool key_press_enter() { + public bool key_press_enter() { if (m_candidate_panel_is_visible) { uint index = m_lookup_table.get_cursor_pos(); - candidate_panel_select_index(index); + return has_variants(index); } else if (m_category_active_index >= 0) { Gtk.ListBoxRow gtkrow = m_list_box.get_selected_row(); EBoxRow row = gtkrow as EBoxRow; @@ -1789,13 +1936,111 @@ public class IBusEmojier : Gtk.ApplicationWindow { } + private Gdk.Rectangle get_monitor_geometry() { + Gdk.Rectangle monitor_area = { 0, }; + + // Use get_monitor_geometry() instead of get_monitor_area(). + // get_monitor_area() excludes docks, but the lookup window should be + // shown over them. +#if VALA_0_34 + Gdk.Monitor monitor = Gdk.Display.get_default().get_monitor_at_point( + m_cursor_location.x, + m_cursor_location.y); + monitor_area = monitor.get_geometry(); +#else + Gdk.Screen screen = Gdk.Screen.get_default(); + int monitor_num = screen.get_monitor_at_point(m_cursor_location.x, + m_cursor_location.y); + screen.get_monitor_geometry(monitor_num, out monitor_area); +#endif + return monitor_area; + } + + + private void adjust_window_position() { + Gdk.Point cursor_right_bottom = { + m_cursor_location.x + m_cursor_location.width, + m_cursor_location.y + m_cursor_location.height + }; + + Gtk.Allocation allocation; + get_allocation(out allocation); + Gdk.Point window_right_bottom = { + cursor_right_bottom.x + allocation.width, + cursor_right_bottom.y + allocation.height + }; + + Gdk.Rectangle monitor_area = get_monitor_geometry(); + int monitor_right = monitor_area.x + monitor_area.width; + int monitor_bottom = monitor_area.y + monitor_area.height; + + int x, y; + if (window_right_bottom.x > monitor_right) + x = monitor_right - allocation.width; + else + x = cursor_right_bottom.x; + if (x < 0) + x = 0; + + bool changed = false; + if (window_right_bottom.y > monitor_bottom) { + y = m_cursor_location.y - allocation.height; + // Do not up side down in Wayland + if (m_input_context_path == "") { + changed = (m_is_up_side_down == false); + m_is_up_side_down = true; + } else { + changed = (m_is_up_side_down == true); + m_is_up_side_down = false; + } + } else { + y = cursor_right_bottom.y; + changed = (m_is_up_side_down == true); + m_is_up_side_down = false; + } + if (y < 0) + y = 0; + + move(x, y); + if (changed) { + if (m_redraw_window_id > 0) + GLib.Source.remove(m_redraw_window_id); + m_redraw_window_id = GLib.Timeout.add(100, () => { + m_redraw_window_id = 0; + this.show_all(); + return false; + }); + } + } + + +#if 0 + private void check_action_variant_cb(Gtk.MenuItem item) { + Gtk.CheckMenuItem check = item as Gtk.CheckMenuItem; + m_show_emoji_variant = check.get_active(); + // Redraw emoji candidate panel for m_show_emoji_variant + if (m_candidate_panel_is_visible) { + // DOTO: queue_draw() does not effect at the real time. + this.queue_draw(); + } + } +#else private void check_action_variant_cb(GLib.SimpleAction action, GLib.Variant? parameter) { m_show_emoji_variant = !action.get_state().get_boolean(); action.set_state(new GLib.Variant.boolean(m_show_emoji_variant)); // Redraw emoji candidate panel for m_show_emoji_variant - if (m_candidate_panel_is_visible) - show_candidate_panel(); + if (m_candidate_panel_is_visible) { + // DOTO: queue_draw() does not effect at the real time. + this.queue_draw(); + } + } +#endif + + + private void action_close_cb(GLib.SimpleAction action, + GLib.Variant? parameter) { + candidate_clicked(0, BUTTON_CLOSE_BUTTON, 0); } @@ -1842,6 +2087,123 @@ public class IBusEmojier : Gtk.ApplicationWindow { } + public void set_annotation(string annotation) { + m_annotation = annotation; + remove_all_children(); + if (annotation.length > 0) { + update_candidate_window(); + } else { + if (m_show_unicode) + update_unicode_blocks(); + else + update_category_list(); + } + } + + + public IBus.LookupTable get_one_dimension_lookup_table() { + var lookup_table = new IBus.LookupTable(EMOJI_GRID_PAGE, 0, true, true); + uint i = 0; + for (; i < m_lookup_table.get_number_of_candidates(); i++) { + IBus.Text text = new IBus.Text.from_string(""); + text.copy(m_lookup_table.get_candidate(i)); + lookup_table.append_candidate(text); + } + if (i > 0) + lookup_table.set_cursor_pos(m_lookup_table.get_cursor_pos()); + return lookup_table; + } + + + public uint get_number_of_candidates() { + return m_lookup_table.get_number_of_candidates(); + } + + + public uint get_cursor_pos() { + return m_lookup_table.get_cursor_pos(); + } + + + public void set_cursor_pos(uint cursor_pos) { + m_lookup_table.set_cursor_pos(cursor_pos); + } + + + public string get_current_candidate() { + // If category_list mode, do not show the category name on preedit. + // If candidate_panel mode, the first space key does not show the + // lookup table but the first candidate is avaiable on preedit. + if (!m_candidate_panel_mode) + return ""; + uint cursor = m_lookup_table.get_cursor_pos(); + return m_lookup_table.get_candidate(cursor).text; + } + + + public IBus.Text get_title_text() { + var language = _(IBus.get_language_name(m_current_lang_id)); + uint ncandidates = this.get_number_of_candidates(); + string main_title = _("Emoji Choice"); + if (m_show_unicode) + main_title = _("Unicode Choice"); + var text = new IBus.Text.from_string( + "%s (%s) (%u / %u)".printf( + main_title, + language, + this.get_cursor_pos() + 1, + ncandidates)); + int char_count = text.text.char_count(); + int start_index = -1; + for (int i = 0; i < char_count; i++) { + if (text.text.utf8_offset(i).has_prefix(language)) { + start_index = i; + break; + } + } + if (start_index >= 0) { + var attr = new IBus.Attribute( + IBus.AttrType.FOREGROUND, + 0x808080, + start_index, + start_index + language.char_count()); + var attrs = new IBus.AttrList(); + attrs.append(attr); + text.set_attributes(attrs); + } + return text; + } + + +#if 0 + public GLib.SList<string>? get_candidates() { + if (m_annotation.length == 0) { + return null; + } + if (m_annotation.length > m_emoji_max_seq_len) { + return null; + } + string? unicode_point = check_unicode_point(m_annotation); + GLib.SList<string>? total_emojis = + lookup_emojis_from_annotation(m_annotation); + if (total_emojis == null) { + /* Users can type title strings against lower case. + * E.g. "Smile" against "smile" + * But the dictionary has the case sensitive annotations. + * E.g. ":D" and ":q" + * So need to call lookup_emojis_from_annotation() twice. + */ + string lower_annotation = m_annotation.down(); + total_emojis = lookup_emojis_from_annotation(lower_annotation); + } + if (unicode_point != null) + total_emojis.prepend(unicode_point); + return total_emojis; + } +#endif + + +#if 0 public string run(string input_context_path, Gdk.Event event) { assert (m_loop == null); @@ -1915,12 +2277,34 @@ public class IBusEmojier : Gtk.ApplicationWindow { return m_result; } +#endif /* override virtual functions */ - public override void show() { - base.show(); - set_focus_visible(true); + public override void show_all() { + base.show_all(); + if (m_candidate_panel_mode) + show_candidate_panel(); + else if (m_show_unicode) + show_unicode_blocks(); + else + show_category_list(); + } + + + public override void hide() { + base.hide(); + m_candidate_panel_is_visible = false; + // m_candidate_panel_mode is not false in when you type something + // during enabling the candidate panel. + if (m_redraw_window_id > 0) { + GLib.Source.remove(m_redraw_window_id); + m_redraw_window_id = 0; + } + if (m_unicode_progress_id > 0) { + GLib.Source.remove(m_unicode_progress_id); + m_unicode_progress_id = 0; + } } @@ -1935,11 +2319,16 @@ public class IBusEmojier : Gtk.ApplicationWindow { switch (keyval) { case Gdk.Key.Escape: if (key_press_escape()) - return true; - break; + show_all(); + return true; case Gdk.Key.Return: case Gdk.Key.KP_Enter: - key_press_enter(); + if (key_press_enter()) { + show_all(); + } else { + m_result = get_current_candidate(); + hide(); + } return true; case Gdk.Key.BackSpace: if (m_entry.get_text().length > 0) { @@ -1977,42 +2366,49 @@ public class IBusEmojier : Gtk.ApplicationWindow { } else { category_list_cursor_move(Gdk.Key.Down); + show_all(); } return true; case Gdk.Key.Right: case Gdk.Key.KP_Right: key_press_cursor_horizontal(Gdk.Key.Right, modifiers); + show_all(); return true; case Gdk.Key.Left: case Gdk.Key.KP_Left: key_press_cursor_horizontal(Gdk.Key.Left, modifiers); + show_all(); return true; case Gdk.Key.Down: case Gdk.Key.KP_Down: key_press_cursor_vertical(Gdk.Key.Down, modifiers); + show_all(); return true; case Gdk.Key.Up: case Gdk.Key.KP_Up: key_press_cursor_vertical(Gdk.Key.Up, modifiers); + show_all(); return true; case Gdk.Key.Page_Down: case Gdk.Key.KP_Page_Down: key_press_cursor_vertical(Gdk.Key.Page_Down, modifiers); + show_all(); return true; case Gdk.Key.Page_Up: case Gdk.Key.KP_Page_Up: key_press_cursor_vertical(Gdk.Key.Page_Up, modifiers); + show_all(); return true; case Gdk.Key.Home: case Gdk.Key.KP_Home: - if (key_press_cursor_home_end(Gdk.Key.Home, modifiers)) - return true; - break; + key_press_cursor_home_end(Gdk.Key.Home, modifiers); + show_all(); + return true; case Gdk.Key.End: case Gdk.Key.KP_End: - if (key_press_cursor_home_end(Gdk.Key.End, modifiers)) - return true; - break; + key_press_cursor_home_end(Gdk.Key.End, modifiers); + show_all(); + return true; case Gdk.Key.Insert: case Gdk.Key.KP_Insert: GLib.Signal.emit_by_name(m_entry, "toggle-overwrite"); @@ -2023,26 +2419,30 @@ public class IBusEmojier : Gtk.ApplicationWindow { switch (keyval) { case Gdk.Key.f: key_press_cursor_horizontal(Gdk.Key.Right, modifiers); + show_all(); return true; case Gdk.Key.b: key_press_cursor_horizontal(Gdk.Key.Left, modifiers); + show_all(); return true; case Gdk.Key.n: case Gdk.Key.N: key_press_cursor_vertical(Gdk.Key.Down, modifiers); + show_all(); return true; case Gdk.Key.p: case Gdk.Key.P: key_press_cursor_vertical(Gdk.Key.Up, modifiers); + show_all(); return true; case Gdk.Key.h: - if (key_press_cursor_home_end(Gdk.Key.Home, modifiers)) - return true; - break; + key_press_cursor_home_end(Gdk.Key.Home, modifiers); + show_all(); + return true; case Gdk.Key.e: - if (key_press_cursor_home_end(Gdk.Key.End, modifiers)) - return true; - break; + key_press_cursor_home_end(Gdk.Key.End, modifiers); + show_all(); + return true; case Gdk.Key.u: if (m_entry.get_text().length > 0) { GLib.Signal.emit_by_name(m_entry, @@ -2103,14 +2503,41 @@ public class IBusEmojier : Gtk.ApplicationWindow { } + public void set_input_context_path(string input_context_path) { + m_input_context_path = input_context_path; + if (input_context_path == "") { + m_warning_message = _("" + + "Failed to get the current text application. " + + "Please re-focus your application. E.g. Press Esc key " + + "several times to release the emoji typing mode, " + + "click your desktop and click your text application again." + ); + } else { + m_warning_message = ""; + } + } + + public string get_selected_string() { return m_result; } + private void reset_window_mode() { + m_backward_index = -1; + m_backward = null; + m_candidate_panel_is_visible = false; + m_candidate_panel_mode = false; + // Do not clear m_lookup_table to work with space key later. + } + + public void reset() { + reset_window_mode(); m_input_context_path = ""; m_result = null; + m_category_active_index = -1; + m_show_unicode = false; } @@ -2145,6 +2572,23 @@ public class IBusEmojier : Gtk.ApplicationWindow { } + public void set_cursor_location(int x, + int y, + int width, + int height) { + Gdk.Rectangle location = Gdk.Rectangle(){ + x = x, y = y, width = width, height = height }; + if (m_cursor_location == location) + return; + m_cursor_location = location; + } + + + public bool is_candidate_panel_mode() { + return m_candidate_panel_mode; + } + + public static bool has_loaded_emoji_dict() { if (m_emoji_to_data_dict == null) return false; @@ -2165,6 +2609,10 @@ public class IBusEmojier : Gtk.ApplicationWindow { } + public static string get_annotation_lang() { + return m_current_lang_id; + } + public static void set_emoji_font(string? emoji_font) { return_if_fail(emoji_font != null && emoji_font != ""); Pango.FontDescription font_desc = @@ -2182,18 +2630,21 @@ public class IBusEmojier : Gtk.ApplicationWindow { m_has_partial_match = has_partial_match; } + public static void set_partial_match_length(int length) { if (length < 1) return; m_partial_match_length = length; } + public static void set_partial_match_condition(int condition) { if (condition < 0) return; m_partial_match_condition = condition; } + public static void set_favorites(string[]? unowned_favorites, string[]? unowned_favorite_annotations) { m_favorites = {}; diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala index 9506a945..787d448f 100644 --- a/ui/gtk3/emojierapp.vala +++ b/ui/gtk3/emojierapp.vala @@ -28,8 +28,9 @@ int partial_match_condition = -1; public class EmojiApplication : Gtk.Application { private IBusEmojier? m_emojier; - GLib.Settings m_settings_emoji = + private GLib.Settings m_settings_emoji = new GLib.Settings("org.freedesktop.ibus.panel.emoji"); + private ApplicationCommandLine? m_command_line = null; private EmojiApplication() { @@ -40,25 +41,39 @@ public class EmojiApplication : Gtk.Application { private void show_dialog(ApplicationCommandLine command_line) { - m_emojier = new IBusEmojier(); - // For title handling in gnome-shell - add_window(m_emojier); - Gdk.Event event = Gtk.get_current_event(); - // Plasma and GNOME3 desktop returns null event - if (event == null) { - event = new Gdk.Event(Gdk.EventType.KEY_PRESS); - event.key.time = Gdk.CURRENT_TIME; - // event.get_seat() refers event.any.window - event.key.window = Gdk.get_default_root_window(); - event.key.window.ref(); + m_command_line = command_line; + m_emojier.reset(); + m_emojier.set_annotation(""); + m_emojier.show_all(); + } + + + public void candidate_clicked_lookup_table(uint index, + uint button, + uint state) { + if (m_command_line == null) + return; + if (button == IBusEmojier.BUTTON_CLOSE_BUTTON) { + m_emojier.hide(); + m_command_line.print("%s\n", _("Canceled to choose an emoji.")); + m_command_line = null; + return; } - string emoji = m_emojier.run("", event); - remove_window(m_emojier); - if (emoji == null) { - m_emojier = null; - command_line.print("%s\n", _("Canceled to choose an emoji.")); + if (m_emojier == null) + return; + bool show_candidate = false; + uint ncandidates = m_emojier.get_number_of_candidates(); + if (ncandidates > 0 && ncandidates >= index) { + m_emojier.set_cursor_pos(index); + show_candidate = m_emojier.has_variants(index); + } else { + return; + } + if (show_candidate) { return; } + string emoji = m_emojier.get_current_candidate(); + m_emojier.hide(); Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD); clipboard.set_text(emoji, -1); clipboard.store(); @@ -75,9 +90,8 @@ public class EmojiApplication : Gtk.Application { emojier_favorites += emoji; m_settings_emoji.set_strv("favorites", emojier_favorites); } - - m_emojier = null; - command_line.print("%s\n", _("Copied an emoji to your clipboard.")); + m_command_line.print("%s\n", _("Copied an emoji to your clipboard.")); + m_command_line = null; } @@ -88,7 +102,7 @@ public class EmojiApplication : Gtk.Application { } - private int _command_line (ApplicationCommandLine command_line) { + private int _command_line(ApplicationCommandLine command_line) { // Set default font size IBusEmojier.set_emoji_font(m_settings_emoji.get_string("font")); @@ -181,13 +195,22 @@ public class EmojiApplication : Gtk.Application { IBusEmojier.load_unicode_dict(); + if (m_emojier == null) { + m_emojier = new IBusEmojier(); + // For title handling in gnome-shell + add_window(m_emojier); + m_emojier.candidate_clicked.connect((i, b, s) => { + candidate_clicked_lookup_table(i, b, s); + }); + } + activate_dialog(command_line); return Posix.EXIT_SUCCESS; } - public override int command_line (ApplicationCommandLine command_line) { + public override int command_line(ApplicationCommandLine command_line) { // keep the application running until we are done with this commandline this.hold(); int result = _command_line(command_line); @@ -196,6 +219,13 @@ public class EmojiApplication : Gtk.Application { } + public override void shutdown() { + base.shutdown(); + remove_window(m_emojier); + m_emojier = null; + } + + public static int main (string[] args) { GLib.Intl.bindtextdomain(Config.GETTEXT_PACKAGE, Config.GLIB_LOCALE_DIR); diff --git a/ui/gtk3/extension.vala b/ui/gtk3/extension.vala index 7d6d76e7..c729fd7e 100644 --- a/ui/gtk3/extension.vala +++ b/ui/gtk3/extension.vala @@ -50,20 +50,20 @@ class ExtensionGtk : Gtk.Application { "org.freedesktop.DBus", "NameAcquired", "/org/freedesktop/DBus", - IBus.SERVICE_PANEL_EXTENSION, + IBus.SERVICE_PANEL_EXTENSION_EMOJI, DBusSignalFlags.NONE, bus_name_acquired_cb); connection.signal_subscribe("org.freedesktop.DBus", "org.freedesktop.DBus", "NameLost", "/org/freedesktop/DBus", - IBus.SERVICE_PANEL_EXTENSION, + IBus.SERVICE_PANEL_EXTENSION_EMOJI, DBusSignalFlags.NONE, bus_name_lost_cb); var flags = IBus.BusNameFlag.ALLOW_REPLACEMENT | IBus.BusNameFlag.REPLACE_EXISTING; - m_bus.request_name(IBus.SERVICE_PANEL_EXTENSION, flags); + m_bus.request_name(IBus.SERVICE_PANEL_EXTENSION_EMOJI, flags); } diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala index d9238c89..4c3b00ca 100644 --- a/ui/gtk3/panel.vala +++ b/ui/gtk3/panel.vala @@ -1148,26 +1148,15 @@ class Panel : IBus.PanelService { #if EMOJI_DICT item = new Gtk.MenuItem.with_label(_("Emoji Choice")); item.activate.connect((i) => { - Gdk.Event event = Gtk.get_current_event(); - if (event == null) { - event = new Gdk.Event(Gdk.EventType.KEY_PRESS); - event.key.time = Gdk.CURRENT_TIME; - // event.get_seat() refers event.any.window - event.key.window = Gdk.get_default_root_window(); - event.key.window.ref(); - } - IBus.XEvent xevent = new IBus.XEvent( - "event-type", IBus.XEventType.KEY_PRESS, - "window", - (event.key.window as Gdk.X11.Window).get_xid(), - "time", event.key.time, - "purpose", "emoji"); - /* new GLib.Variant("(sv)", "emoji", xevent.serialize_object()) + IBus.ExtensionEvent event = new IBus.ExtensionEvent( + "name", "emoji", "is-enabled", true, + "params", "category-list"); + /* new GLib.Variant("(sv)", "emoji", event.serialize_object()) * will call g_variant_unref() for the child variant by vala. * I have no idea not to unref the object so integrated - * the purpose to IBus.XEvent above. + * the purpose to IBus.ExtensionEvent above. */ - panel_extension(xevent.serialize_object()); + panel_extension(event); }); m_sys_menu.append(item); #endif diff --git a/ui/gtk3/panelbinding.vala b/ui/gtk3/panelbinding.vala index 581f721e..52b78c17 100644 --- a/ui/gtk3/panelbinding.vala +++ b/ui/gtk3/panelbinding.vala @@ -21,7 +21,193 @@ * USA */ +class Preedit : Gtk.Window { + private Gtk.Label m_extension_preedit_text; + private Gtk.Label m_extension_preedit_emoji; + private IBus.Text? m_engine_preedit_text; + private bool m_engine_preedit_text_show; + private uint m_engine_preedit_cursor_pos; + private string m_prefix = "@"; + private bool m_is_shown = true; + + + public Preedit() { + GLib.Object( + name : "IBusPreedit", + type: Gtk.WindowType.POPUP + ); + m_extension_preedit_text = new Gtk.Label(""); + m_extension_preedit_emoji = new Gtk.Label(""); + } + + + public new void hide() { + reset(); + base.hide(); + m_is_shown = false; + } + + + public bool is_shown() { + return m_is_shown; + } + + + public void reset() { + set_emoji(""); + set_text(""); + resize(1, 1); + m_is_shown = true; + } + + public void append_text(string text) { + if (text.length == 0) + return; + string total = m_extension_preedit_text.get_text(); + total += text; + m_extension_preedit_text.set_text(total); + } + + + public string get_text() { + return m_extension_preedit_text.get_text(); + } + + + public void set_text(string text) { + m_extension_preedit_text.set_text(text); + } + + + public string get_emoji() { + return m_extension_preedit_emoji.get_text(); + } + + + public void set_emoji(string text) { + m_extension_preedit_emoji.set_text(text); + } + + + public bool backspace() { + string total = m_extension_preedit_emoji.get_text(); + if (total.length > 0) { + m_extension_preedit_emoji.set_text(""); + resize(1, 1); + return false; + } + total = m_extension_preedit_text.get_text(); + int char_count = total.char_count(); + if (char_count == 0) + return true; + total = total[0:total.index_of_nth_char(char_count - 1)]; + resize(1, 1); + m_extension_preedit_text.set_text(total); + if (total.length == 0) + resize(1, 1); + return true; + } + + + private string get_extension_text () { + string extension_text = m_extension_preedit_emoji.get_text(); + if (extension_text.length == 0) + extension_text = m_extension_preedit_text.get_text(); + return m_prefix + extension_text; + } + + + private void set_preedit_color(IBus.Text text, + uint start_index, + uint end_index) { + text.append_attribute(IBus.AttrType.UNDERLINE, + IBus.AttrUnderline.SINGLE, + start_index, (int)end_index); + } + + + public IBus.Text get_engine_preedit_text() { + string extension_text = get_extension_text(); + uint char_count = extension_text.char_count(); + IBus.Text retval; + if (m_engine_preedit_text == null || !m_engine_preedit_text_show) { + retval = new IBus.Text.from_string(extension_text); + set_preedit_color(retval, 0, char_count); + return retval; + } + retval = new IBus.Text.from_string( + extension_text + m_engine_preedit_text.get_text()); + set_preedit_color(retval, 0, char_count); + + unowned IBus.AttrList attrs = m_engine_preedit_text.get_attributes(); + + if (attrs == null) + return retval; + + int i = 0; + while (true) { + IBus.Attribute attr = attrs.get(i++); + if (attr == null) + break; + long start_index = attr.start_index; + long end_index = attr.end_index; + if (start_index < 0) + start_index = 0; + if (end_index < 0) + end_index = m_engine_preedit_text.get_length(); + retval.append_attribute(attr.type, attr.value, + char_count + (uint)start_index, + (int)char_count + (int)end_index); + } + return retval; + } + + + public void set_engine_preedit_text(IBus.Text? text) { + m_engine_preedit_text = text; + } + + + public void show_engine_preedit_text() { + m_engine_preedit_text_show = true; + } + + + public void hide_engine_preedit_text() { + m_engine_preedit_text_show = false; + } + + + public uint get_engine_preedit_cursor_pos() { + return get_extension_text().char_count() + m_engine_preedit_cursor_pos; + } + + + public void set_engine_preedit_cursor_pos(uint cursor_pos) { + m_engine_preedit_cursor_pos = cursor_pos; + } + + + public IBus.Text get_commit_text() { + string extension_text = m_extension_preedit_emoji.get_text(); + if (extension_text.length == 0) + extension_text = m_extension_preedit_text.get_text(); + return new IBus.Text.from_string(extension_text); + } + + + public void set_extension_name(string extension_name) { + if (extension_name.length == 0) + m_prefix = "@"; + else + m_prefix = extension_name[0:1]; + } +} + + class PanelBinding : IBus.PanelService { + private bool m_is_wayland; + private bool m_wayland_lookup_table_is_visible; private IBus.Bus m_bus; private Gtk.Application m_application; private GLib.Settings m_settings_panel = null; @@ -38,18 +224,26 @@ class PanelBinding : IBus.PanelService { private bool m_loaded_emoji = false; private bool m_load_unicode_at_startup; private bool m_loaded_unicode = false; + private bool m_enable_extension; + private string m_extension_name = ""; + private Preedit m_preedit; public PanelBinding(IBus.Bus bus, Gtk.Application application) { GLib.assert(bus.is_connected()); // Chain up base class constructor GLib.Object(connection : bus.get_connection(), - object_path : IBus.PATH_PANEL_EXTENSION); + object_path : IBus.PATH_PANEL_EXTENSION_EMOJI); + + Type instance_type = Gdk.Display.get_default().get_type(); + Type wayland_type = typeof(GdkWayland.Display); + m_is_wayland = instance_type.is_a(wayland_type); m_bus = bus; m_application = application; init_settings(); + m_preedit = new Preedit(); } @@ -69,12 +263,20 @@ class PanelBinding : IBus.PanelService { ref m_css_provider); }); + m_settings_emoji.changed["unicode-hotkey"].connect((key) => { + set_emoji_hotkey(); + }); + m_settings_emoji.changed["font"].connect((key) => { BindingCommon.set_custom_font(m_settings_panel, m_settings_emoji, ref m_css_provider); }); + m_settings_emoji.changed["hotkey"].connect((key) => { + set_emoji_hotkey(); + }); + m_settings_emoji.changed["favorites"].connect((key) => { set_emoji_favorites(); }); @@ -109,6 +311,54 @@ class PanelBinding : IBus.PanelService { } + private unowned + IBus.ProcessKeyEventData? parse_accelerator(string accelerator) { + IBus.ProcessKeyEventData key = {}; + uint keysym = 0; + IBus.ModifierType modifiers = 0; + IBus.accelerator_parse(accelerator, + out keysym, out modifiers); + if (keysym == 0U && modifiers == 0) { + warning("Failed to parse shortcut key '%s'".printf(accelerator)); + return null; + } + if ((modifiers & IBus.ModifierType.SUPER_MASK) != 0) { + modifiers ^= IBus.ModifierType.SUPER_MASK; + modifiers |= IBus.ModifierType.MOD4_MASK; + } + key.keyval = keysym; + key.state = modifiers; + return key; + } + + + private void set_emoji_hotkey() { + IBus.ProcessKeyEventData[] emoji_keys = {}; + IBus.ProcessKeyEventData key; + string[] accelerators = m_settings_emoji.get_strv("hotkey"); + foreach (var accelerator in accelerators) { + key = parse_accelerator(accelerator); + emoji_keys += key; + } + + /* Since {} is not allocated, parse_accelerator() should be unowned. */ + key = {}; + emoji_keys += key; + + IBus.ProcessKeyEventData[] unicode_keys = {}; + accelerators = m_settings_emoji.get_strv("unicode-hotkey"); + foreach (var accelerator in accelerators) { + key = parse_accelerator(accelerator); + unicode_keys += key; + } + key = {}; + unicode_keys += key; + + panel_extension_register_keys("emoji", emoji_keys, + "unicode", unicode_keys); + } + + private void set_emoji_favorites() { m_emojier_favorites = m_settings_emoji.get_strv("favorites"); IBusEmojier.set_favorites( @@ -159,6 +409,7 @@ class PanelBinding : IBus.PanelService { public void load_settings() { + set_emoji_hotkey(); set_load_emoji_at_startup(); set_load_unicode_at_startup(); BindingCommon.set_custom_font(m_settings_panel, @@ -181,36 +432,37 @@ class PanelBinding : IBus.PanelService { GLib.Source.remove(m_emojier_set_emoji_lang_id); m_emojier_set_emoji_lang_id = 0; } - m_application = null; - } - - - private void show_emojier(Gdk.Event event) { - if (!m_loaded_emoji) - set_emoji_lang(); - if (!m_loaded_unicode && m_loaded_emoji) { - IBusEmojier.load_unicode_dict(); - m_loaded_unicode = true; - } - m_emojier = new IBusEmojier(); - // For title handling in gnome-shell - m_application.add_window(m_emojier); - string emoji = m_emojier.run(m_real_current_context_path, event); - m_application.remove_window(m_emojier); - if (emoji == null) { + if (m_emojier != null) { + m_application.remove_window(m_emojier); m_emojier = null; - return; } - this.emojier_focus_commit(); + m_application = null; } - private void handle_emoji_typing(Gdk.Event event) { - if (m_emojier != null && m_emojier.is_running()) { - m_emojier.present_centralize(event); + private void commit_text_update_favorites(IBus.Text text) { + commit_text(text); + IBus.ExtensionEvent event = new IBus.ExtensionEvent( + "name", m_extension_name, + "is-enabled", false, + "is-extension", true); + panel_extension(event); + string committed_string = text.text; + string preedit_string = m_preedit.get_text(); + m_preedit.hide(); + if (preedit_string == committed_string) return; + bool has_favorite = false; + foreach (unowned string favorite in m_emojier_favorites) { + if (favorite == committed_string) { + has_favorite = true; + break; + } + } + if (!has_favorite) { + m_emojier_favorites += committed_string; + m_settings_emoji.set_strv("favorites", m_emojier_favorites); } - show_emojier(event); } @@ -223,19 +475,8 @@ class PanelBinding : IBus.PanelService { prev_context_path != "" && prev_context_path == m_current_context_path) { IBus.Text text = new IBus.Text.from_string(selected_string); - commit_text(text); - m_emojier = null; - bool has_favorite = false; - foreach (unowned string favorite in m_emojier_favorites) { - if (favorite == selected_string) { - has_favorite = true; - break; - } - } - if (!has_favorite) { - m_emojier_favorites += selected_string; - m_settings_emoji.set_strv("favorites", m_emojier_favorites); - } + commit_text_update_favorites(text); + m_emojier.reset(); return true; } @@ -249,8 +490,7 @@ class PanelBinding : IBus.PanelService { string selected_string = m_emojier.get_selected_string(); string prev_context_path = m_emojier.get_input_context_path(); if (selected_string == null && - prev_context_path != "" && - m_emojier.is_running()) { + prev_context_path != "") { var context = GLib.MainContext.default(); if (m_emojier_focus_commit_text_id > 0 && context.find_source_by_id(m_emojier_focus_commit_text_id) @@ -277,6 +517,243 @@ class PanelBinding : IBus.PanelService { } + private bool key_press_escape() { + if (is_emoji_lookup_table()) { + bool show_candidate = m_emojier.key_press_escape(); + convert_preedit_text(); + return show_candidate; + } + if (m_preedit.get_emoji() != "") { + m_preedit.set_emoji(""); + string annotation = m_preedit.get_text(); + m_emojier.set_annotation(annotation); + return false; + } + m_enable_extension = false; + hide_emoji_lookup_table(); + m_preedit.hide(); + IBus.ExtensionEvent event = new IBus.ExtensionEvent( + "name", m_extension_name, + "is-enabled", false, + "is-extension", true); + panel_extension(event); + return false; + } + + + private bool key_press_enter() { + if (m_extension_name != "unicode" && is_emoji_lookup_table()) { + // Check if variats exist + if (m_emojier.key_press_enter()) + return true; + } + IBus.Text text = m_preedit.get_commit_text(); + commit_text_update_favorites(text); + return false; + } + + + private void convert_preedit_text() { + if (m_emojier.get_number_of_candidates() > 0) + m_preedit.set_emoji(m_emojier.get_current_candidate()); + else + m_preedit.set_emoji(""); + } + + + private bool key_press_space() { + bool show_candidate = false; + if (m_preedit.get_emoji() != "") { + m_emojier.key_press_cursor_horizontal(Gdk.Key.Right, 0); + show_candidate = true; + } else { + string annotation = m_preedit.get_text(); + if (annotation.length == 0) { + show_candidate = true; + if (is_emoji_lookup_table()) + m_emojier.key_press_cursor_horizontal(Gdk.Key.Right, 0); + } else { + m_emojier.set_annotation(annotation); + } + } + convert_preedit_text(); + return show_candidate; + } + + + private bool key_press_cursor_horizontal(uint keyval, + uint modifiers) { + if (is_emoji_lookup_table()) { + m_emojier.key_press_cursor_horizontal(keyval, modifiers); + convert_preedit_text(); + return true; + } + return false; + } + + + private bool key_press_cursor_vertical(uint keyval, + uint modifiers) { + if (is_emoji_lookup_table()) { + m_emojier.key_press_cursor_vertical(keyval, modifiers); + convert_preedit_text(); + return true; + } + return false; + } + + + private bool key_press_cursor_home_end(uint keyval, + uint modifiers) { + if (is_emoji_lookup_table()) { + m_emojier.key_press_cursor_home_end(keyval, modifiers); + convert_preedit_text(); + return true; + } + return false; + } + + + private bool key_press_control_keyval(uint keyval, + uint modifiers) { + bool show_candidate = false; + switch(keyval) { + case Gdk.Key.f: + show_candidate = key_press_cursor_horizontal(Gdk.Key.Right, + modifiers); + break; + case Gdk.Key.b: + show_candidate = key_press_cursor_horizontal(Gdk.Key.Left, + modifiers); + break; + case Gdk.Key.n: + case Gdk.Key.N: + show_candidate = key_press_cursor_vertical(Gdk.Key.Down, modifiers); + break; + case Gdk.Key.p: + case Gdk.Key.P: + show_candidate = key_press_cursor_vertical(Gdk.Key.Up, modifiers); + break; + case Gdk.Key.h: + show_candidate = key_press_cursor_home_end(Gdk.Key.Home, modifiers); + break; + case Gdk.Key.e: + show_candidate = key_press_cursor_home_end(Gdk.Key.End, modifiers); + break; + case Gdk.Key.u: + m_preedit.reset(); + m_emojier.set_annotation(""); + hide_emoji_lookup_table(); + break; + case Gdk.Key.C: + case Gdk.Key.c: + if ((modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) { + if (!m_is_wayland && m_emojier != null && + m_emojier.get_number_of_candidates() > 0) { + var text = m_emojier.get_current_candidate(); + Gtk.Clipboard clipboard = + Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD); + clipboard.set_text(text, -1); + clipboard.store(); + } + show_candidate = is_emoji_lookup_table(); + } + break; + default: + show_candidate = is_emoji_lookup_table(); + break; + } + return show_candidate; + } + + + private void hide_wayland_lookup_table() { + m_wayland_lookup_table_is_visible = false; + var text = new IBus.Text.from_string(""); + update_auxiliary_text_received(text, false); + update_lookup_table_received( + new IBus.LookupTable(1, 0, false, true), + false); + } + + + private void show_wayland_lookup_table(IBus.Text text) { + m_wayland_lookup_table_is_visible = true; + var table = m_emojier.get_one_dimension_lookup_table(); + uint ncandidates = table.get_number_of_candidates(); + update_auxiliary_text_received( + text, + ncandidates > 0 ? true : false); + update_lookup_table_received( + table, + ncandidates > 0 ? true : false); + } + + + private bool is_visible_wayland_lookup_table() { + return m_wayland_lookup_table_is_visible; + } + + + private void hide_emoji_lookup_table() { + if (m_emojier == null) + return; + if (m_is_wayland) + hide_wayland_lookup_table(); + else + m_emojier.hide(); + } + + + private void show_emoji_lookup_table() { + /* Emojier category_list is shown in both Xorg and Wayland + * because the annotation information is useful but the Wayland lookup + * window is alway one dimension. So the category_list is shown + * when the user annotation is null. + */ + if (m_is_wayland && m_preedit.get_text() != "") { + var text = m_emojier.get_title_text(); + show_wayland_lookup_table(text); + } else { + // POPUP window takes the focus in Wayland. + if (m_is_wayland) + m_emojier.set_input_context_path(m_real_current_context_path); + m_emojier.show_all(); + } + } + + + private bool is_emoji_lookup_table() { + if (m_is_wayland) + return is_visible_wayland_lookup_table(); + else + return m_emojier.get_visible(); + } + + + private void show_preedit_and_candidate(bool show_candidate) { + uint cursor_pos = 0; + if (!show_candidate) + cursor_pos = m_preedit.get_engine_preedit_cursor_pos(); + update_preedit_text_received( + m_preedit.get_engine_preedit_text(), + cursor_pos, + true); + if (!show_candidate) { + hide_emoji_lookup_table(); + return; + } + if (m_emojier == null) + return; + /* Wayland gives the focus on Emojir which is a GTK popup window + * and move the focus fom the current input context to Emojier. + * This forwards the lookup table to gnome-shell's lookup table + * but it enables one dimension lookup table only. + */ + show_emoji_lookup_table(); + } + + public override void focus_in(string input_context_path) { m_current_context_path = input_context_path; @@ -299,48 +776,280 @@ class PanelBinding : IBus.PanelService { } - public override void panel_extension_received(GLib.Variant data) { - IBus.XEvent? xevent = IBus.Serializable.deserialize_object(data) - as IBus.XEvent; - if (xevent == null) { - warning ("Failed to deserialize IBusXEvent"); + public override void panel_extension_received(IBus.ExtensionEvent event) { + m_extension_name = event.get_name(); + if (m_extension_name != "emoji" && m_extension_name != "unicode") { + string format = "The name %s is not implemented in PanelExtension"; + warning (format.printf(m_extension_name)); + m_extension_name = ""; return; } - if (xevent.get_purpose() != "emoji") { - string format = "The purpose %s is not implemented in PanelExtension"; - warning (format.printf(xevent.get_purpose())); + m_enable_extension = event.is_enabled; + if (!m_enable_extension) { + hide_emoji_lookup_table(); + return; + } + if (!m_loaded_emoji) + set_emoji_lang(); + if (!m_loaded_unicode && m_loaded_emoji) { + IBusEmojier.load_unicode_dict(); + m_loaded_unicode = true; + } + if (m_emojier == null) { + m_emojier = new IBusEmojier(); + // For title handling in gnome-shell + m_application.add_window(m_emojier); + m_emojier.candidate_clicked.connect((i, b, s) => { + if (!m_is_wayland) + candidate_clicked_lookup_table(i, b, s); + }); + } + m_emojier.reset(); + m_emojier.set_annotation(""); + m_preedit.set_extension_name(m_extension_name); + m_preedit.reset(); + update_preedit_text_received( + m_preedit.get_engine_preedit_text(), + m_preedit.get_engine_preedit_cursor_pos(), + true); + string params = event.get_params(); + if (params == "category-list") { + key_press_space(); + show_preedit_and_candidate(true); + } + } + + + public override void set_cursor_location(int x, + int y, + int width, + int height) { + if (m_emojier != null) + m_emojier.set_cursor_location(x, y, width, height); + } + + + public override void update_preedit_text(IBus.Text text, + uint cursor_pos, + bool visible) { + m_preedit.set_engine_preedit_text(text); + if (visible) + m_preedit.show_engine_preedit_text(); + else + m_preedit.hide_engine_preedit_text(); + m_preedit.set_engine_preedit_cursor_pos(cursor_pos); + update_preedit_text_received(m_preedit.get_engine_preedit_text(), + m_preedit.get_engine_preedit_cursor_pos(), + visible); + } + + + public override void show_preedit_text() { + m_preedit.show_engine_preedit_text(); + show_preedit_and_candidate(false); + } + + + public override void hide_preedit_text() { + m_preedit.hide_engine_preedit_text(); + show_preedit_and_candidate(false); + } + + + public override bool process_key_event(uint keyval, + uint keycode, + uint state) { + if ((state & IBus.ModifierType.RELEASE_MASK) != 0) + return false; + uint modifiers = state; + bool show_candidate = false; + switch(keyval) { + case Gdk.Key.Escape: + show_candidate = key_press_escape(); + if (!m_preedit.is_shown()) + return true; + break; + case Gdk.Key.Return: + case Gdk.Key.KP_Enter: + if (m_extension_name == "unicode") + key_press_space(); + show_candidate = key_press_enter(); + if (!m_preedit.is_shown()) { + hide_emoji_lookup_table(); + return true; + } + break; + case Gdk.Key.BackSpace: + m_preedit.backspace(); + string annotation = m_preedit.get_text(); + if (annotation == "" && m_extension_name == "unicode") { + key_press_escape(); + return true; + } + m_emojier.set_annotation(annotation); + break; + case Gdk.Key.space: + case Gdk.Key.KP_Space: + show_candidate = key_press_space(); + if (m_extension_name == "unicode") { + key_press_enter(); + return true; + } + break; + case Gdk.Key.Right: + case Gdk.Key.KP_Right: + /* one dimension in Wayland, two dimensions in X11 */ + if (m_is_wayland) { + show_candidate = key_press_cursor_vertical(Gdk.Key.Down, + modifiers); + } else { + show_candidate = key_press_cursor_horizontal(Gdk.Key.Right, + modifiers); + } + break; + case Gdk.Key.Left: + case Gdk.Key.KP_Left: + if (m_is_wayland) { + show_candidate = key_press_cursor_vertical(Gdk.Key.Up, + modifiers); + } else { + show_candidate = key_press_cursor_horizontal(Gdk.Key.Left, + modifiers); + } + break; + case Gdk.Key.Down: + case Gdk.Key.KP_Down: + if (m_is_wayland) { + show_candidate = key_press_cursor_horizontal(Gdk.Key.Right, + modifiers); + } else { + show_candidate = key_press_cursor_vertical(Gdk.Key.Down, + modifiers); + } + break; + case Gdk.Key.Up: + case Gdk.Key.KP_Up: + if (m_is_wayland) { + show_candidate = key_press_cursor_horizontal(Gdk.Key.Left, + modifiers); + } else { + show_candidate = key_press_cursor_vertical(Gdk.Key.Up, + modifiers); + } + break; + case Gdk.Key.Page_Down: + case Gdk.Key.KP_Page_Down: + if (m_is_wayland) { + show_candidate = key_press_cursor_vertical(Gdk.Key.Down, + modifiers); + } else { + show_candidate = key_press_cursor_vertical(Gdk.Key.Page_Down, + modifiers); + } + break; + case Gdk.Key.Page_Up: + case Gdk.Key.KP_Page_Up: + if (m_is_wayland) { + show_candidate = key_press_cursor_vertical(Gdk.Key.Up, + modifiers); + } else { + show_candidate = key_press_cursor_vertical(Gdk.Key.Page_Up, + modifiers); + } + break; + case Gdk.Key.Home: + case Gdk.Key.KP_Home: + show_candidate = key_press_cursor_home_end(Gdk.Key.Home, modifiers); + break; + case Gdk.Key.End: + case Gdk.Key.KP_End: + show_candidate = key_press_cursor_home_end(Gdk.Key.End, modifiers); + break; + default: + if ((modifiers & Gdk.ModifierType.CONTROL_MASK) != 0) { + show_candidate = key_press_control_keyval(keyval, modifiers); + break; + } + unichar ch = IBus.keyval_to_unicode(keyval); + if (ch.iscntrl()) + return true; + string str = ch.to_string(); + m_preedit.append_text(str); + string annotation = m_preedit.get_text(); + m_emojier.set_annotation(annotation); + m_preedit.set_emoji(""); + show_candidate = is_emoji_lookup_table(); + break; + } + show_preedit_and_candidate(show_candidate); + return true; + } + + public override void commit_text_received(IBus.Text text) { + unowned string? str = text.text; + if (str == null) + return; + /* Do not call convert_preedit_text() because it depends on + * each IME whether process_key_event() receives Shift-space or not. + */ + m_preedit.append_text(str); + m_preedit.set_emoji(""); + string annotation = m_preedit.get_text(); + m_emojier.set_annotation(annotation); + show_preedit_and_candidate(false); + } + + public override void page_up_lookup_table() { + bool show_candidate = key_press_cursor_vertical(Gdk.Key.Up, 0); + show_preedit_and_candidate(show_candidate); + } + + public override void page_down_lookup_table() { + bool show_candidate = key_press_cursor_vertical(Gdk.Key.Down, 0); + show_preedit_and_candidate(show_candidate); + } + + public override void cursor_up_lookup_table() { + bool show_candidate = key_press_cursor_horizontal(Gdk.Key.Left, 0); + show_preedit_and_candidate(show_candidate); + } + + public override void cursor_down_lookup_table() { + bool show_candidate = key_press_cursor_horizontal(Gdk.Key.Right, 0); + show_preedit_and_candidate(show_candidate); + } + + public override void candidate_clicked_lookup_table(uint index, + uint button, + uint state) { + if (button == IBusEmojier.BUTTON_CLOSE_BUTTON) { + m_enable_extension = false; + hide_emoji_lookup_table(); + m_preedit.hide(); + IBus.ExtensionEvent event = new IBus.ExtensionEvent( + "name", m_extension_name, + "is-enabled", false, + "is-extension", true); + panel_extension(event); return; } - Gdk.EventType event_type; - if (xevent.get_event_type() == IBus.XEventType.KEY_PRESS) { - event_type = Gdk.EventType.KEY_PRESS; - } else if (xevent.get_event_type() == IBus.XEventType.KEY_RELEASE) { - event_type = Gdk.EventType.KEY_RELEASE; + if (m_emojier == null) + return; + bool show_candidate = false; + uint ncandidates = m_emojier.get_number_of_candidates(); + if (ncandidates > 0 && ncandidates >= index) { + m_emojier.set_cursor_pos(index); + show_candidate = m_emojier.has_variants(index); + m_preedit.set_emoji(m_emojier.get_current_candidate()); } else { - warning ("Not supported type %d".printf(xevent.get_event_type())); return; } - Gdk.Event event = new Gdk.Event(event_type); - uint32 time = xevent.get_time(); - if (time == 0) - time = Gtk.get_current_event_time(); - event.key.time = time; - X.Window xid = xevent.get_window(); - Gdk.Display? display = Gdk.Display.get_default(); - Gdk.Window? window = null; - if (window == null && xid != 0) { - window = Gdk.X11.Window.lookup_for_display( - display as Gdk.X11.Display, xid); - } - if (window == null && xid != 0) { - window = new Gdk.X11.Window.foreign_for_display( - display as Gdk.X11.Display, xid); - } - if (window == null) { - window = Gdk.get_default_root_window(); - window.ref(); - } - event.key.window = window; - handle_emoji_typing(event); + if (!show_candidate) { + IBus.Text text = m_preedit.get_commit_text(); + commit_text_update_favorites(text); + hide_emoji_lookup_table(); + return; + } + show_preedit_and_candidate(show_candidate); } } |