2011 строки
74 KiB
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;
|
|
}
|
|
|