/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ /* vim:set et sts=4: */ /* ibus - The Input Bus * Copyright (C) 2008-2013 Peng Huang * Copyright (C) 2008-2013 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 "dbusimpl.h" #include #include "global.h" #include "ibusimpl.h" #include "marshalers.h" #include "matchrule.h" #include "types.h" enum { NAME_OWNER_CHANGED, NAME_LOST, NAME_ACQUIRED, LAST_SIGNAL, }; enum { PROP_0, }; static guint dbus_signals[LAST_SIGNAL] = { 0 }; struct _BusDBusImpl { IBusService parent; /* instance members */ /* a map from a unique bus name (e.g. ":1.0") to a BusConnection. */ GHashTable *unique_names; /* a map from a requested well-known name (e.g. "org.freedesktop.IBus.Panel") to a BusNameService. */ GHashTable *names; /* a list of IBusService objects. */ GList *objects; /* a list of active BusConnections. */ GList *connections; /* a list of BusMatchRules requested by the connections above. */ GList *rules; /* a serial number used to generate a unique name of a bus. */ guint id; GMutex dispatch_lock; GList *dispatch_queue; GMutex forward_lock; GList *forward_queue; /* a list of BusMethodCall to be used to reply when services are really available */ GList *start_service_calls; }; struct _BusDBusImplClass { IBusServiceClass parent; /* class members */ void (* name_owner_changed) (BusDBusImpl *dbus, BusConnection *connection, gchar *name, gchar *old_name, gchar *new_name); void (* name_lost) (BusDBusImpl *dbus, BusConnection *connection, gchar *name); void (* name_acquired) (BusDBusImpl *dbus, BusConnection *connection, gchar *name); }; typedef struct _BusDispatchData BusDispatchData; struct _BusDispatchData { GDBusMessage *message; BusConnection *skip_connection; }; typedef struct _BusNameService BusNameService; struct _BusNameService { gchar *name; GSList *owners; }; typedef struct _BusConnectionOwner BusConnectionOwner; struct _BusConnectionOwner { BusConnection *conn; guint allow_replacement : 1; guint do_not_queue : 1; }; typedef struct _BusMethodCall BusMethodCall; struct _BusMethodCall { BusDBusImpl *dbus; BusConnection *connection; GVariant *parameters; GDBusMethodInvocation *invocation; guint timeout_id; }; /* functions prototype */ static void bus_dbus_impl_destroy (BusDBusImpl *dbus); static void bus_dbus_impl_service_method_call (IBusService *service, GDBusConnection *dbus_connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation); static GVariant *bus_dbus_impl_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_dbus_impl_service_set_property (IBusService *service, GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant *value, GError **error); static void bus_dbus_impl_name_owner_changed (BusDBusImpl *dbus, BusConnection *connection, gchar *name, gchar *old_name, gchar *new_name); static void bus_dbus_impl_name_lost (BusDBusImpl *dbus, BusConnection *connection, gchar *name); static void bus_dbus_impl_name_acquired (BusDBusImpl *dbus, BusConnection *connection, gchar *name); static void bus_dbus_impl_connection_destroy_cb (BusConnection *connection, BusDBusImpl *dbus); static void bus_dbus_impl_rule_destroy_cb (BusMatchRule *rule, BusDBusImpl *dbus); static void bus_dbus_impl_object_destroy_cb(IBusService *object, BusDBusImpl *dbus); G_DEFINE_TYPE(BusDBusImpl, bus_dbus_impl, IBUS_TYPE_SERVICE) /* The D-Bus interfaces available in this class, which consists of a list of methods this class implements and * a list of signals this class may emit. See bus_dbus_impl_new_connection and ibusservice.c for more details. */ static const gchar introspection_xml[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; static void bus_connection_owner_set_flags (BusConnectionOwner *owner, guint32 flags) { owner->allow_replacement = (flags & IBUS_BUS_NAME_FLAG_ALLOW_REPLACEMENT) != 0; owner->do_not_queue = (flags & IBUS_BUS_NAME_FLAG_DO_NOT_QUEUE) != 0; } static BusConnectionOwner * bus_connection_owner_new (BusConnection *connection, guint32 flags) { BusConnectionOwner *owner = NULL; g_assert (BUS_IS_CONNECTION (connection)); owner = g_slice_new (BusConnectionOwner); if (owner != NULL) { owner->conn = g_object_ref (connection); bus_connection_owner_set_flags (owner, flags); } return owner; } static void bus_connection_owner_free (BusConnectionOwner *owner) { g_assert (owner != NULL); g_object_unref (owner->conn); owner->conn = NULL; g_slice_free (BusConnectionOwner, owner); } static GSList * bus_name_service_find_owner_link (BusNameService *service, BusConnection *connection) { GSList *owners = service->owners; while (owners) { BusConnectionOwner *owner = (BusConnectionOwner *) owners->data; if (owner->conn == connection) { break; } owners = owners->next; } return owners; } static BusNameService * bus_name_service_new (const gchar *name) { BusNameService *service = NULL; g_assert (name != NULL); service = g_slice_new (BusNameService); g_assert (service != NULL); service->name = g_strdup (name); service->owners = NULL; return service; } static void bus_name_service_free (BusNameService *service) { g_assert (service != NULL); g_slist_free_full (service->owners, (GDestroyNotify) bus_connection_owner_free); service->owners = NULL; g_free (service->name); g_slice_free (BusNameService, service); } static void bus_name_service_set_primary_owner (BusNameService *service, BusConnectionOwner *owner, BusDBusImpl *dbus) { g_assert (service != NULL); g_assert (owner != NULL); g_assert (dbus != NULL); BusConnectionOwner *old = service->owners != NULL ? (BusConnectionOwner *)service->owners->data : NULL; if (old != NULL) { g_signal_emit (dbus, dbus_signals[NAME_LOST], 0, old->conn, service->name); } g_signal_emit (dbus, dbus_signals[NAME_ACQUIRED], 0, owner->conn, service->name ? service->name : ""); g_signal_emit (dbus, dbus_signals[NAME_OWNER_CHANGED], 0, owner->conn, service->name, old != NULL ? bus_connection_get_unique_name (old->conn) : "", bus_connection_get_unique_name (owner->conn)); if (old != NULL && old->do_not_queue != 0) { /* If old primary owner does not want to be in queue, we remove it. */ service->owners = g_slist_remove (service->owners, old); bus_connection_remove_name (old->conn, service->name); bus_connection_owner_free (old); } service->owners = g_slist_prepend (service->owners, (gpointer) owner); } static BusConnectionOwner * bus_name_service_get_primary_owner (BusNameService *service) { g_assert (service != NULL); if (service->owners == NULL) { return NULL; } return (BusConnectionOwner *) service->owners->data; } static void bus_name_service_add_non_primary_owner (BusNameService *service, BusConnectionOwner *owner, BusDBusImpl *dbus) { g_assert (service != NULL); g_assert (owner != NULL); g_assert (dbus != NULL); g_assert (service->owners != NULL); service->owners = g_slist_append (service->owners, (gpointer) owner); } static BusConnectionOwner * bus_name_service_find_owner (BusNameService *service, BusConnection *connection) { g_assert (service != NULL); g_assert (connection != NULL); GSList *owners = bus_name_service_find_owner_link (service, connection); if (owners != NULL) return (BusConnectionOwner *)owners->data; return NULL; } static void bus_name_service_remove_owner (BusNameService *service, BusConnectionOwner *owner, BusDBusImpl *dbus) { GSList *owners; g_assert (service != NULL); g_assert (owner != NULL); owners = bus_name_service_find_owner_link (service, owner->conn); g_assert (owners != NULL); if (owners->data == bus_name_service_get_primary_owner (service)) { BusConnectionOwner *_new = NULL; if (owners->next != NULL) { _new = (BusConnectionOwner *)owners->next->data; } if (dbus != NULL) { g_signal_emit (dbus, dbus_signals[NAME_LOST], 0, owner->conn, service->name); if (_new != NULL) { g_signal_emit (dbus, dbus_signals[NAME_ACQUIRED], 0, _new->conn, service->name); } g_signal_emit (dbus, dbus_signals[NAME_OWNER_CHANGED], 0, _new != NULL ? _new->conn : NULL, service->name, bus_connection_get_unique_name (owner->conn), _new != NULL ? bus_connection_get_unique_name (_new->conn) : ""); } } service->owners = g_slist_remove_link (service->owners, (gpointer) owners); } static gboolean bus_name_service_get_allow_replacement (BusNameService *service) { BusConnectionOwner *owner = NULL; g_assert (service != NULL); owner = bus_name_service_get_primary_owner (service); if (owner == NULL) { return TRUE; } return owner->allow_replacement; } static BusMethodCall * bus_method_call_new (BusDBusImpl *dbus, BusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { BusMethodCall *call = g_slice_new0 (BusMethodCall); call->dbus = g_object_ref (dbus); call->connection = g_object_ref (connection); call->parameters = g_variant_ref (parameters); call->invocation = g_object_ref (invocation); return call; } static void bus_method_call_free (BusMethodCall *call) { if (call->timeout_id != 0) { g_source_remove (call->timeout_id); } g_object_unref (call->dbus); g_object_unref (call->connection); g_variant_unref (call->parameters); g_object_unref (call->invocation); g_slice_free (BusMethodCall, call); } static void bus_dbus_impl_class_init (BusDBusImplClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); IBUS_OBJECT_CLASS (gobject_class)->destroy = (IBusObjectDestroyFunc) bus_dbus_impl_destroy; /* override the default implementations in the parent class. */ IBUS_SERVICE_CLASS (class)->service_method_call = bus_dbus_impl_service_method_call; IBUS_SERVICE_CLASS (class)->service_get_property = bus_dbus_impl_service_get_property; IBUS_SERVICE_CLASS (class)->service_set_property = bus_dbus_impl_service_set_property; ibus_service_class_add_interfaces (IBUS_SERVICE_CLASS (class), introspection_xml); /* register a handler of the name-owner-changed signal below. */ class->name_owner_changed = bus_dbus_impl_name_owner_changed; /* register a handler of the name-lost signal below. */ class->name_lost = bus_dbus_impl_name_lost; /* register a handler of the name-acquired signal below. */ class->name_acquired = bus_dbus_impl_name_acquired; /* install signals */ dbus_signals[NAME_OWNER_CHANGED] = g_signal_new (I_("name-owner-changed"), G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (BusDBusImplClass, name_owner_changed), NULL, NULL, bus_marshal_VOID__OBJECT_STRING_STRING_STRING, G_TYPE_NONE, 4, BUS_TYPE_CONNECTION, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); dbus_signals[NAME_LOST] = g_signal_new (I_("name-lost"), G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (BusDBusImplClass, name_lost), NULL, NULL, bus_marshal_VOID__OBJECT_STRING, G_TYPE_NONE, 2, BUS_TYPE_CONNECTION, G_TYPE_STRING); dbus_signals[NAME_ACQUIRED] = g_signal_new (I_("name-acquired"), G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (BusDBusImplClass, name_acquired), NULL, NULL, bus_marshal_VOID__OBJECT_STRING, G_TYPE_NONE, 2, BUS_TYPE_CONNECTION, G_TYPE_STRING); } static void bus_dbus_impl_init (BusDBusImpl *dbus) { dbus->unique_names = g_hash_table_new (g_str_hash, g_str_equal); dbus->names = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) bus_name_service_free); g_mutex_init (&dbus->dispatch_lock); g_mutex_init (&dbus->forward_lock); /* other members are automatically zero-initialized. */ } static void bus_dbus_impl_destroy (BusDBusImpl *dbus) { GList *p; for (p = dbus->objects; p != NULL; p = p->next) { IBusService *object = (IBusService *) p->data; g_signal_handlers_disconnect_by_func (object, G_CALLBACK (bus_dbus_impl_object_destroy_cb), dbus); ibus_object_destroy ((IBusObject *) object); g_object_unref (object); } g_list_free (dbus->objects); dbus->objects = NULL; for (p = dbus->rules; p != NULL; p = p->next) { BusMatchRule *rule = BUS_MATCH_RULE (p->data); g_signal_handlers_disconnect_by_func (rule, G_CALLBACK (bus_dbus_impl_rule_destroy_cb), dbus); ibus_object_destroy ((IBusObject *) rule); g_object_unref (rule); } g_list_free (dbus->rules); dbus->rules = NULL; for (p = dbus->connections; p != NULL; p = p->next) { BusConnection *connection = BUS_CONNECTION (p->data); g_signal_handlers_disconnect_by_func (connection, bus_dbus_impl_connection_destroy_cb, dbus); ibus_object_destroy (IBUS_OBJECT (connection)); g_object_unref (connection); } g_list_free (dbus->connections); dbus->connections = NULL; g_hash_table_remove_all (dbus->unique_names); g_hash_table_remove_all (dbus->names); dbus->unique_names = NULL; dbus->names = NULL; g_list_free_full (dbus->start_service_calls, (GDestroyNotify) bus_method_call_free); dbus->start_service_calls = NULL; g_mutex_clear (&dbus->dispatch_lock); g_mutex_clear (&dbus->forward_lock); /* FIXME destruct _lock and _queue members. */ IBUS_OBJECT_CLASS(bus_dbus_impl_parent_class)->destroy ((IBusObject *) dbus); } /** * bus_dbus_impl_hello: * * Implement the "Hello" method call of the org.freedesktop.DBus interface. * Assign a unique bus name like ":1.0" for the connection and return the name (as a D-Bus reply.) */ static void bus_dbus_impl_hello (BusDBusImpl *dbus, BusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { if (bus_connection_get_unique_name (connection) != NULL) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Already handled an Hello message"); } else { gchar *name = g_strdup_printf (":1.%u", ++dbus->id); bus_connection_set_unique_name (connection, name); g_free (name); name = (gchar *) bus_connection_get_unique_name (connection); g_hash_table_insert (dbus->unique_names, name, connection); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", name)); g_signal_emit (dbus, dbus_signals[NAME_OWNER_CHANGED], 0, connection, name, "", name); } } /** * bus_dbus_impl_list_names: * * Implement the "ListNames" method call of the org.freedesktop.DBus interface. * Return all bus names (e.g. ":1.0", "org.freedesktop.IBus.Panel") as a D-Bus reply. */ static void bus_dbus_impl_list_names (BusDBusImpl *dbus, BusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { GVariantBuilder builder; g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); /* FIXME should add them? */ g_variant_builder_add (&builder, "s", "org.freedesktop.DBus"); g_variant_builder_add (&builder, "s", "org.freedesktop.IBus"); /* append well-known names */ GList *names, *name; names = g_hash_table_get_keys (dbus->names); for (name = names; name != NULL; name = name->next) { g_variant_builder_add (&builder, "s", name->data); } g_list_free (names); /* append unique names */ names = g_hash_table_get_keys (dbus->unique_names); for (name = names; name != NULL; name = name->next) { g_variant_builder_add (&builder, "s", name->data); } g_list_free (names); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(as)", &builder)); } /** * bus_dbus_impl_list_names: * * Implement the "NameHasOwner" method call of the org.freedesktop.DBus interface. * Return TRUE (as a D-Bus reply) if the name is available in dbus->unique_names or is a well-known name. */ static void bus_dbus_impl_name_has_owner (BusDBusImpl *dbus, BusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { const gchar *name = NULL; g_variant_get (parameters, "(&s)", &name); gboolean has_owner; if (!g_dbus_is_name (name)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "'%s' is not a legal bus name", name ? name : "(null)"); return; } if (g_dbus_is_unique_name (name)) { has_owner = g_hash_table_lookup (dbus->unique_names, name) != NULL; } else { if (g_strcmp0 (name, "org.freedesktop.DBus") == 0 || g_strcmp0 (name, "org.freedesktop.IBus") == 0) has_owner = TRUE; else has_owner = g_hash_table_lookup (dbus->names, name) != NULL; } g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", has_owner)); } /** * bus_dbus_impl_get_name_owner: * * Implement the "GetNameOwner" method call of the org.freedesktop.DBus interface. */ static void bus_dbus_impl_get_name_owner (BusDBusImpl *dbus, BusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { const gchar *name_owner = NULL; const gchar *name = NULL; g_variant_get (parameters, "(&s)", &name); if (g_strcmp0 (name, "org.freedesktop.DBus") == 0 || g_strcmp0 (name, "org.freedesktop.IBus") == 0) { name_owner = name; } else { BusConnection *owner = bus_dbus_impl_get_connection_by_name (dbus, name); if (owner != NULL) { name_owner = bus_connection_get_unique_name (owner); } } if (name_owner == NULL) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER, "Can not get name owner of '%s': no such name", name); } else { g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", name_owner)); } } /** * bus_dbus_impl_list_queued_owners: * * Implement the "ListQueuedOwners" method call of the org.freedesktop.DBus interface. */ static void bus_dbus_impl_list_queued_owners (BusDBusImpl *dbus, BusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { const gchar *name = NULL; const gchar *name_owner = NULL; GVariantBuilder builder; BusConnection *named_conn = NULL; g_variant_get (parameters, "(&s)", &name); g_assert (BUS_IS_DBUS_IMPL (dbus)); g_assert (name != NULL); g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); if (G_LIKELY (g_dbus_is_unique_name (name))) { named_conn = (BusConnection *) g_hash_table_lookup (dbus->unique_names, name); if (named_conn == NULL) { g_dbus_method_invocation_return_value (invocation, g_variant_new ("(as)", &builder)); return; } name_owner = bus_connection_get_unique_name (named_conn); if (name_owner == NULL) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER, "Can not get name owner of '%s': no such name", name); return; } g_variant_builder_add (&builder, "s", name_owner); } else { BusNameService *service; GSList *owners; service = (BusNameService *) g_hash_table_lookup (dbus->names, name); if (service == NULL) { g_dbus_method_invocation_return_value (invocation, g_variant_new ("(as)", &builder)); return; } for (owners = service->owners; owners; owners = owners->next) { BusConnectionOwner *owner = (BusConnectionOwner *) owners->data; if (owner == NULL) { continue; } named_conn = owner->conn; if (named_conn == NULL) { continue; } name_owner = bus_connection_get_unique_name (named_conn); if (name_owner == NULL) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER, "Can not get name owner of '%s': no such name", name); return; } g_variant_builder_add (&builder, "s", name_owner); } } g_dbus_method_invocation_return_value (invocation, g_variant_new ("(as)", &builder)); } /** * bus_dbus_impl_get_id: * * Implement the "GetId" method call of the org.freedesktop.DBus interface. * This function is not implemented yet and always returns a dummy string - "FIXME". */ static void bus_dbus_impl_get_id (BusDBusImpl *dbus, BusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { /* FIXME */ const gchar *uuid = "FIXME"; g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", uuid)); } /** * bus_dbus_impl_rule_destroy_cb: * * A function to be called when one of the dbus->rules is destroyed. */ static void bus_dbus_impl_rule_destroy_cb (BusMatchRule *rule, BusDBusImpl *dbus) { dbus->rules = g_list_remove (dbus->rules, rule); g_object_unref (rule); } /** * bus_dbus_impl_get_id: * * Implement the "AddMatch" method call of the org.freedesktop.DBus interface. */ static void bus_dbus_impl_add_match (BusDBusImpl *dbus, BusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { const gchar *rule_text = NULL; g_variant_get (parameters, "(&s)", &rule_text); BusMatchRule *rule = bus_match_rule_new (rule_text); if (rule == NULL) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_MATCH_RULE_INVALID, "Parse match rule [%s] failed", rule_text); return; } g_dbus_method_invocation_return_value (invocation, NULL); GList *p; for (p = dbus->rules; p != NULL; p = p->next) { if (bus_match_rule_is_equal (rule, (BusMatchRule *) p->data)) { /* The same rule is already registered. Just reuse it. */ bus_match_rule_add_recipient ((BusMatchRule *) p->data, connection); g_object_unref (rule); return; } } if (rule) { bus_match_rule_add_recipient (rule, connection); dbus->rules = g_list_append (dbus->rules, rule); g_signal_connect (rule, "destroy", G_CALLBACK (bus_dbus_impl_rule_destroy_cb), dbus); } } /** * bus_dbus_impl_get_id: * * Implement the "RemoveMatch" method call of the org.freedesktop.DBus interface. */ static void bus_dbus_impl_remove_match (BusDBusImpl *dbus, BusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { const gchar *rule_text = NULL; g_variant_get (parameters, "(&s)", &rule_text); BusMatchRule *rule = bus_match_rule_new (rule_text); if (rule == NULL) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_MATCH_RULE_INVALID, "Parse match rule [%s] failed", rule_text); return; } g_dbus_method_invocation_return_value (invocation, NULL); GList *p; for (p = dbus->rules; p != NULL; p = p->next) { if (bus_match_rule_is_equal (rule, (BusMatchRule *) p->data)) { /* p->data will be destroyed when the final recipient is removed. */ bus_match_rule_remove_recipient ((BusMatchRule *) p->data, connection); break; } /* FIXME should we return G_DBUS_ERROR if rule is not found in dbus->rules */ } g_object_unref (rule); } /** * bus_dbus_impl_request_name: * * Implement the "RequestName" method call of the org.freedesktop.DBus interface. */ static void bus_dbus_impl_request_name (BusDBusImpl *dbus, BusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { const gchar *name = NULL; // e.g. "org.freedesktop.IBus.Panel" guint32 flags = 0; BusNameService *service = NULL; BusConnectionOwner *primary_owner = NULL; BusConnectionOwner *owner = NULL; g_variant_get (parameters, "(&su)", &name, &flags); if (name == NULL || !g_dbus_is_name (name) || g_dbus_is_unique_name (name)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "'%s' is not a legal service name.", name); return; } if (g_strcmp0 (name, "org.freedesktop.DBus") == 0 || g_strcmp0 (name, "org.freedesktop.IBus") == 0) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Can not acquire the service name '%s', it is reserved by IBus", name); return; } enum { ACTION_INVALID, ACTION_IN_QUEUE, ACTION_REPLACE, ACTION_EXISTS, ACTION_ALREADY_OWN, } action = ACTION_INVALID; service = (BusNameService *) g_hash_table_lookup (dbus->names, name); /* If the name servise does not exist, we will create one. */ if (service == NULL) { service = bus_name_service_new (name); g_hash_table_insert (dbus->names, service->name, service); } else { primary_owner = bus_name_service_get_primary_owner (service); } if (primary_owner != NULL) { if (primary_owner->conn == connection) { action = ACTION_ALREADY_OWN; } else { action = (flags & IBUS_BUS_NAME_FLAG_DO_NOT_QUEUE) ? ACTION_EXISTS : ACTION_IN_QUEUE; if ((bus_name_service_get_allow_replacement (service) == TRUE) && (flags & IBUS_BUS_NAME_FLAG_REPLACE_EXISTING)) { action = ACTION_REPLACE; } } } else { action = ACTION_REPLACE; } if (action == ACTION_ALREADY_OWN) { g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", IBUS_BUS_REQUEST_NAME_REPLY_ALREADY_OWNER)); return; } owner = bus_name_service_find_owner (service, connection); /* If connection already in queue, we need remove it at first. */ if (owner != NULL) { bus_connection_remove_name (connection, name); bus_name_service_remove_owner (service, owner, NULL); bus_connection_owner_free (owner); } switch (action) { case ACTION_EXISTS: g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", IBUS_BUS_REQUEST_NAME_REPLY_EXISTS)); return; case ACTION_IN_QUEUE: owner = bus_connection_owner_new (connection, flags); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", IBUS_BUS_REQUEST_NAME_REPLY_IN_QUEUE)); bus_name_service_add_non_primary_owner (service, owner, dbus); return; case ACTION_REPLACE: bus_connection_add_name (connection, name); owner = bus_connection_owner_new (connection, flags); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", IBUS_BUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)); bus_name_service_set_primary_owner (service, owner, dbus); return; default: g_assert_not_reached (); } } /** * bus_dbus_impl_release_name: * * Implement the "ReleaseName" method call of the org.freedesktop.DBus interface. */ static void bus_dbus_impl_release_name (BusDBusImpl *dbus, BusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { const gchar *name= NULL; BusNameService *service; g_variant_get (parameters, "(&s)", &name); if (name == NULL || !g_dbus_is_name (name) || g_dbus_is_unique_name (name)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "'%s' is not a legal service name.", name); return; } if (g_strcmp0 (name, "org.freedesktop.DBus") == 0 || g_strcmp0 (name, "org.freedesktop.IBus") == 0) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Service name '%s' is owned by IBus.", name); return; } guint retval; service = g_hash_table_lookup (dbus->names, name); if (service == NULL) { retval = 2; /* DBUS_RELEASE_NAME_REPLY_NON_EXISTENT */ } else { /* "ReleaseName" method removes the name in connection->names * and the connection owner. * bus_dbus_impl_connection_destroy_cb() removes all * connection->names and the connection owners. * See also comments in bus_dbus_impl_connection_destroy_cb(). */ if (bus_connection_remove_name (connection, name)) { BusConnectionOwner *owner = bus_name_service_find_owner (service, connection); bus_name_service_remove_owner (service, owner, dbus); if (service->owners == NULL) { g_hash_table_remove (dbus->names, service->name); } bus_connection_owner_free (owner); retval = 1; /* DBUS_RELEASE_NAME_REPLY_RELEASED */ } else { retval = 3; /* DBUS_RELEASE_NAME_REPLY_NOT_OWNER */ } } g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", retval)); } static gboolean start_service_timeout_cb (BusMethodCall *call) { const gchar *name= NULL; guint32 flags; /* currently not used in the D-Bus spec */ g_variant_get (call->parameters, "(&su)", &name, &flags); g_dbus_method_invocation_return_error (call->invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Timeout reached before starting %s", name); GList *p = g_list_find (call->dbus->start_service_calls, call); g_return_val_if_fail (p != NULL, FALSE); bus_method_call_free ((BusMethodCall *) p->data); call->dbus->start_service_calls = g_list_delete_link (call->dbus->start_service_calls, p); return FALSE; } /** * bus_dbus_impl_start_service_by_name: * * Implement the "StartServiceByName" method call of the * org.freedesktop.DBus interface. */ static void bus_dbus_impl_start_service_by_name (BusDBusImpl *dbus, BusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { const gchar *name= NULL; guint32 flags; /* currently not used in the D-Bus spec */ g_variant_get (parameters, "(&su)", &name, &flags); if (name == NULL || !g_dbus_is_name (name) || g_dbus_is_unique_name (name)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "'%s' is not a legal service name.", name); return; } if (g_strcmp0 (name, "org.freedesktop.DBus") == 0 || g_strcmp0 (name, "org.freedesktop.IBus") == 0) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Service name '%s' is owned by IBus.", name); return; } if (g_hash_table_lookup (dbus->names, name) != NULL) { g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", IBUS_BUS_START_REPLY_ALREADY_RUNNING)); return; } BusComponent *component = bus_ibus_impl_lookup_component_by_name ( BUS_DEFAULT_IBUS, name); if (component == NULL || !bus_component_start (component, g_verbose)) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Failed to start %s", name); return; } BusMethodCall *call = bus_method_call_new (dbus, connection, parameters, invocation); call->timeout_id = g_timeout_add (g_gdbus_timeout, (GSourceFunc) start_service_timeout_cb, call); dbus->start_service_calls = g_list_prepend (dbus->start_service_calls, (gpointer) call); } /** * bus_dbus_impl_name_owner_changed: * * The function is called on name-owner-changed signal, typically when g_signal_emit (dbus, NAME_OWNER_CHANGED) * is called, and broadcasts the signal to clients. */ static void bus_dbus_impl_name_owner_changed (BusDBusImpl *dbus, BusConnection *connection, gchar *name, gchar *old_owner, gchar *new_owner) { g_assert (BUS_IS_DBUS_IMPL (dbus)); g_assert (name != NULL); g_assert (old_owner != NULL); g_assert (new_owner != NULL); GDBusMessage *message = g_dbus_message_new_signal ("/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged"); g_dbus_message_set_sender (message, "org.freedesktop.DBus"); /* set a non-zero serial to make libdbus happy */ g_dbus_message_set_serial (message, 1); g_dbus_message_set_body (message, g_variant_new ("(sss)", name, old_owner, new_owner)); /* broadcast the message to clients that listen to the signal. */ bus_dbus_impl_dispatch_message_by_rule (dbus, message, NULL); g_object_unref (message); } /** * bus_dbus_impl_name_lost: * * The function is called on name-lost signal, typically when g_signal_emit (dbus, NAME_LOST) * is called, and broadcasts the signal to clients. */ static void bus_dbus_impl_name_lost (BusDBusImpl *dbus, BusConnection *connection, gchar *name) { g_assert (BUS_IS_DBUS_IMPL (dbus)); g_assert (name != NULL); GDBusMessage *message = g_dbus_message_new_signal ("/org/freedesktop/DBus", "org.freedesktop.DBus", "NameLost"); g_dbus_message_set_sender (message, "org.freedesktop.DBus"); g_dbus_message_set_destination (message, bus_connection_get_unique_name (connection)); /* set a non-zero serial to make libdbus happy */ g_dbus_message_set_serial (message, 1); g_dbus_message_set_body (message, g_variant_new ("(s)", name)); bus_dbus_impl_forward_message (dbus, connection, message); g_object_unref (message); } /** * bus_dbus_impl_name_acquired: * * The function is called on name-acquired signal, typically when g_signal_emit (dbus, NAME_ACQUIRED) * is called, and broadcasts the signal to clients. */ static void bus_dbus_impl_name_acquired (BusDBusImpl *dbus, BusConnection *connection, gchar *name) { g_assert (BUS_IS_DBUS_IMPL (dbus)); g_assert (name != NULL); GDBusMessage *message = g_dbus_message_new_signal ("/org/freedesktop/DBus", "org.freedesktop.DBus", "NameAcquired"); g_dbus_message_set_sender (message, "org.freedesktop.DBus"); g_dbus_message_set_destination (message, bus_connection_get_unique_name (connection)); /* set a non-zero serial to make libdbus happy */ g_dbus_message_set_serial (message, 1); g_dbus_message_set_body (message, g_variant_new ("(s)", name)); bus_dbus_impl_forward_message (dbus, connection, message); g_object_unref (message); GList *p = dbus->start_service_calls; while (p != NULL) { BusMethodCall *call = p->data; const gchar *_name= NULL; guint32 flags; GList *next = p->next; g_variant_get (call->parameters, "(&su)", &_name, &flags); if (g_strcmp0 (name, _name) == 0) { g_dbus_method_invocation_return_value (call->invocation, g_variant_new ("(u)", IBUS_BUS_START_REPLY_SUCCESS)); bus_method_call_free ((BusMethodCall *) p->data); dbus->start_service_calls = g_list_delete_link (dbus->start_service_calls, p); } p = next; } } /** * bus_dbus_impl_service_method_call: * * Handle a D-Bus method call from a client. This function overrides an implementation in src/ibusservice.c. */ static void bus_dbus_impl_service_method_call (IBusService *service, GDBusConnection *dbus_connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation) { BusDBusImpl *dbus = BUS_DBUS_IMPL (service); if (g_strcmp0 (interface_name, "org.freedesktop.DBus") != 0) { IBUS_SERVICE_CLASS (bus_dbus_impl_parent_class)->service_method_call ( (IBusService *) dbus, dbus_connection, sender, object_path, interface_name, method_name, parameters, invocation); return; } static const struct { const gchar *method_name; void (* method) (BusDBusImpl *, BusConnection *, GVariant *, GDBusMethodInvocation *); } methods[] = { /* DBus interface */ { "Hello", bus_dbus_impl_hello }, { "ListNames", bus_dbus_impl_list_names }, { "NameHasOwner", bus_dbus_impl_name_has_owner }, { "GetNameOwner", bus_dbus_impl_get_name_owner }, { "ListQueuedOwners", bus_dbus_impl_list_queued_owners }, { "GetId", bus_dbus_impl_get_id }, { "AddMatch", bus_dbus_impl_add_match }, { "RemoveMatch", bus_dbus_impl_remove_match }, { "RequestName", bus_dbus_impl_request_name }, { "ReleaseName", bus_dbus_impl_release_name }, { "StartServiceByName", bus_dbus_impl_start_service_by_name }, }; gint i; for (i = 0; i < G_N_ELEMENTS (methods); i++) { if (g_strcmp0 (method_name, methods[i].method_name) == 0) { BusConnection *connection = bus_connection_lookup (dbus_connection); g_assert (BUS_IS_CONNECTION (connection)); methods[i].method (dbus, connection, parameters, invocation); return; } } /* unsupported methods */ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, "org.freedesktop.DBus does not support %s", method_name); } /** * bus_dbus_impl_service_get_property: * * Handle a D-Bus method call from a client. This function overrides an implementation in src/ibusservice.c. */ static GVariant * bus_dbus_impl_service_get_property (IBusService *service, GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error) { /* FIXME implement the function. */ return IBUS_SERVICE_CLASS (bus_dbus_impl_parent_class)-> service_get_property (service, connection, sender, object_path, interface_name, property_name, error); } /** * bus_dbus_impl_service_set_property: * * Handle a D-Bus method call from a client. This function overrides an implementation in src/ibusservice.c. */ static gboolean bus_dbus_impl_service_set_property (IBusService *service, GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant *value, GError **error) { /* FIXME implement the function. */ return IBUS_SERVICE_CLASS (bus_dbus_impl_parent_class)-> service_set_property (service, connection, sender, object_path, interface_name, property_name, value, error); } /** * bus_dbus_impl_connection_filter_cb: * @returns: A GDBusMessage that will be processed by bus_dbus_impl_service_method_call. NULL when dropping the message. * * A filter function that is called for all incoming and outgoing messages. * WARNING - this function could be called by the GDBus's worker thread. So you should not call thread unsafe IBus functions. */ static GDBusMessage * bus_dbus_impl_connection_filter_cb (GDBusConnection *dbus_connection, GDBusMessage *message, gboolean incoming, gpointer user_data) { g_assert (G_IS_DBUS_CONNECTION (dbus_connection)); g_assert (G_IS_DBUS_MESSAGE (message)); g_assert (BUS_IS_DBUS_IMPL (user_data)); BusDBusImpl *dbus = (BusDBusImpl *) user_data; BusConnection *connection = bus_connection_lookup (dbus_connection); g_assert (connection != NULL); if (incoming) { /* is incoming message */ /* get the destination aka bus name of the message. the destination is set by g_dbus_connection_call_sync (for DBus and IBus messages * in the IBusBus class) or g_initable_new (for config and context messages in the IBusProxy sub classes.) */ const gchar *destination = g_dbus_message_get_destination (message); GDBusMessageType message_type = g_dbus_message_get_message_type (message); if (g_dbus_message_get_locked (message)) { /* If the message is locked, we need make a copy of it. */ GDBusMessage *new_message = g_dbus_message_copy (message, NULL); g_assert (new_message != NULL); g_object_unref (message); message = new_message; } /* connection unique name as sender of the message*/ g_dbus_message_set_sender (message, bus_connection_get_unique_name (connection)); if (g_strcmp0 (destination, "org.freedesktop.IBus") == 0) { /* the message is sent to IBus service. messages from ibusbus and ibuscontext may fall into this category. */ switch (message_type) { case G_DBUS_MESSAGE_TYPE_METHOD_CALL: case G_DBUS_MESSAGE_TYPE_METHOD_RETURN: case G_DBUS_MESSAGE_TYPE_ERROR: /* dispatch messages by match rule */ bus_dbus_impl_dispatch_message_by_rule (dbus, message, NULL); return message; case G_DBUS_MESSAGE_TYPE_SIGNAL: /* notreached - signals should not be sent to IBus service. dispatch signal messages by match rule, just in case. */ bus_dbus_impl_dispatch_message_by_rule (dbus, message, NULL); g_object_unref (message); g_return_val_if_reached (NULL); /* return NULL since the service does not handle signals. */ default: g_object_unref (message); g_return_val_if_reached (NULL); /* return NULL since the service does not handle signals. */ } } else if (g_strcmp0 (destination, "org.freedesktop.DBus") == 0) { /* the message is sent to DBus service. messages from ibusbus may fall into this category. */ switch (message_type) { case G_DBUS_MESSAGE_TYPE_METHOD_CALL: case G_DBUS_MESSAGE_TYPE_METHOD_RETURN: case G_DBUS_MESSAGE_TYPE_ERROR: /* dispatch messages by match rule */ bus_dbus_impl_dispatch_message_by_rule (dbus, message, NULL); return message; case G_DBUS_MESSAGE_TYPE_SIGNAL: /* notreached - signals should not be sent to IBus service. dispatch signal messages by match rule, just in case. */ bus_dbus_impl_dispatch_message_by_rule (dbus, message, NULL); g_object_unref (message); g_return_val_if_reached (NULL); /* return NULL since the service does not handle signals. */ default: g_object_unref (message); g_return_val_if_reached (NULL); /* return NULL since the service does not handle signals. */ } } else if (destination == NULL) { /* the message is sent to the current connection. communications between ibus-daemon and panel/engines may fall into this * category since the panel/engine proxies created by ibus-daemon does not set bus name. */ switch (message_type) { case G_DBUS_MESSAGE_TYPE_SIGNAL: case G_DBUS_MESSAGE_TYPE_METHOD_RETURN: case G_DBUS_MESSAGE_TYPE_ERROR: /* dispatch messages by match rule */ bus_dbus_impl_dispatch_message_by_rule (dbus, message, NULL); return message; case G_DBUS_MESSAGE_TYPE_METHOD_CALL: g_warning ("Unknown method call: destination=NULL, path='%s', interface='%s', member='%s'", g_dbus_message_get_path (message), g_dbus_message_get_interface (message), g_dbus_message_get_member (message)); bus_dbus_impl_dispatch_message_by_rule (dbus, message, NULL); return message; /* return the message, GDBus library will handle it */ default: /* notreached. */ g_object_unref (message); g_return_val_if_reached (NULL); /* return NULL since the service does not handle messages. */ } } else { /* The message is sent to an other service. Forward it. * For example, the config proxy class in src/ibusconfig.c sets its "g-name" property (i.e. destination) to IBUS_SERVICE_CONFIG. */ bus_dbus_impl_forward_message (dbus, connection, message); g_object_unref (message); return NULL; } } else { /* is outgoing message */ if (g_dbus_message_get_sender (message) == NULL) { if (g_dbus_message_get_locked (message)) { GDBusMessage *new_message = g_dbus_message_copy (message, NULL); g_assert (new_message != NULL); g_object_unref (message); message = new_message; } /* If the message is sending from ibus-daemon directly, * we set the sender to org.freedesktop.DBus */ g_dbus_message_set_sender (message, "org.freedesktop.DBus"); } /* dispatch the outgoing message by rules. */ bus_dbus_impl_dispatch_message_by_rule (dbus, message, connection); return message; } } BusDBusImpl * bus_dbus_impl_get_default (void) { static BusDBusImpl *dbus = NULL; if (dbus == NULL) { dbus = (BusDBusImpl *) g_object_new (BUS_TYPE_DBUS_IMPL, "object-path", "/org/freedesktop/DBus", NULL); } return dbus; } static void bus_dbus_impl_connection_destroy_cb (BusConnection *connection, BusDBusImpl *dbus) { const gchar *unique_name = bus_connection_get_unique_name (connection); const GList *names = NULL; BusNameService *service = NULL; if (unique_name != NULL) { GList *p = dbus->start_service_calls; while (p != NULL) { BusMethodCall *call = p->data; GList *next = p->next; if (call->connection == connection) { bus_method_call_free ((BusMethodCall *) p->data); dbus->start_service_calls = g_list_delete_link (dbus->start_service_calls, p); } p = next; } g_hash_table_remove (dbus->unique_names, unique_name); g_signal_emit (dbus, dbus_signals[NAME_OWNER_CHANGED], 0, connection, unique_name, unique_name, ""); } /* service->owners is the queue of connections. * If the connection is the primary owner and * bus_name_service_remove_owner() is called, the owner is removed * in the queue and the next owner will become the primary owner * automatically because service->owners is just a GSList. * If service->owners == NULL, it's good to remove the service in * dbus->names. * I suppose dbus->names are the global queue for every connection * and connection->names are the private queue of the connection. */ names = bus_connection_get_names (connection); while (names != NULL) { const gchar *name = (const gchar *)names->data; service = (BusNameService *) g_hash_table_lookup (dbus->names, name); g_assert (service != NULL); BusConnectionOwner *owner = bus_name_service_find_owner (service, connection); g_assert (owner != NULL); bus_name_service_remove_owner (service, owner, dbus); if (service->owners == NULL) { g_hash_table_remove (dbus->names, service->name); } bus_connection_owner_free (owner); names = names->next; } dbus->connections = g_list_remove (dbus->connections, connection); g_object_unref (connection); } gboolean bus_dbus_impl_new_connection (BusDBusImpl *dbus, BusConnection *connection) { g_assert (BUS_IS_DBUS_IMPL (dbus)); g_assert (BUS_IS_CONNECTION (connection)); g_assert (g_list_find (dbus->connections, connection) == NULL); g_object_ref_sink (connection); dbus->connections = g_list_append (dbus->connections, connection); bus_connection_set_filter (connection, bus_dbus_impl_connection_filter_cb, g_object_ref (dbus), g_object_unref); g_signal_connect (connection, "destroy", G_CALLBACK (bus_dbus_impl_connection_destroy_cb), dbus); /* add introspection_xml[] (see above) to the connection. */ ibus_service_register ((IBusService *) dbus, bus_connection_get_dbus_connection (connection), NULL); GList *p; for (p = dbus->objects; p != NULL; p = p->next) { /* add all introspection xmls in dbus->objects to the connection. */ ibus_service_register ((IBusService *) p->data, bus_connection_get_dbus_connection (connection), NULL); } return TRUE; } BusConnection * bus_dbus_impl_get_connection_by_name (BusDBusImpl *dbus, const gchar *name) { g_assert (BUS_IS_DBUS_IMPL (dbus)); g_assert (name != NULL); if (G_LIKELY (g_dbus_is_unique_name (name))) { return (BusConnection *) g_hash_table_lookup (dbus->unique_names, name); } else { BusNameService *service; BusConnectionOwner *owner; service = (BusNameService *) g_hash_table_lookup (dbus->names, name); if (service == NULL) { return NULL; } owner = bus_name_service_get_primary_owner (service); return owner ? owner->conn : NULL; } } typedef struct _BusForwardData BusForwardData; struct _BusForwardData { GDBusMessage *message; BusConnection *sender_connection; }; /** * bus_dbus_impl_forward_message_ible_cb: * * Process the first element of the dbus->forward_queue. The first element is forwarded by g_dbus_connection_send_message. */ static gboolean bus_dbus_impl_forward_message_idle_cb (BusDBusImpl *dbus) { g_return_val_if_fail (dbus->forward_queue != NULL, FALSE); g_mutex_lock (&dbus->forward_lock); BusForwardData *data = (BusForwardData *) dbus->forward_queue->data; dbus->forward_queue = g_list_delete_link (dbus->forward_queue, dbus->forward_queue); gboolean has_message = (dbus->forward_queue != NULL); g_mutex_unlock (&dbus->forward_lock); do { const gchar *destination = g_dbus_message_get_destination (data->message); BusConnection *dest_connection = NULL; if (destination != NULL) dest_connection = bus_dbus_impl_get_connection_by_name (dbus, destination); if (dest_connection != NULL) { /* FIXME workaround for gdbus. gdbus can not set an empty body message with signature '()' */ if (g_dbus_message_get_body (data->message) == NULL) g_dbus_message_set_signature (data->message, NULL); GError *error = NULL; gboolean retval = g_dbus_connection_send_message ( bus_connection_get_dbus_connection (dest_connection), data->message, G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL, NULL, &error); if (retval) break; g_warning ("forward message failed: %s.", error->message); g_error_free (error); } /* can not forward message */ if (g_dbus_message_get_message_type (data->message) != G_DBUS_MESSAGE_TYPE_METHOD_CALL) { /* skip non method messages */ break; } /* reply an error message, if forward method call message failed. */ GDBusMessage *reply_message = g_dbus_message_new_method_error (data->message, "org.freedesktop.DBus.Error.ServiceUnknown ", "The service name is '%s'.", destination); g_dbus_message_set_sender (reply_message, "org.freedesktop.DBus"); g_dbus_message_set_destination (reply_message, bus_connection_get_unique_name (data->sender_connection)); g_dbus_connection_send_message (bus_connection_get_dbus_connection (data->sender_connection), reply_message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); g_object_unref (reply_message); } while (0); g_object_unref (data->message); g_object_unref (data->sender_connection); g_slice_free (BusForwardData, data); return has_message; } void bus_dbus_impl_forward_message (BusDBusImpl *dbus, BusConnection *connection, GDBusMessage *message) { /* WARNING - this function could be called by the GDBus's worker thread. So you should not call thread unsafe IBus functions. */ g_assert (BUS_IS_DBUS_IMPL (dbus)); g_assert (BUS_IS_CONNECTION (connection)); g_assert (G_IS_DBUS_MESSAGE (message)); if (G_UNLIKELY (IBUS_OBJECT_DESTROYED (dbus))) return; /* FIXME the check above might not be sufficient. dbus object could be destroyed in the main thread right after the check, though the * dbus structure itself would not be freed (since the dbus object is ref'ed in bus_dbus_impl_new_connection.) * Anyway, it'd be better to investigate whether the thread safety issue could cause any real problems. */ BusForwardData *data = g_slice_new (BusForwardData); data->message = g_object_ref (message); data->sender_connection = g_object_ref (connection); g_mutex_lock (&dbus->forward_lock); gboolean is_running = (dbus->forward_queue != NULL); dbus->forward_queue = g_list_append (dbus->forward_queue, data); g_mutex_unlock (&dbus->forward_lock); if (!is_running) { g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc) bus_dbus_impl_forward_message_idle_cb, g_object_ref (dbus), (GDestroyNotify) g_object_unref); /* the idle callback function will be called from the ibus's main thread. */ } } static BusDispatchData * bus_dispatch_data_new (GDBusMessage *message, BusConnection *skip_connection) { BusDispatchData *data = g_slice_new (BusDispatchData); data->message = (GDBusMessage *) g_object_ref (message); if (skip_connection) { data->skip_connection = (BusConnection *) g_object_ref (skip_connection); } else { data->skip_connection = NULL; } return data; } static void bus_dispatch_data_free (BusDispatchData *data) { g_object_unref (data->message); if (data->skip_connection) g_object_unref (data->skip_connection); g_slice_free (BusDispatchData, data); } /** * bus_dbus_impl_dispatch_message_by_rule_idle_cb: * * Process the first element of the dbus->dispatch_queue. */ static gboolean bus_dbus_impl_dispatch_message_by_rule_idle_cb (BusDBusImpl *dbus) { g_return_val_if_fail (dbus->dispatch_queue != NULL, FALSE); if (G_UNLIKELY (IBUS_OBJECT_DESTROYED (dbus))) { /* dbus was destryed */ g_mutex_lock (&dbus->dispatch_lock); g_list_free_full (dbus->dispatch_queue, (GDestroyNotify) bus_dispatch_data_free); dbus->dispatch_queue = NULL; g_mutex_unlock (&dbus->dispatch_lock); return FALSE; /* return FALSE to prevent this callback to be called again. */ } /* remove fist node */ g_mutex_lock (&dbus->dispatch_lock); BusDispatchData *data = (BusDispatchData *) dbus->dispatch_queue->data; dbus->dispatch_queue = g_list_delete_link (dbus->dispatch_queue, dbus->dispatch_queue); gboolean has_message = (dbus->dispatch_queue != NULL); g_mutex_unlock (&dbus->dispatch_lock); GList *link = NULL; GList *recipients = NULL; /* check each match rules, and get recipients */ for (link = dbus->rules; link != NULL; link = link->next) { GList *list = bus_match_rule_get_recipients ((BusMatchRule *) link->data, data->message); recipients = g_list_concat (recipients, list); } /* send message to each recipients */ for (link = recipients; link != NULL; link = link->next) { BusConnection *connection = (BusConnection *) link->data; if (G_LIKELY (connection != data->skip_connection)) { g_dbus_connection_send_message (bus_connection_get_dbus_connection (connection), data->message, G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL, NULL, NULL); } } g_list_free (recipients); bus_dispatch_data_free (data); return has_message; /* remove this idle callback if no message is left by returning FALSE. */ } void bus_dbus_impl_dispatch_message_by_rule (BusDBusImpl *dbus, GDBusMessage *message, BusConnection *skip_connection) { /* WARNING - this function could be called by the GDBus's worker thread. So you should not call thread unsafe IBus functions. */ g_assert (BUS_IS_DBUS_IMPL (dbus)); g_assert (message != NULL); g_assert (skip_connection == NULL || BUS_IS_CONNECTION (skip_connection)); if (G_UNLIKELY (IBUS_OBJECT_DESTROYED (dbus))) return; /* FIXME - see the FIXME comment in bus_dbus_impl_forward_message. */ static GQuark dispatched_quark = 0; if (dispatched_quark == 0) { dispatched_quark = g_quark_from_static_string ("DISPATCHED"); } /* A message sent or forwarded by bus_dbus_impl_dispatch_message_by_rule_idle_cb is also processed by the filter callback. * If this message has been dispatched by rule, do nothing. */ if (g_object_get_qdata ((GObject *) message, dispatched_quark) != NULL) return; g_object_set_qdata ((GObject *) message, dispatched_quark, GINT_TO_POINTER (1)); /* append dispatch data into the queue, and start idle task if necessary */ g_mutex_lock (&dbus->dispatch_lock); gboolean is_running = (dbus->dispatch_queue != NULL); dbus->dispatch_queue = g_list_append (dbus->dispatch_queue, bus_dispatch_data_new (message, skip_connection)); g_mutex_unlock (&dbus->dispatch_lock); if (!is_running) { g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc) bus_dbus_impl_dispatch_message_by_rule_idle_cb, g_object_ref (dbus), (GDestroyNotify) g_object_unref); /* the idle callback function will be called from the ibus's main thread. */ } } static void bus_dbus_impl_object_destroy_cb (IBusService *object, BusDBusImpl *dbus) { bus_dbus_impl_unregister_object (dbus, object); } gboolean bus_dbus_impl_register_object (BusDBusImpl *dbus, IBusService *object) { g_assert (BUS_IS_DBUS_IMPL (dbus)); g_assert (IBUS_IS_SERVICE (object)); if (G_UNLIKELY (IBUS_OBJECT_DESTROYED (dbus))) { return FALSE; } dbus->objects = g_list_prepend (dbus->objects, g_object_ref (object)); g_signal_connect (object, "destroy", G_CALLBACK (bus_dbus_impl_object_destroy_cb), dbus); GList *p; for (p = dbus->connections; p != NULL; p = p->next) { GDBusConnection *connection = bus_connection_get_dbus_connection ((BusConnection *) p->data); if (connection != ibus_service_get_connection ((IBusService *) object)) ibus_service_register ((IBusService *) object, bus_connection_get_dbus_connection ((BusConnection *) p->data), NULL); } return TRUE; } gboolean bus_dbus_impl_unregister_object (BusDBusImpl *dbus, IBusService *object) { g_assert (BUS_IS_DBUS_IMPL (dbus)); g_assert (IBUS_IS_SERVICE (object)); GList *p = g_list_find (dbus->objects, object); if (p == NULL) return FALSE; g_signal_handlers_disconnect_by_func (object, G_CALLBACK (bus_dbus_impl_object_destroy_cb), dbus); dbus->objects = g_list_delete_link (dbus->objects, p); if (!IBUS_OBJECT_DESTROYED (object)) { GList *p; for (p = dbus->connections; p != NULL; p = p->next) { ibus_service_unregister ((IBusService *) object, bus_connection_get_dbus_connection ((BusConnection *) p->data)); } } g_object_unref (object); return TRUE; }