2 This file is part of PulseAudio.
4 Copyright 2009 Tanu Kaskinen
5 Copyright 2009 Vincent Filali-Ansary <filali.v@azurdigitalnetworks.net>
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published
9 by the Free Software Foundation; either version 2.1 of the License,
10 or (at your option) any later version.
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with PulseAudio; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
27 #include <dbus/dbus.h>
29 #include <pulsecore/core-util.h>
30 #include <pulsecore/dbus-util.h>
31 #include <pulsecore/protocol-dbus.h>
33 #include "iface-client.h"
35 #define OBJECT_NAME "client"
37 struct pa_dbusiface_client
{
38 pa_dbusiface_core
*core
;
42 pa_proplist
*proplist
;
44 pa_dbus_protocol
*dbus_protocol
;
45 pa_subscription
*subscription
;
48 static void handle_get_index(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
);
49 static void handle_get_driver(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
);
50 static void handle_get_owner_module(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
);
51 static void handle_get_playback_streams(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
);
52 static void handle_get_record_streams(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
);
53 static void handle_get_property_list(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
);
55 static void handle_get_all(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
);
57 static void handle_kill(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
);
58 static void handle_update_properties(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
);
59 static void handle_remove_properties(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
);
61 enum property_handler_index
{
62 PROPERTY_HANDLER_INDEX
,
63 PROPERTY_HANDLER_DRIVER
,
64 PROPERTY_HANDLER_OWNER_MODULE
,
65 PROPERTY_HANDLER_PLAYBACK_STREAMS
,
66 PROPERTY_HANDLER_RECORD_STREAMS
,
67 PROPERTY_HANDLER_PROPERTY_LIST
,
71 static pa_dbus_property_handler property_handlers
[PROPERTY_HANDLER_MAX
] = {
72 [PROPERTY_HANDLER_INDEX
] = { .property_name
= "Index", .type
= "u", .get_cb
= handle_get_index
, .set_cb
= NULL
},
73 [PROPERTY_HANDLER_DRIVER
] = { .property_name
= "Driver", .type
= "s", .get_cb
= handle_get_driver
, .set_cb
= NULL
},
74 [PROPERTY_HANDLER_OWNER_MODULE
] = { .property_name
= "OwnerModule", .type
= "o", .get_cb
= handle_get_owner_module
, .set_cb
= NULL
},
75 [PROPERTY_HANDLER_PLAYBACK_STREAMS
] = { .property_name
= "PlaybackStreams", .type
= "ao", .get_cb
= handle_get_playback_streams
, .set_cb
= NULL
},
76 [PROPERTY_HANDLER_RECORD_STREAMS
] = { .property_name
= "RecordStreams", .type
= "ao", .get_cb
= handle_get_record_streams
, .set_cb
= NULL
},
77 [PROPERTY_HANDLER_PROPERTY_LIST
] = { .property_name
= "PropertyList", .type
= "a{say}", .get_cb
= handle_get_property_list
, .set_cb
= NULL
}
80 enum method_handler_index
{
82 METHOD_HANDLER_UPDATE_PROPERTIES
,
83 METHOD_HANDLER_REMOVE_PROPERTIES
,
87 static pa_dbus_arg_info update_properties_args
[] = { { "property_list", "a{say}", "in" }, { "update_mode", "u", "in" } };
88 static pa_dbus_arg_info remove_properties_args
[] = { { "keys", "as", "in" } };
90 static pa_dbus_method_handler method_handlers
[METHOD_HANDLER_MAX
] = {
91 [METHOD_HANDLER_KILL
] = {
92 .method_name
= "Kill",
95 .receive_cb
= handle_kill
},
96 [METHOD_HANDLER_UPDATE_PROPERTIES
] = {
97 .method_name
= "UpdateProperties",
98 .arguments
= update_properties_args
,
99 .n_arguments
= sizeof(update_properties_args
) / sizeof(pa_dbus_arg_info
),
100 .receive_cb
= handle_update_properties
},
101 [METHOD_HANDLER_REMOVE_PROPERTIES
] = {
102 .method_name
= "RemoveProperties",
103 .arguments
= remove_properties_args
,
104 .n_arguments
= sizeof(remove_properties_args
) / sizeof(pa_dbus_arg_info
),
105 .receive_cb
= handle_remove_properties
}
109 SIGNAL_PROPERTY_LIST_UPDATED
,
114 static pa_dbus_arg_info property_list_updated_args
[] = { { "property_list", "a{say}", NULL
} };
115 static pa_dbus_arg_info client_event_args
[] = { { "name", "s", NULL
},
116 { "property_list", "a{say}", NULL
} };
118 static pa_dbus_signal_info signals
[SIGNAL_MAX
] = {
119 [SIGNAL_PROPERTY_LIST_UPDATED
] = { .name
= "PropertyListUpdated", .arguments
= property_list_updated_args
, .n_arguments
= 1 },
120 /* ClientEvent is sent from module-dbus-protocol.c. */
121 [SIGNAL_CLIENT_EVENT
] = { .name
= "ClientEvent", .arguments
= client_event_args
, .n_arguments
= 1 }
124 static pa_dbus_interface_info client_interface_info
= {
125 .name
= PA_DBUSIFACE_CLIENT_INTERFACE
,
126 .method_handlers
= method_handlers
,
127 .n_method_handlers
= METHOD_HANDLER_MAX
,
128 .property_handlers
= property_handlers
,
129 .n_property_handlers
= PROPERTY_HANDLER_MAX
,
130 .get_all_properties_cb
= handle_get_all
,
132 .n_signals
= SIGNAL_MAX
135 static void handle_get_index(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
) {
136 pa_dbusiface_client
*c
= userdata
;
137 dbus_uint32_t idx
= 0;
143 idx
= c
->client
->index
;
145 pa_dbus_send_basic_variant_reply(conn
, msg
, DBUS_TYPE_UINT32
, &idx
);
148 static void handle_get_driver(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
) {
149 pa_dbusiface_client
*c
= userdata
;
155 pa_dbus_send_basic_variant_reply(conn
, msg
, DBUS_TYPE_STRING
, &c
->client
->driver
);
158 static void handle_get_owner_module(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
) {
159 pa_dbusiface_client
*c
= userdata
;
160 const char *owner_module
= NULL
;
166 if (!c
->client
->module
) {
167 pa_dbus_send_error(conn
, msg
, PA_DBUS_ERROR_NO_SUCH_PROPERTY
, "Client %d doesn't have an owner module.", c
->client
->index
);
171 owner_module
= pa_dbusiface_core_get_module_path(c
->core
, c
->client
->module
);
173 pa_dbus_send_basic_variant_reply(conn
, msg
, DBUS_TYPE_OBJECT_PATH
, &owner_module
);
176 /* The caller frees the array, but not the strings. */
177 static const char **get_playback_streams(pa_dbusiface_client
*c
, unsigned *n
) {
178 const char **playback_streams
= NULL
;
181 pa_sink_input
*sink_input
= NULL
;
186 *n
= pa_idxset_size(c
->client
->sink_inputs
);
191 playback_streams
= pa_xnew(const char *, *n
);
193 PA_IDXSET_FOREACH(sink_input
, c
->client
->sink_inputs
, idx
)
194 playback_streams
[i
++] = pa_dbusiface_core_get_playback_stream_path(c
->core
, sink_input
);
196 return playback_streams
;
199 static void handle_get_playback_streams(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
) {
200 pa_dbusiface_client
*c
= userdata
;
201 const char **playback_streams
= NULL
;
202 unsigned n_playback_streams
= 0;
208 playback_streams
= get_playback_streams(c
, &n_playback_streams
);
210 pa_dbus_send_basic_array_variant_reply(conn
, msg
, DBUS_TYPE_OBJECT_PATH
, playback_streams
, n_playback_streams
);
212 pa_xfree(playback_streams
);
215 /* The caller frees the array, but not the strings. */
216 static const char **get_record_streams(pa_dbusiface_client
*c
, unsigned *n
) {
217 const char **record_streams
= NULL
;
220 pa_source_output
*source_output
= NULL
;
225 *n
= pa_idxset_size(c
->client
->source_outputs
);
230 record_streams
= pa_xnew(const char *, *n
);
232 PA_IDXSET_FOREACH(source_output
, c
->client
->source_outputs
, idx
)
233 record_streams
[i
++] = pa_dbusiface_core_get_record_stream_path(c
->core
, source_output
);
235 return record_streams
;
238 static void handle_get_record_streams(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
) {
239 pa_dbusiface_client
*c
= userdata
;
240 const char **record_streams
= NULL
;
241 unsigned n_record_streams
= 0;
247 record_streams
= get_record_streams(c
, &n_record_streams
);
249 pa_dbus_send_basic_array_variant_reply(conn
, msg
, DBUS_TYPE_OBJECT_PATH
, record_streams
, n_record_streams
);
251 pa_xfree(record_streams
);
254 static void handle_get_property_list(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
) {
255 pa_dbusiface_client
*c
= userdata
;
261 pa_dbus_send_proplist_variant_reply(conn
, msg
, c
->client
->proplist
);
264 static void handle_get_all(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
) {
265 pa_dbusiface_client
*c
= userdata
;
266 DBusMessage
*reply
= NULL
;
267 DBusMessageIter msg_iter
;
268 DBusMessageIter dict_iter
;
269 dbus_uint32_t idx
= 0;
270 const char *owner_module
= NULL
;
271 const char **playback_streams
= NULL
;
272 unsigned n_playback_streams
= 0;
273 const char **record_streams
= NULL
;
274 unsigned n_record_streams
= 0;
280 idx
= c
->client
->index
;
281 if (c
->client
->module
)
282 owner_module
= pa_dbusiface_core_get_module_path(c
->core
, c
->client
->module
);
283 playback_streams
= get_playback_streams(c
, &n_playback_streams
);
284 record_streams
= get_record_streams(c
, &n_record_streams
);
286 pa_assert_se((reply
= dbus_message_new_method_return(msg
)));
288 dbus_message_iter_init_append(reply
, &msg_iter
);
289 pa_assert_se(dbus_message_iter_open_container(&msg_iter
, DBUS_TYPE_ARRAY
, "{sv}", &dict_iter
));
291 pa_dbus_append_basic_variant_dict_entry(&dict_iter
, property_handlers
[PROPERTY_HANDLER_INDEX
].property_name
, DBUS_TYPE_UINT32
, &idx
);
292 pa_dbus_append_basic_variant_dict_entry(&dict_iter
, property_handlers
[PROPERTY_HANDLER_DRIVER
].property_name
, DBUS_TYPE_STRING
, &c
->client
->driver
);
295 pa_dbus_append_basic_variant_dict_entry(&dict_iter
, property_handlers
[PROPERTY_HANDLER_OWNER_MODULE
].property_name
, DBUS_TYPE_OBJECT_PATH
, &owner_module
);
297 pa_dbus_append_basic_array_variant_dict_entry(&dict_iter
, property_handlers
[PROPERTY_HANDLER_PLAYBACK_STREAMS
].property_name
, DBUS_TYPE_OBJECT_PATH
, playback_streams
, n_playback_streams
);
298 pa_dbus_append_basic_array_variant_dict_entry(&dict_iter
, property_handlers
[PROPERTY_HANDLER_RECORD_STREAMS
].property_name
, DBUS_TYPE_OBJECT_PATH
, record_streams
, n_record_streams
);
299 pa_dbus_append_proplist_variant_dict_entry(&dict_iter
, property_handlers
[PROPERTY_HANDLER_PROPERTY_LIST
].property_name
, c
->client
->proplist
);
301 pa_assert_se(dbus_message_iter_close_container(&msg_iter
, &dict_iter
));
303 pa_assert_se(dbus_connection_send(conn
, reply
, NULL
));
305 dbus_message_unref(reply
);
307 pa_xfree(playback_streams
);
308 pa_xfree(record_streams
);
311 static void handle_kill(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
) {
312 pa_dbusiface_client
*c
= userdata
;
318 dbus_connection_ref(conn
);
320 pa_client_kill(c
->client
);
322 pa_dbus_send_empty_reply(conn
, msg
);
324 dbus_connection_unref(conn
);
327 static void handle_update_properties(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
) {
328 pa_dbusiface_client
*c
= userdata
;
329 DBusMessageIter msg_iter
;
330 pa_proplist
*property_list
= NULL
;
331 dbus_uint32_t update_mode
= 0;
337 if (pa_dbus_protocol_get_client(c
->dbus_protocol
, conn
) != c
->client
) {
338 pa_dbus_send_error(conn
, msg
, DBUS_ERROR_ACCESS_DENIED
, "Client tried to modify the property list of another client.");
342 pa_assert_se(dbus_message_iter_init(msg
, &msg_iter
));
344 if (!(property_list
= pa_dbus_get_proplist_arg(conn
, msg
, &msg_iter
)))
347 dbus_message_iter_get_basic(&msg_iter
, &update_mode
);
349 if (!(update_mode
== PA_UPDATE_SET
|| update_mode
== PA_UPDATE_MERGE
|| update_mode
== PA_UPDATE_REPLACE
)) {
350 pa_dbus_send_error(conn
, msg
, DBUS_ERROR_INVALID_ARGS
, "Invalid update mode: %u", update_mode
);
354 pa_client_update_proplist(c
->client
, update_mode
, property_list
);
356 pa_dbus_send_empty_reply(conn
, msg
);
360 pa_proplist_free(property_list
);
363 static void handle_remove_properties(DBusConnection
*conn
, DBusMessage
*msg
, void *userdata
) {
364 pa_dbusiface_client
*c
= userdata
;
367 pa_bool_t changed
= FALSE
;
374 if (pa_dbus_protocol_get_client(c
->dbus_protocol
, conn
) != c
->client
) {
375 pa_dbus_send_error(conn
, msg
, DBUS_ERROR_ACCESS_DENIED
, "Client tried to modify the property list of another client.");
379 pa_assert_se(dbus_message_get_args(msg
, NULL
, DBUS_TYPE_ARRAY
, DBUS_TYPE_STRING
, &keys
, &n_keys
, DBUS_TYPE_INVALID
));
381 for (i
= 0; i
< n_keys
; ++i
)
382 changed
|= pa_proplist_unset(c
->client
->proplist
, keys
[i
]) >= 0;
384 pa_dbus_send_empty_reply(conn
, msg
);
387 pa_subscription_post(c
->client
->core
, PA_SUBSCRIPTION_EVENT_CLIENT
|PA_SUBSCRIPTION_EVENT_CHANGE
, c
->client
->index
);
389 dbus_free_string_array(keys
);
392 static void subscription_cb(pa_core
*core
, pa_subscription_event_type_t t
, uint32_t idx
, void *userdata
) {
393 pa_dbusiface_client
*c
= userdata
;
394 DBusMessage
*signal_msg
= NULL
;
397 pa_assert((t
& PA_SUBSCRIPTION_EVENT_FACILITY_MASK
) == PA_SUBSCRIPTION_EVENT_CLIENT
);
400 /* We can't use idx != c->client->index, because the c->client pointer may
401 * be stale at this point. */
402 if (pa_idxset_get_by_index(core
->clients
, idx
) != c
->client
)
405 if ((t
& PA_SUBSCRIPTION_EVENT_TYPE_MASK
) != PA_SUBSCRIPTION_EVENT_CHANGE
)
408 if (!pa_proplist_equal(c
->proplist
, c
->client
->proplist
)) {
409 DBusMessageIter msg_iter
;
411 pa_proplist_update(c
->proplist
, PA_UPDATE_SET
, c
->client
->proplist
);
413 pa_assert_se(signal_msg
= dbus_message_new_signal(c
->path
,
414 PA_DBUSIFACE_CLIENT_INTERFACE
,
415 signals
[SIGNAL_PROPERTY_LIST_UPDATED
].name
));
416 dbus_message_iter_init_append(signal_msg
, &msg_iter
);
417 pa_dbus_append_proplist(&msg_iter
, c
->proplist
);
419 pa_dbus_protocol_send_signal(c
->dbus_protocol
, signal_msg
);
420 dbus_message_unref(signal_msg
);
425 pa_dbusiface_client
*pa_dbusiface_client_new(pa_dbusiface_core
*core
, pa_client
*client
) {
426 pa_dbusiface_client
*c
= NULL
;
431 c
= pa_xnew(pa_dbusiface_client
, 1);
434 c
->path
= pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH
, OBJECT_NAME
, client
->index
);
435 c
->proplist
= pa_proplist_copy(client
->proplist
);
436 c
->dbus_protocol
= pa_dbus_protocol_get(client
->core
);
437 c
->subscription
= pa_subscription_new(client
->core
, PA_SUBSCRIPTION_MASK_CLIENT
, subscription_cb
, c
);
439 pa_assert_se(pa_dbus_protocol_add_interface(c
->dbus_protocol
, c
->path
, &client_interface_info
, c
) >= 0);
444 void pa_dbusiface_client_free(pa_dbusiface_client
*c
) {
447 pa_assert_se(pa_dbus_protocol_remove_interface(c
->dbus_protocol
, c
->path
, client_interface_info
.name
) >= 0);
449 pa_dbus_protocol_unref(c
->dbus_protocol
);
455 const char *pa_dbusiface_client_get_path(pa_dbusiface_client
*c
) {