/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* vim:set et sts=4: */ /* ibus - The Input Bus * Copyright (C) 2017-2019 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 * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include "ibus-portal-dbus.h" typedef struct _IBusPortal IBusPortal; typedef struct _IBusPortalClass IBusPortalClass; typedef struct _IBusPortalContext IBusPortalContext; typedef struct _IBusPortalContextClass IBusPortalContextClass; struct _IBusPortalContext { IBusDbusInputContextSkeleton parent_instance; IBusInputContext *context; guint id; char *owner; char *object_path; IBusDbusService *service; }; struct _IBusPortalContextClass { IBusDbusInputContextSkeletonClass parent_class; }; struct _IBusPortal { IBusDbusPortalSkeleton parent_instance; }; struct _IBusPortalClass { IBusDbusPortalSkeletonClass parent_class; }; enum { PROP_CONTENT_TYPE = 1, PROP_CLIENT_COMMIT_PREEDIT, N_PROPERTIES }; static GMainLoop *loop = NULL; static IBusBus *ibus_bus; static IBusPortal *ibus_portal = NULL; static gboolean opt_verbose; static gboolean opt_replace; static GList *all_contexts = NULL; static guint next_context_id; GType ibus_portal_context_get_type (void) G_GNUC_CONST; static void ibus_portal_context_iface_init (IBusDbusInputContextIface *iface); static void portal_context_g_signal (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters, IBusPortalContext *portal_context); G_DEFINE_TYPE_WITH_CODE (IBusPortalContext, ibus_portal_context, IBUS_DBUS_TYPE_INPUT_CONTEXT_SKELETON, G_IMPLEMENT_INTERFACE (IBUS_DBUS_TYPE_INPUT_CONTEXT, ibus_portal_context_iface_init)); static void _forward_method_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GDBusMethodInvocation *invocation = user_data; GError *error = NULL; GVariant *variant = g_dbus_proxy_call_finish ((GDBusProxy *) source_object, res, &error); if (variant == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); g_error_free (error); return; } g_dbus_method_invocation_return_value (invocation, variant); } static gboolean _forward_method (IBusDbusInputContext *object, GDBusMethodInvocation *invocation) { IBusPortalContext *portal_context = (IBusPortalContext *)object; GDBusMessage *message = g_dbus_method_invocation_get_message (invocation); g_dbus_proxy_call (G_DBUS_PROXY (portal_context->context), g_dbus_method_invocation_get_method_name (invocation), g_dbus_message_get_body (message), G_DBUS_CALL_FLAGS_NONE, -1, NULL, /* cancellable */ _forward_method_cb, invocation); return TRUE; } static gboolean ibus_dbus_context_cancel_hand_writing (IBusDbusInputContext *object, GDBusMethodInvocation *invocation, guint arg_n_strokes) { return _forward_method (object, invocation); } static gboolean ibus_dbus_context_focus_in (IBusDbusInputContext *object, GDBusMethodInvocation *invocation) { return _forward_method (object, invocation); } static gboolean ibus_dbus_context_focus_out (IBusDbusInputContext *object, GDBusMethodInvocation *invocation) { return _forward_method (object, invocation); } static gboolean ibus_dbus_context_get_engine (IBusDbusInputContext *object, GDBusMethodInvocation *invocation) { return _forward_method (object, invocation); } static gboolean ibus_dbus_context_process_hand_writing_event (IBusDbusInputContext *object, GDBusMethodInvocation *invocation, GVariant *arg_coordinates) { return _forward_method (object, invocation); } static gboolean ibus_dbus_context_process_key_event (IBusDbusInputContext *object, GDBusMethodInvocation *invocation, guint arg_keyval, guint arg_keycode, guint arg_state) { return _forward_method (object, invocation); } static gboolean ibus_dbus_context_property_activate (IBusDbusInputContext *object, GDBusMethodInvocation *invocation, const gchar *arg_name, guint arg_state) { return _forward_method (object, invocation); } static gboolean ibus_dbus_context_reset (IBusDbusInputContext *object, GDBusMethodInvocation *invocation) { return _forward_method (object, invocation); } static gboolean ibus_dbus_context_set_capabilities (IBusDbusInputContext *object, GDBusMethodInvocation *invocation, guint arg_caps) { return _forward_method (object, invocation); } static gboolean ibus_dbus_context_set_cursor_location (IBusDbusInputContext *object, GDBusMethodInvocation *invocation, gint arg_x, gint arg_y, gint arg_w, gint arg_h) { return _forward_method (object, invocation); } static gboolean ibus_dbus_context_set_cursor_location_relative (IBusDbusInputContext *object, GDBusMethodInvocation *invocation, gint arg_x, gint arg_y, gint arg_w, gint arg_h) { return _forward_method (object, invocation); } static gboolean ibus_dbus_context_set_engine (IBusDbusInputContext *object, GDBusMethodInvocation *invocation, const gchar *arg_name) { return _forward_method (object, invocation); } static gboolean ibus_dbus_context_set_surrounding_text (IBusDbusInputContext *object, GDBusMethodInvocation *invocation, GVariant *arg_text, guint arg_cursor_pos, guint arg_anchor_pos) { return _forward_method (object, invocation); } static void ibus_portal_context_iface_init (IBusDbusInputContextIface *iface) { iface->handle_cancel_hand_writing = ibus_dbus_context_cancel_hand_writing; iface->handle_focus_in = ibus_dbus_context_focus_in; iface->handle_focus_out = ibus_dbus_context_focus_out; iface->handle_get_engine = ibus_dbus_context_get_engine; iface->handle_process_hand_writing_event = ibus_dbus_context_process_hand_writing_event; iface->handle_process_key_event = ibus_dbus_context_process_key_event; iface->handle_property_activate = ibus_dbus_context_property_activate; iface->handle_reset = ibus_dbus_context_reset; iface->handle_set_capabilities = ibus_dbus_context_set_capabilities; iface->handle_set_cursor_location = ibus_dbus_context_set_cursor_location; iface->handle_set_cursor_location_relative = ibus_dbus_context_set_cursor_location_relative; iface->handle_set_engine = ibus_dbus_context_set_engine; iface->handle_set_surrounding_text = ibus_dbus_context_set_surrounding_text; } static void ibus_portal_context_init (IBusPortalContext *portal_context) { } static void ibus_portal_context_finalize (GObject *object) { IBusPortalContext *portal_context = (IBusPortalContext *)object; all_contexts = g_list_remove (all_contexts, portal_context); g_dbus_interface_skeleton_unexport ( G_DBUS_INTERFACE_SKELETON (portal_context->service)); g_dbus_interface_skeleton_unexport ( G_DBUS_INTERFACE_SKELETON (portal_context)); g_free (portal_context->owner); g_free (portal_context->object_path); g_object_unref (portal_context->service); g_signal_handlers_disconnect_by_func ( portal_context->context, G_CALLBACK(portal_context_g_signal), portal_context); g_object_unref (portal_context->context); G_OBJECT_CLASS (ibus_portal_context_parent_class)->finalize (object); } static void ibus_portal_context_set_property (IBusPortalContext *portal_context, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_CONTENT_TYPE: g_dbus_proxy_call (G_DBUS_PROXY (portal_context->context), "org.freedesktop.DBus.Properties.Set", g_variant_new ("(ssv)", IBUS_INTERFACE_INPUT_CONTEXT, "ContentType", g_value_get_variant (value)), G_DBUS_CALL_FLAGS_NONE, -1, NULL, /* cancellable */ NULL, /* callback */ NULL /* user_data */ ); break; case PROP_CLIENT_COMMIT_PREEDIT: g_dbus_proxy_call (G_DBUS_PROXY (portal_context->context), "org.freedesktop.DBus.Properties.Set", g_variant_new ("(ssv)", IBUS_INTERFACE_INPUT_CONTEXT, "ClientCommitPreedit", g_value_get_variant (value)), G_DBUS_CALL_FLAGS_NONE, -1, NULL, /* cancellable */ NULL, /* callback */ NULL /* user_data */ ); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (portal_context, prop_id, pspec); } } static void ibus_portal_context_get_property (IBusPortalContext *portal_context, guint prop_id, GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_CONTENT_TYPE: case PROP_CLIENT_COMMIT_PREEDIT: g_warning ("No support for setting content type"); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (portal_context, prop_id, pspec); } } static gboolean ibus_portal_context_g_authorize_method (GDBusInterfaceSkeleton *interface, GDBusMethodInvocation *invocation) { IBusPortalContext *portal_context = (IBusPortalContext *)interface; if (g_strcmp0 (g_dbus_method_invocation_get_sender (invocation), portal_context->owner) == 0) { return TRUE; } g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Access denied"); return FALSE; } static void ibus_portal_context_class_init (IBusPortalContextClass *klass) { GObjectClass *gobject_class; GDBusInterfaceSkeletonClass *skeleton_class; gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = ibus_portal_context_finalize; gobject_class->set_property = (GObjectSetPropertyFunc) ibus_portal_context_set_property; gobject_class->get_property = (GObjectGetPropertyFunc) ibus_portal_context_get_property; skeleton_class = G_DBUS_INTERFACE_SKELETON_CLASS(klass); skeleton_class->g_authorize_method = ibus_portal_context_g_authorize_method; ibus_dbus_input_context_override_properties (gobject_class, PROP_CONTENT_TYPE); } static void portal_context_g_signal (GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters, IBusPortalContext *portal_context) { GError *error = NULL; GDBusConnection *connection; if (g_strcmp0 (sender_name, IBUS_SERVICE_IBUS) != 0) return; connection = g_dbus_interface_skeleton_get_connection ( G_DBUS_INTERFACE_SKELETON (portal_context)); if (!g_dbus_connection_emit_signal (connection, portal_context->owner, portal_context->object_path, IBUS_INTERFACE_INPUT_CONTEXT, signal_name, parameters, &error)) { g_warning ("Unable to emit signal %s: %s", signal_name, error->message); g_error_free (error); } g_signal_stop_emission_by_name (proxy, "g-signal"); } static gboolean ibus_portal_context_handle_destroy (IBusDbusService *object, GDBusMethodInvocation *invocation, IBusPortalContext *portal_context) { g_object_unref (portal_context); return FALSE; } static IBusPortalContext * ibus_portal_context_new (IBusInputContext *context, GDBusConnection *connection, const char *owner, GError **error) { IBusPortalContext *portal_context = g_object_new (ibus_portal_context_get_type (), NULL); g_signal_connect (context, "g-signal", G_CALLBACK(portal_context_g_signal), portal_context); portal_context->id = ++next_context_id; portal_context->context = g_object_ref (context); portal_context->owner = g_strdup (owner); portal_context->object_path = g_strdup_printf (IBUS_PATH_INPUT_CONTEXT, portal_context->id); portal_context->service = ibus_dbus_service_skeleton_new (); g_signal_connect (portal_context->service, "handle-destroy", G_CALLBACK (ibus_portal_context_handle_destroy), portal_context); if (!g_dbus_interface_skeleton_export ( G_DBUS_INTERFACE_SKELETON (portal_context->service), connection, portal_context->object_path, error) || !g_dbus_interface_skeleton_export ( G_DBUS_INTERFACE_SKELETON (portal_context), connection, portal_context->object_path, error)) { g_object_unref (portal_context); return NULL; } all_contexts = g_list_prepend (all_contexts, portal_context); return portal_context; } GType ibus_portal_get_type (void) G_GNUC_CONST; static void ibus_portal_iface_init (IBusDbusPortalIface *iface); G_DEFINE_TYPE_WITH_CODE (IBusPortal, ibus_portal, IBUS_DBUS_TYPE_PORTAL_SKELETON, G_IMPLEMENT_INTERFACE (IBUS_DBUS_TYPE_PORTAL, ibus_portal_iface_init)); static void create_input_context_done (IBusBus *bus, GAsyncResult *res, GDBusMethodInvocation *invocation) { GError *error = NULL; IBusInputContext *context; IBusPortalContext *portal_context; context = ibus_bus_create_input_context_async_finish (ibus_bus, res, &error); if (context == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); g_error_free (error); return; } portal_context = ibus_portal_context_new ( context, g_dbus_method_invocation_get_connection (invocation), g_dbus_method_invocation_get_sender (invocation), &error); g_object_unref (context); if (portal_context == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); g_error_free (error); g_object_unref (portal_context); return; } ibus_dbus_portal_complete_create_input_context ( IBUS_DBUS_PORTAL(ibus_portal), invocation, portal_context->object_path); } static gboolean ibus_portal_handle_create_input_context (IBusDbusPortal *object, GDBusMethodInvocation *invocation, const gchar *arg_client_name) { ibus_bus_create_input_context_async ( ibus_bus, arg_client_name, -1, NULL, (GAsyncReadyCallback)create_input_context_done, invocation); return TRUE; } static void ibus_portal_iface_init (IBusDbusPortalIface *iface) { iface->handle_create_input_context = ibus_portal_handle_create_input_context; } static void ibus_portal_init (IBusPortal *portal) { } static void ibus_portal_class_init (IBusPortalClass *klass) { } static void show_version_and_quit (void) { g_print ("%s - Version %s\n", g_get_application_name (), VERSION); exit (EXIT_SUCCESS); } static const GOptionEntry entries[] = { { "version", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_version_and_quit, "Show the application's version.", NULL }, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose, "verbose.", NULL }, { "replace", 'r', 0, G_OPTION_ARG_NONE, &opt_replace, "Replace.", NULL }, { NULL }, }; static void on_bus_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { GError *error = NULL; ibus_portal = g_object_new (ibus_portal_get_type (), NULL); if (!g_dbus_interface_skeleton_export ( G_DBUS_INTERFACE_SKELETON (ibus_portal), connection, IBUS_PATH_IBUS, &error)) { g_warning ("Error exporting portal: %s", error->message); g_error_free (error); return; } } static void on_name_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { } static void on_name_lost (GDBusConnection *connection, const gchar *name, gpointer user_data) { g_main_loop_quit (loop); } static void name_owner_changed (GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { const char *name, *from, *to; g_variant_get (parameters, "(sss)", &name, &from, &to); if (name[0] == ':' && g_strcmp0 (name, from) == 0 && g_strcmp0 (to, "") == 0) { GList *l, *next; /* Client disconnected, free any input contexts it may have */ for (l = all_contexts; l != NULL; l = next) { IBusPortalContext *portal_context = l->data; next = l->next; if (g_strcmp0 (portal_context->owner, name) == 0) { g_object_unref (portal_context); } } } } static void _bus_disconnected_cb (IBusBus *ibusbus) { g_main_loop_quit (loop); } gint main (gint argc, gchar **argv) { GDBusConnection *session_bus = NULL; guint owner_id; setlocale (LC_ALL, ""); GOptionContext *context = g_option_context_new ("- ibus daemon"); g_option_context_add_main_entries (context, entries, "ibus-daemon"); GError *error = NULL; if (!g_option_context_parse (context, &argc, &argv, &error)) { g_printerr ("Option parsing failed: %s\n", error->message); g_error_free (error); exit (-1); } /* Avoid even loading gvfs to avoid accidental confusion */ g_setenv ("GIO_USE_VFS", "local", TRUE); ibus_init (); ibus_set_log_handler (opt_verbose); ibus_bus = ibus_bus_new (); if (!ibus_bus_is_connected (ibus_bus)) { g_printerr ("Not connected to the ibus bus\n"); exit (1); } g_signal_connect (ibus_bus, "disconnected", G_CALLBACK (_bus_disconnected_cb), NULL); loop = g_main_loop_new (NULL, FALSE); session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); if (session_bus == NULL) { g_printerr ("No session bus: %s", error->message); exit (-1); } g_dbus_connection_signal_subscribe (session_bus, "org.freedesktop.DBus", "org.freedesktop.DBus", "NameOwnerChanged", "/org/freedesktop/DBus", NULL, G_DBUS_SIGNAL_FLAGS_NONE, name_owner_changed, NULL, NULL); owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, IBUS_SERVICE_PORTAL, G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | (opt_replace ? G_BUS_NAME_OWNER_FLAGS_REPLACE : 0), on_bus_acquired, on_name_acquired, on_name_lost, NULL, NULL); g_main_loop_run (loop); g_bus_unown_name (owner_id); g_main_loop_unref (loop); return 0; }