ibus/bus/dbusimpl.c

2011 строки
74 KiB
C

/* -*- 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 <shawn.p.huang@gmail.com>
* Copyright (C) 2015-2020 Takao Fujiwara <takao.fujiwara1@gmail.com>
* Copyright (C) 2008-2020 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 <string.h>
#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[] =
"<node>"
" <interface name='org.freedesktop.DBus'>"
" <method name='Hello'>"
" <arg direction='out' type='s' name='unique_name' />"
" </method>"
" <method name='RequestName'>"
" <arg direction='in' type='s' name='name' />"
" <arg direction='in' type='u' name='flags' />"
" <arg direction='out' type='u' />"
" </method>"
" <method name='ReleaseName'>"
" <arg direction='in' type='s' name='name' />"
" <arg direction='out' type='u' />"
" </method>"
" <method name='StartServiceByName'>"
" <arg direction='in' type='s' />"
" <arg direction='in' type='u' />"
" <arg direction='out' type='u' />"
" </method>"
" <method name='UpdateActivationEnvironment'>"
" <arg direction='in' type='a{ss}'/>"
" </method>"
" <method name='NameHasOwner'>"
" <arg direction='in' type='s' name='name' />"
" <arg direction='out' type='b' />"
" </method>"
" <method name='ListNames'>"
" <arg direction='out' type='as' />"
" </method>"
" <method name='ListActivatableNames'>"
" <arg direction='out' type='as' />"
" </method>"
" <method name='AddMatch'>"
" <arg direction='in' type='s' name='match_rule' />"
" </method>"
" <method name='RemoveMatch'>"
" <arg direction='in' type='s' name='match_rule' />"
" </method>"
" <method name='GetNameOwner'>"
" <arg direction='in' type='s' name='name' />"
" <arg direction='out' type='s' name='unique_name' />"
" </method>"
" <method name='ListQueuedOwners'>"
" <arg direction='in' type='s' name='name' />"
" <arg direction='out' type='as' />"
" </method>"
" <method name='GetConnectionUnixUser'>"
" <arg direction='in' type='s' />"
" <arg direction='out' type='u' />"
" </method>"
" <method name='GetConnectionUnixProcessID'>"
" <arg direction='in' type='s' />"
" <arg direction='out' type='u' />"
" </method>"
" <method name='GetAdtAuditSessionData'>"
" <arg direction='in' type='s' />"
" <arg direction='out' type='ay' />"
" </method>"
" <method name='GetConnectionSELinuxSecurityContext'>"
" <arg direction='in' type='s' />"
" <arg direction='out' type='ay' />"
" </method>"
" <method name='ReloadConfig' />"
" <method name='GetId'>"
" <arg direction='out' type='s' />"
" </method>"
" <signal name='NameOwnerChanged'>"
" <arg type='s' name='name' />"
" <arg type='s' name='old_owner' />"
" <arg type='s' name='new_owner' />"
" </signal>"
" <signal name='NameLost'>"
" <arg type='s' name='name' />"
" </signal>"
" <signal name='NameAcquired'>"
" <arg type='s' name='name' />"
" </signal>"
" </interface>"
"</node>";
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)
{
gboolean has_old_owner = FALSE;
g_assert (service != NULL);
g_assert (owner != NULL);
g_assert (dbus != NULL);
BusConnectionOwner *old = service->owners != NULL ?
(BusConnectionOwner *)service->owners->data : NULL;
/* rhbz#1432252 If bus_connection_get_unique_name() == NULL,
* "Hello" method is not received yet.
*/
if (old != NULL && bus_connection_get_unique_name (old->conn) != NULL) {
has_old_owner = TRUE;
}
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,
has_old_owner ? 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;
gboolean has_new_owner = FALSE;
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;
/* rhbz#1406699 If bus_connection_get_unique_name() == NULL,
* "Hello" method is not received yet.
*/
if (_new != NULL &&
bus_connection_get_unique_name (_new->conn) != NULL) {
has_new_owner = TRUE;
}
}
if (dbus != NULL) {
g_signal_emit (dbus,
dbus_signals[NAME_LOST],
0,
owner->conn,
service->name);
if (has_new_owner) {
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),
has_new_owner ? 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;
}