diff --git a/bus/inputcontext.c b/bus/inputcontext.c index 8aded5d81..4d1fb0414 100644 --- a/bus/inputcontext.c +++ b/bus/inputcontext.c @@ -31,6 +31,8 @@ #include "marshalers.h" #include "types.h" +#define MAX_SYNC_DATA 30 + struct _SetEngineByDescData { /* context related to the data */ BusInputContext *context; @@ -46,6 +48,11 @@ struct _SetEngineByDescData { }; typedef struct _SetEngineByDescData SetEngineByDescData; +typedef struct _SyncForwardingData { + gchar key; + IBusText *text; +} SyncForwardingData; + struct _BusInputContext { IBusService parent; @@ -99,6 +106,9 @@ struct _BusInputContext { BusPanelProxy *emoji_extension; gboolean is_extension_lookup_table; + GQueue *queue_during_process_key_event; + gboolean use_post_process_key_event; + gboolean processing_key_event; }; struct _BusInputContextClass { @@ -156,6 +166,15 @@ static void bus_input_context_service_method_call const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation); +static GVariant * + bus_input_context_service_get_property + (IBusService *service, + GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error); static gboolean bus_input_context_service_set_property (IBusService *service, GDBusConnection *connection, @@ -215,8 +234,21 @@ static const gchar introspection_xml[] = "\n" " \n" /* properties */ + " \n" + " \n" + " \n" + " \n" " \n" " \n" + " \n" + " \n" + " \n" + " \n" /* methods */ " \n" " \n" @@ -353,6 +385,8 @@ bus_input_context_class_init (BusInputContextClass *class) /* override the parent class's implementation. */ IBUS_SERVICE_CLASS (class)->service_method_call = bus_input_context_service_method_call; + IBUS_SERVICE_CLASS (class)->service_get_property = + bus_input_context_service_get_property; IBUS_SERVICE_CLASS (class)->service_set_property = bus_input_context_service_set_property; /* register the xml so that bus_ibus_impl_service_method_call will be @@ -782,6 +816,11 @@ bus_input_context_property_changed (BusInputContext *context, } +typedef struct _PanelProcessKeyEventData { + GDBusMethodInvocation *invocation; + BusInputContext *context; +} PanelProcessKeyEventData; + /** * _panel_process_key_event_cb: * @@ -789,14 +828,21 @@ bus_input_context_property_changed (BusInputContext *context, * bus_panel_proxy_process_key_event() is finished. */ static void -_panel_process_key_event_cb (GObject *source, - GAsyncResult *res, - GDBusMethodInvocation *invocation) +_panel_process_key_event_cb (GObject *source, + GAsyncResult *res, + PanelProcessKeyEventData *data) { GError *error = NULL; GVariant *value = g_dbus_proxy_call_finish ((GDBusProxy *)source, res, &error); + GDBusMethodInvocation *invocation; + BusInputContext *context; + + g_assert (data); + invocation = data->invocation; + context = data->context; + g_slice_free (PanelProcessKeyEventData, data); if (value != NULL) { g_dbus_method_invocation_return_value (invocation, value); g_variant_unref (value); @@ -805,6 +851,7 @@ _panel_process_key_event_cb (GObject *source, g_dbus_method_invocation_return_gerror (invocation, error); g_error_free (error); } + context->processing_key_event = FALSE; } typedef struct _ProcessKeyEventData ProcessKeyEventData; @@ -841,21 +888,27 @@ _ic_process_key_event_reply_cb (GObject *source, gboolean retval = FALSE; g_variant_get (value, "(b)", &retval); if (context->emoji_extension && !retval) { + PanelProcessKeyEventData *pdata = + g_slice_new (PanelProcessKeyEventData); + pdata->invocation = invocation; + pdata->context = context; bus_panel_proxy_process_key_event (context->emoji_extension, keyval, keycode, modifiers, (GAsyncReadyCallback) _panel_process_key_event_cb, - invocation); + pdata); } else { g_dbus_method_invocation_return_value (invocation, value); + context->processing_key_event = FALSE; } g_variant_unref (value); } else { g_dbus_method_invocation_return_gerror (invocation, error); g_error_free (error); + context->processing_key_event = FALSE; } g_object_unref (context); @@ -877,6 +930,8 @@ _ic_process_key_event (BusInputContext *context, guint keycode = 0; guint modifiers = 0; + if (context->use_post_process_key_event) + context->processing_key_event = TRUE; g_variant_get (parameters, "(uuu)", &keyval, &keycode, &modifiers); if (G_UNLIKELY (!context->has_focus)) { /* workaround: set focus if context does not have focus */ @@ -1372,17 +1427,109 @@ bus_input_context_service_method_call (IBusService *service, g_return_if_reached (); } -static void +/** + * _ic_get_post_process_key_event: + * + * Implement the "PostProcessKeyEvent" get property of the + * org.freedesktop.IBus.InputContext interface because currently the Gio + * D-Bus method calls don't support multiple nested tuples likes + * G_VARIANT_TYPE ("((ba(yv)))")) in "ProcessKeyEvent" D-Bus method + * So these post events are separated from the return value "b" of + * the "ProcessKeyEvent" D-Bus method call. + */ +static GVariant * +_ic_get_post_process_key_event (BusInputContext *context, + GDBusConnection *connection, + GError **error) +{ + const char *error_message = NULL; + GVariantBuilder array; + SyncForwardingData *data; + + do { + if (!BUS_IS_INPUT_CONTEXT (context)) { + error_message = "BusInputContext is freed"; + break; + } + if (context->processing_key_event) { + error_message = "Another ProcessKeyEvent is called."; + break; + } + g_variant_builder_init (&array, G_VARIANT_TYPE ("a(yv)")); + while ((data = + g_queue_pop_head (context->queue_during_process_key_event))) { + GVariant *variant = ibus_serializable_serialize_object ( + IBUS_SERIALIZABLE (data->text)); + g_variant_builder_add (&array, "(yv)", data->key, variant); + g_object_unref (data->text); + g_slice_free (SyncForwardingData, data); + } + } while (FALSE); + if (error_message) { + g_set_error (error, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "%s", error_message); + return NULL; + } + return g_variant_builder_end (&array); +} + +static GVariant * +bus_input_context_service_get_property (IBusService *service, + GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error) +{ + int i; + static const struct { + const char *property_name; + GVariant * (* property_callback) (BusInputContext *, + GDBusConnection *, + GError **); + } properties [] = { + { "PostProcessKeyEvent", _ic_get_post_process_key_event }, + }; + + if (error) + *error = NULL; + if (g_strcmp0 (interface_name, IBUS_INTERFACE_INPUT_CONTEXT) != 0) { + return IBUS_SERVICE_CLASS (bus_input_context_parent_class)-> + service_get_property ( + service, connection, sender, object_path, + interface_name, property_name, + error); + } + for (i = 0; i < G_N_ELEMENTS (properties); i++) { + if (g_strcmp0 (properties[i].property_name, property_name) == 0) { + return properties[i].property_callback ((BusInputContext *)service, + connection, + error); + } + } + + g_set_error (error, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "service_get_property received an unknown property: %s", + property_name ? property_name : "(null)"); + g_return_val_if_reached (NULL); +} + +static gboolean _ic_set_content_type (BusInputContext *context, - GVariant *value) + GVariant *value, + GError **error) { guint purpose = 0; guint hints = 0; + gboolean retval = TRUE; g_variant_get (value, "(uu)", &purpose, &hints); if (purpose != context->purpose || hints != context->hints) { - GError *error; - gboolean retval; context->purpose = purpose; context->hints = hints; @@ -1400,24 +1547,30 @@ _ic_set_content_type (BusInputContext *context, context->hints); } - error = NULL; retval = bus_input_context_property_changed (context, "ContentType", value, - &error); - if (!retval) { - g_warning ("Failed to emit PropertiesChanged signal: %s", - error->message); - g_error_free (error); - } + error); } + return retval; } -static void +static gboolean _ic_set_client_commit_preedit (BusInputContext *context, - GVariant *value) + GVariant *value, + GError **error) { g_variant_get (value, "(b)", &context->client_commit_preedit); + return TRUE; +} + +static gboolean +_ic_set_use_post_process_key_event (BusInputContext *context, + GVariant *value, + GError **error) +{ + g_variant_get (value, "(b)", &context->use_post_process_key_event); + return TRUE; } static gboolean @@ -1430,6 +1583,18 @@ bus_input_context_service_set_property (IBusService *service, GVariant *value, GError **error) { + int i; + static const struct { + const char *property_name; + gboolean (* property_callback) (BusInputContext *, + GVariant *, + GError **); + } properties [] = { + { "ContentType", _ic_set_content_type }, + { "ClientCommitPreedit", _ic_set_client_commit_preedit }, + { "EffectivePostProcessKeyEvent", _ic_set_use_post_process_key_event }, + }; + if (error) *error = NULL; if (g_strcmp0 (interface_name, IBUS_INTERFACE_INPUT_CONTEXT) != 0) { @@ -1460,14 +1625,12 @@ bus_input_context_service_set_property (IBusService *service, " "); return FALSE; } - - if (g_strcmp0 (property_name, "ContentType") == 0) { - _ic_set_content_type (BUS_INPUT_CONTEXT (service), value); - return TRUE; - } - if (g_strcmp0 (property_name, "ClientCommitPreedit") == 0) { - _ic_set_client_commit_preedit (BUS_INPUT_CONTEXT (service), value); - return TRUE; + for (i = 0; i < G_N_ELEMENTS (properties); i++) { + if (g_strcmp0 (properties[i].property_name, property_name) == 0) { + return properties[i].property_callback ((BusInputContext *) service, + value, + error); + } } g_set_error (error, @@ -2204,7 +2367,23 @@ _engine_forward_key_event_cb (BusEngineProxy *engine, g_assert (BUS_IS_INPUT_CONTEXT (context)); g_assert (context->engine == engine); - + g_assert (context->queue_during_process_key_event); + + if (context->processing_key_event && g_queue_get_length ( + context->queue_during_process_key_event) <= MAX_SYNC_DATA) { + SyncForwardingData *data; + IBusText *text = ibus_text_new_from_printf ("%u,%u,%u", + keyval, keycode, state); + if (g_queue_get_length (context->queue_during_process_key_event) + == MAX_SYNC_DATA) { + g_warning ("Exceed max number of post process_key_event data"); + } + data = g_slice_new (SyncForwardingData); + data->key = 'f'; + data->text = text; + g_queue_push_tail (context->queue_during_process_key_event, data); + return; + } bus_input_context_emit_signal (context, "ForwardKeyEvent", g_variant_new ("(uuu)", @@ -2455,6 +2634,7 @@ bus_input_context_new (BusConnection *connection, /* it is a fake input context, just need process hotkey */ context->fake = (strncmp (client, "fake", 4) == 0); + context->queue_during_process_key_event = g_queue_new (); if (connection) { g_object_ref_sink (connection); @@ -2938,11 +3118,17 @@ bus_input_context_set_content_type (BusInputContext *context, guint hints) { GVariant *value; + GError *error = NULL; g_assert (BUS_IS_INPUT_CONTEXT (context)); value = g_variant_ref_sink (g_variant_new ("(uu)", purpose, hints)); - _ic_set_content_type (context, value); + _ic_set_content_type (context, value, &error); + if (error) { + g_warning ("Failed to emit PropertiesChanged signal: %s", + error->message); + g_error_free (error); + } g_variant_unref (value); } @@ -2952,12 +3138,24 @@ bus_input_context_commit_text_use_extension (BusInputContext *context, gboolean use_extension) { g_assert (BUS_IS_INPUT_CONTEXT (context)); + g_assert (context->queue_during_process_key_event); if (text == text_empty || text == NULL) return; if (use_extension && context->emoji_extension) { bus_panel_proxy_commit_text_received (context->emoji_extension, text); + } else if (context->processing_key_event && g_queue_get_length ( + context->queue_during_process_key_event) <= MAX_SYNC_DATA) { + SyncForwardingData *data; + if (g_queue_get_length (context->queue_during_process_key_event) + == MAX_SYNC_DATA) { + g_warning ("Exceed max number of sync process_key_event data"); + } + data = g_slice_new (SyncForwardingData); + data->key = 'c'; + data->text = g_object_ref (text); + g_queue_push_tail (context->queue_during_process_key_event, data); } else { GVariant *variant = ibus_serializable_serialize ( (IBusSerializable *)text); diff --git a/client/gtk2/ibusimcontext.c b/client/gtk2/ibusimcontext.c index ea8270bb8..7ccc129de 100644 --- a/client/gtk2/ibusimcontext.c +++ b/client/gtk2/ibusimcontext.c @@ -111,7 +111,7 @@ static guint _signal_delete_surrounding_id = 0; static guint _signal_retrieve_surrounding_id = 0; #if GTK_CHECK_VERSION (3, 98, 4) -static char _use_sync_mode = 2; +static char _use_sync_mode = 1; #else static const gchar *_no_snooper_apps = NO_SNOOPER_APPS; static gboolean _use_key_snooper = ENABLE_SNOOPER; @@ -386,6 +386,7 @@ typedef struct { gboolean retval; } ProcessKeyEventReplyData; + static void _process_key_event_done (GObject *object, GAsyncResult *res, @@ -435,6 +436,7 @@ _process_key_event_done (GObject *object, #endif } + static void _process_key_event_reply_done (GObject *object, GAsyncResult *res, @@ -457,6 +459,7 @@ _process_key_event_reply_done (GObject *object, g_source_remove (data->count_cb_id); } + static gboolean _process_key_event_count_cb (gpointer user_data) { @@ -472,6 +475,101 @@ _process_key_event_count_cb (gpointer user_data) return G_SOURCE_CONTINUE; } + +static gboolean +_process_key_event_sync (IBusInputContext *context, + guint keyval, + guint keycode, + guint state) +{ + gboolean retval; + + g_assert (IBUS_IS_INPUT_CONTEXT (context)); + retval = ibus_input_context_process_key_event (context, + keyval, + keycode - 8, + state); + ibus_input_context_post_process_key_event (context); + return retval; +} + + +static gboolean +_process_key_event_async (IBusInputContext *context, + guint keyval, + guint keycode, + guint state, + GdkEvent *event, + IBusIMContext *ibusimcontext) +{ + ProcessKeyEventData *data = g_slice_new0 (ProcessKeyEventData); + + g_assert (event); + if (!data) { + g_warning ("Cannot allocate async data"); + return _process_key_event_sync (context, keyval, keycode, state); + } +#if GTK_CHECK_VERSION (3, 98, 4) + data->event = gdk_event_ref (event); +#else + data->event = gdk_event_copy (event); +#endif + data->ibusimcontext = ibusimcontext; + ibus_input_context_process_key_event_async (context, + keyval, + keycode - 8, + state, + -1, + NULL, + _process_key_event_done, + data); + + return TRUE; +} + + +static gboolean +_process_key_event_hybrid_async (IBusInputContext *context, + guint keyval, + guint keycode, + guint state) +{ + GSource *source = g_timeout_source_new (1); + ProcessKeyEventReplyData *data = NULL; + gboolean retval = FALSE; + + if (source) + data = g_slice_new0 (ProcessKeyEventReplyData); + if (!data) { + g_warning ("Cannot wait for the reply of the process key event."); + retval = _process_key_event_sync (context, keyval, keycode, state); + if (source) + g_source_destroy (source); + return retval; + } + data->count = 1; + g_source_attach (source, NULL); + g_source_unref (source); + data->count_cb_id = g_source_get_id (source); + ibus_input_context_process_key_event_async (context, + keyval, + keycode - 8, + state, + -1, + NULL, + _process_key_event_reply_done, + data); + g_source_set_callback (source, _process_key_event_count_cb, data, NULL); + while (data->count) + g_main_context_iteration (NULL, TRUE); + /* #2498 Checking source->ref_count might cause Nautilus hang up + */ + retval = data->retval; + g_slice_free (ProcessKeyEventReplyData, data); + return retval; +} + + static gboolean _process_key_event (IBusInputContext *context, #if GTK_CHECK_VERSION (3, 98, 4) @@ -505,70 +603,20 @@ _process_key_event (IBusInputContext *context, switch (_use_sync_mode) { case 1: { - retval = ibus_input_context_process_key_event (context, - keyval, - keycode - 8, - state); + retval = _process_key_event_sync (context, keyval, keycode, state); break; } case 2: { - GSource *source = g_timeout_source_new (1); - ProcessKeyEventReplyData *data = NULL; - - if (source) - data = g_slice_new0 (ProcessKeyEventReplyData); - if (!data) { - g_warning ("Cannot wait for the reply of the process key event."); - retval = ibus_input_context_process_key_event (context, - keyval, - keycode - 8, - state); - if (source) - g_source_destroy (source); - break; - } - data->count = 1; - g_source_attach (source, NULL); - g_source_unref (source); - data->count_cb_id = g_source_get_id (source); - ibus_input_context_process_key_event_async (context, - keyval, - keycode - 8, - state, - -1, - NULL, - _process_key_event_reply_done, - data); - g_source_set_callback (source, _process_key_event_count_cb, data, NULL); - while (data->count) - g_main_context_iteration (NULL, TRUE); - if (source->ref_count > 0) { - /* g_source_get_id() could causes a SEGV */ - g_info ("Broken GSource.ref_count and maybe a timing issue in %p.", - source); - } - retval = data->retval; - g_slice_free (ProcessKeyEventReplyData, data); + retval = _process_key_event_hybrid_async (context, + keyval, keycode, state); break; } default: { - ProcessKeyEventData *data = g_slice_new0 (ProcessKeyEventData); -#if GTK_CHECK_VERSION (3, 98, 4) - data->event = gdk_event_ref (event); -#else - data->event = gdk_event_copy ((GdkEvent *)event); -#endif - data->ibusimcontext = ibusimcontext; - ibus_input_context_process_key_event_async (context, - keyval, - keycode - 8, - state, - -1, - NULL, - _process_key_event_done, - data); - - retval = TRUE; + retval = _process_key_event_async (context, + keyval, keycode, state, + (GdkEvent *)event, + ibusimcontext); + break; } } @@ -877,7 +925,55 @@ ibus_im_context_class_init (IBusIMContextClass *class) g_assert (_signal_retrieve_surrounding_id != 0); #if GTK_CHECK_VERSION (3, 98, 4) - _use_sync_mode = _get_char_env ("IBUS_ENABLE_SYNC_MODE", 2); + /* IBus GtkIMModule, QtIMModlue, ibus-x11, ibus-wayland are called as + * IBus clients. + * Each GTK application, each QT application, Xorg server, Wayland + * comppsitor are called as IBus event owners here. + * + * The IBus client processes the key events between the IBus event owner + * and the IBus daemon and the procedure step is to: + * + * receive the key event from the IBus event owner and forward the + * event to the IBus daemon with the "ProcessKeyEvent" D-Bus method at + * first, + * + * receive the return value from the IBus daemon with the "ProessKeyEvent" + * D-Bus method and forward the value to the IBus event owner secondly and + * the return value includes if the key event is processed normally or not. + * + * The procedure behavior can be changed by the "IBUS_ENABLE_SYNC_MODE" + * environment variable with the synchronous procedure or asynchronous + * one and value is: + * + * 1: Synchronous process key event: + * Wait for the return of the IBus "ProcessKeyEvent" D-Bus method + * synchronously and forward the return value to the IBus event owner + * synchronously. + * 0: Asynchronous process key event: + * Return to the IBus event owner as the key event is processed normally + * at first as soon as the IBus client receives the event from the + * IBus event owner and also forward the event to the IBus daemon with + * the "ProcessKeyEvent" D-Bus method and wait for the return value of + * the D-Bus method *asynchronously*. + * If the return value indicates the key event is disposed by IBus, + * the IBus client does not perform anything. Otherwise the IBus client + * forwards the key event with the gdk_event_put() in GTK3, + * gtk_im_context_filter_key() in GTK4, IMForwardEvent() in XIM API. + * 2: Hybrid asynchronous process key event: + * Wait for the return of the IBus "ProcessKeyEvent" D-Bus method + * *asynchronously* with a GSource loop and forward the return value + * to the IBus event owner synchronously. So IBus clients perform + * virtually synchronously to cover problems of IBus synchronous APIs. + * + * The purpose of the asynchronous process is that each IBus input + * method can process the key events without D-Bus timeout and also + * the IBus synchronous process has a problem that the IBus + * "ProcessKeyEvent" D-Bus method cannot send the commit-text and + * forwar-key-event D-Bus signals until the D-Bus method is finished. + * + * Relative issues: #1713, #2486 + */ + _use_sync_mode = _get_char_env ("IBUS_ENABLE_SYNC_MODE", 1); #else _use_key_snooper = !_get_boolean_env ("IBUS_DISABLE_SNOOPER", !(ENABLE_SNOOPER)); @@ -1004,8 +1100,6 @@ ibus_im_context_init (GObject *obj) #else ibusimcontext->caps = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS; #endif - if (_use_sync_mode == 1) - ibusimcontext->caps |= IBUS_CAP_SYNC_PROCESS_KEY_V2; ibusimcontext->events_queue = g_queue_new (); @@ -2265,6 +2359,8 @@ _create_input_context_done (IBusBus *bus, else { gboolean requested_surrounding_text = FALSE; ibus_input_context_set_client_commit_preedit (context, TRUE); + if (_use_sync_mode == 1) + ibus_input_context_set_post_process_key_event (context, TRUE); ibusimcontext->ibuscontext = context; g_signal_connect (ibusimcontext->ibuscontext, @@ -2489,9 +2585,8 @@ _create_fake_input_context_done (IBusBus *bus, G_CALLBACK (_ibus_fake_context_destroy_cb), NULL); - guint32 caps = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS | IBUS_CAP_SURROUNDING_TEXT; - if (_use_sync_mode == 1) - caps |= IBUS_CAP_SYNC_PROCESS_KEY_V2; + guint32 caps = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS + | IBUS_CAP_SURROUNDING_TEXT; ibus_input_context_set_capabilities (_fake_context, caps); /* focus in/out the fake context */ diff --git a/src/ibusinputcontext.c b/src/ibusinputcontext.c index 28ae04ad9..def23b258 100644 --- a/src/ibusinputcontext.c +++ b/src/ibusinputcontext.c @@ -2,7 +2,7 @@ /* vim:set et sts=4: */ /* ibus - The Input Bus * Copyright (C) 2008-2013 Peng Huang - * Copyright (C) 2018-2022 Takao Fujiwara + * Copyright (C) 2018-2023 Takao Fujiwara * Copyright (C) 2008-2019 Red Hat, Inc. * * This library is free software; you can redistribute it and/or @@ -1190,14 +1190,14 @@ ibus_input_context_set_content_type (IBusInputContext *context, g_assert (IBUS_IS_INPUT_CONTEXT (context)); cached_content_type = - g_dbus_proxy_get_cached_property ((GDBusProxy *) context, + g_dbus_proxy_get_cached_property ((GDBusProxy *)context, "ContentType"); content_type = g_variant_new ("(uu)", purpose, hints); g_variant_ref_sink (content_type); - if (cached_content_type == NULL || + if (!cached_content_type || !g_variant_equal (content_type, cached_content_type)) { - g_dbus_proxy_call ((GDBusProxy *) context, + g_dbus_proxy_call ((GDBusProxy *)context, "org.freedesktop.DBus.Properties.Set", g_variant_new ("(ssv)", IBUS_INTERFACE_INPUT_CONTEXT, @@ -1209,9 +1209,13 @@ ibus_input_context_set_content_type (IBusInputContext *context, NULL, /* callback */ NULL /* user_data */ ); + /* Need to update the cache by manual since there is a timing issue. */ + g_dbus_proxy_set_cached_property ((GDBusProxy *)context, + "ContentType", + content_type); } - if (cached_content_type != NULL) + if (cached_content_type) g_variant_unref (cached_content_type); g_variant_unref (content_type); } @@ -1324,19 +1328,20 @@ void ibus_input_context_set_client_commit_preedit (IBusInputContext *context, gboolean client_commit) { - GVariant *cached_content_type; + GVariant *cached_var_client_commit; GVariant *var_client_commit; g_assert (IBUS_IS_INPUT_CONTEXT (context)); - cached_content_type = - g_dbus_proxy_get_cached_property ((GDBusProxy *) context, + cached_var_client_commit = + g_dbus_proxy_get_cached_property ((GDBusProxy *)context, "ClientCommitPreedit"); var_client_commit = g_variant_new ("(b)", client_commit); g_variant_ref_sink (var_client_commit); - if (cached_content_type == NULL) { - g_dbus_proxy_call ((GDBusProxy *) context, + if (!cached_var_client_commit || + !g_variant_equal (var_client_commit, cached_var_client_commit)) { + g_dbus_proxy_call ((GDBusProxy *)context, "org.freedesktop.DBus.Properties.Set", g_variant_new ("(ssv)", IBUS_INTERFACE_INPUT_CONTEXT, @@ -1348,13 +1353,146 @@ ibus_input_context_set_client_commit_preedit (IBusInputContext *context, NULL, /* callback */ NULL /* user_data */ ); + /* Need to update the cache by manual since there is a timing issue. */ + g_dbus_proxy_set_cached_property ((GDBusProxy *)context, + "ClientCommitPreedit", + var_client_commit); } - if (cached_content_type != NULL) - g_variant_unref (cached_content_type); + if (cached_var_client_commit) + g_variant_unref (cached_var_client_commit); g_variant_unref (var_client_commit); } +void +ibus_input_context_set_post_process_key_event (IBusInputContext *context, + gboolean enable) +{ + GVariant *cached_var_post; + GVariant *var_post; + + g_assert (IBUS_IS_INPUT_CONTEXT (context)); + + cached_var_post = + g_dbus_proxy_get_cached_property ((GDBusProxy *)context, + "EffectivePostProcessKeyEvent"); + var_post = g_variant_new ("(b)", enable); + g_variant_ref_sink (var_post); + if (!cached_var_post || + !g_variant_equal (var_post, cached_var_post)) { + g_dbus_proxy_call ((GDBusProxy *)context, + "org.freedesktop.DBus.Properties.Set", + g_variant_new ("(ssv)", + IBUS_INTERFACE_INPUT_CONTEXT, + "EffectivePostProcessKeyEvent", + var_post), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, /* cancellable */ + NULL, /* callback */ + NULL /* user_data */ + ); + /* Need to update the cache by manual since there is a timing issue. */ + g_dbus_proxy_set_cached_property ((GDBusProxy *)context, + "EffectivePostProcessKeyEvent", + var_post); + } + + if (cached_var_post) + g_variant_unref (cached_var_post); + g_variant_unref (var_post); +} + +void +ibus_input_context_post_process_key_event (IBusInputContext *context) +{ + GVariant *cached_var_post; + gboolean enable = FALSE; + GVariant *result; + GError *error = NULL; + GVariant *variant = NULL; + GVariantIter iter; + gsize size; + char type = 0; + GVariant *vtext = NULL; + + g_assert (IBUS_IS_INPUT_CONTEXT (context)); + + cached_var_post = + g_dbus_proxy_get_cached_property ((GDBusProxy *)context, + "EffectivePostProcessKeyEvent"); + if (cached_var_post) + g_variant_get (cached_var_post, "(b)", &enable); + if (!enable) { + g_warning ("%s: ibus_input_context_set_post_process_key_event() " + "needs to be called before.", + G_STRFUNC); + if (cached_var_post) + g_variant_unref (cached_var_post); + return; + } + g_variant_unref (cached_var_post); + result = g_dbus_proxy_call_sync ( + (GDBusProxy *)context, + "org.freedesktop.DBus.Properties.Get", + g_variant_new ("(ss)", + IBUS_INTERFACE_INPUT_CONTEXT, + "PostProcessKeyEvent"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (error) { + g_warning ("%s: %s", G_STRFUNC, error->message); + g_error_free (error); + return; + } + + g_variant_get (result, "(v)", &variant); + g_assert (variant); + g_variant_iter_init (&iter, variant); + size = g_variant_iter_n_children (&iter); + while (size >0 && g_variant_iter_loop (&iter, "(yv)", &type, &vtext)) { + IBusText *text = + (IBusText *)ibus_serializable_deserialize_object (vtext); + if (!IBUS_IS_TEXT (text)) { + g_warning ("%s: %s", G_STRFUNC, "text is not IBusText"); + break; + } + switch (type) { + case 'c': + g_signal_emit (context, context_signals[COMMIT_TEXT], 0, text); + break; + case 'f': { + gchar **array = NULL; + guint keyval, keycode, state; + array = g_strsplit (text->text, ",", -1); + keyval = g_ascii_strtoull (array[0], NULL, 10); + keycode = g_ascii_strtoull (array[1], NULL, 10); + state = g_ascii_strtoull (array[2], NULL, 10); + g_strfreev (array); + g_signal_emit (context, + context_signals[FORWARD_KEY_EVENT], + 0, + keyval, + keycode, + state | IBUS_FORWARD_MASK); + break; + } + default: + g_warning ("%s: Type '%c' is not supported.", G_STRFUNC, type); + } + if (g_object_is_floating (text)) { + g_object_ref_sink (text); + g_object_unref (text); + } + g_clear_pointer (&vtext, g_variant_unref); + } + + g_variant_unref (variant); + g_variant_unref (result); +} + #define DEFINE_FUNC(name, Name) \ void \ ibus_input_context_##name (IBusInputContext *context) \ diff --git a/src/ibusinputcontext.h b/src/ibusinputcontext.h index 099921489..ca6046700 100644 --- a/src/ibusinputcontext.h +++ b/src/ibusinputcontext.h @@ -2,7 +2,7 @@ /* vim:set et sts=4: */ /* ibus - The Input Bus * Copyright (C) 2008-2013 Peng Huang - * Copyright (C) 2018 Takao Fujiwara + * Copyright (C) 2018-2023 Takao Fujiwara * Copyright (C) 2008-2018 Red Hat, Inc. * * This library is free software; you can redistribute it and/or @@ -298,7 +298,6 @@ gboolean ibus_input_context_process_key_event guint32 keycode, guint32 state); - /** * ibus_input_context_set_cursor_location: * @context: An IBusInputContext. @@ -519,9 +518,38 @@ void ibus_input_context_set_content_type * * See also ibus_engine_update_preedit_text_with_mode(). */ -void ibus_input_context_set_client_commit_preedit ( - IBusInputContext *context, +void ibus_input_context_set_client_commit_preedit + (IBusInputContext *context, gboolean client_commit); +/** + * ibus_input_context_set_post_process_key_event: + * @context: An #IBusInputContext. + * @enable: Can use ibus_input_context_post_process_key_event() to retrieve + * commit-text and forwar-key-event signals during + * calling ibus_input_context_process_key_event() if it's %TRUE. + * + * Since: 1.5.00 + * Stability: Unstable + */ +void ibus_input_context_set_post_process_key_event + (IBusInputContext *context, + gboolean enable); +/** + * ibus_input_context_post_process_key_event: + * @context: An #IBusInputContext. + * + * Call this API after ibus_input_context_process_key_event() returns + * to retrieve commit-text and forwar-key-event signals during + * calling ibus_input_context_process_key_event(). + * + * See also ibus_input_context_set_post_process_key_event(). + * + * Since: 1.5.00 + * Stability: Unstable + */ +void ibus_input_context_post_process_key_event + (IBusInputContext *context); + G_END_DECLS #endif