1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2009 Tanu Kaskinen
5 Copyright 2009 Vincent Filali-Ansary <filali.v@azurdigitalnetworks.net>
6
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.
11
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.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <dbus/dbus.h>
26
27 #include <pulsecore/core-util.h>
28 #include <pulsecore/dbus-util.h>
29 #include <pulsecore/protocol-dbus.h>
30
31 #include "iface-client.h"
32
33 #define OBJECT_NAME "client"
34
35 struct pa_dbusiface_client {
36 pa_dbusiface_core *core;
37
38 pa_client *client;
39 char *path;
40 pa_proplist *proplist;
41
42 pa_hook_slot *client_proplist_changed_slot;
43
44 pa_dbus_protocol *dbus_protocol;
45 };
46
47 static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
48 static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata);
49 static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata);
50 static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata);
51 static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata);
52 static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
53
54 static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
55
56 static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata);
57 static void handle_update_properties(DBusConnection *conn, DBusMessage *msg, void *userdata);
58 static void handle_remove_properties(DBusConnection *conn, DBusMessage *msg, void *userdata);
59
60 enum property_handler_index {
61 PROPERTY_HANDLER_INDEX,
62 PROPERTY_HANDLER_DRIVER,
63 PROPERTY_HANDLER_OWNER_MODULE,
64 PROPERTY_HANDLER_PLAYBACK_STREAMS,
65 PROPERTY_HANDLER_RECORD_STREAMS,
66 PROPERTY_HANDLER_PROPERTY_LIST,
67 PROPERTY_HANDLER_MAX
68 };
69
70 static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
71 [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL },
72 [PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL },
73 [PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL },
74 [PROPERTY_HANDLER_PLAYBACK_STREAMS] = { .property_name = "PlaybackStreams", .type = "ao", .get_cb = handle_get_playback_streams, .set_cb = NULL },
75 [PROPERTY_HANDLER_RECORD_STREAMS] = { .property_name = "RecordStreams", .type = "ao", .get_cb = handle_get_record_streams, .set_cb = NULL },
76 [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL }
77 };
78
79 enum method_handler_index {
80 METHOD_HANDLER_KILL,
81 METHOD_HANDLER_UPDATE_PROPERTIES,
82 METHOD_HANDLER_REMOVE_PROPERTIES,
83 METHOD_HANDLER_MAX
84 };
85
86 static pa_dbus_arg_info update_properties_args[] = { { "property_list", "a{say}", "in" }, { "update_mode", "u", "in" } };
87 static pa_dbus_arg_info remove_properties_args[] = { { "keys", "as", "in" } };
88
89 static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
90 [METHOD_HANDLER_KILL] = {
91 .method_name = "Kill",
92 .arguments = NULL,
93 .n_arguments = 0,
94 .receive_cb = handle_kill },
95 [METHOD_HANDLER_UPDATE_PROPERTIES] = {
96 .method_name = "UpdateProperties",
97 .arguments = update_properties_args,
98 .n_arguments = sizeof(update_properties_args) / sizeof(pa_dbus_arg_info),
99 .receive_cb = handle_update_properties },
100 [METHOD_HANDLER_REMOVE_PROPERTIES] = {
101 .method_name = "RemoveProperties",
102 .arguments = remove_properties_args,
103 .n_arguments = sizeof(remove_properties_args) / sizeof(pa_dbus_arg_info),
104 .receive_cb = handle_remove_properties }
105 };
106
107 enum signal_index {
108 SIGNAL_PROPERTY_LIST_UPDATED,
109 SIGNAL_CLIENT_EVENT,
110 SIGNAL_MAX
111 };
112
113 static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } };
114 static pa_dbus_arg_info client_event_args[] = { { "name", "s", NULL },
115 { "property_list", "a{say}", NULL } };
116
117 static pa_dbus_signal_info signals[SIGNAL_MAX] = {
118 [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 },
119 /* ClientEvent is sent from module-dbus-protocol.c. */
120 [SIGNAL_CLIENT_EVENT] = { .name = "ClientEvent", .arguments = client_event_args, .n_arguments = 1 }
121 };
122
123 static pa_dbus_interface_info client_interface_info = {
124 .name = PA_DBUSIFACE_CLIENT_INTERFACE,
125 .method_handlers = method_handlers,
126 .n_method_handlers = METHOD_HANDLER_MAX,
127 .property_handlers = property_handlers,
128 .n_property_handlers = PROPERTY_HANDLER_MAX,
129 .get_all_properties_cb = handle_get_all,
130 .signals = signals,
131 .n_signals = SIGNAL_MAX
132 };
133
handle_get_index(DBusConnection * conn,DBusMessage * msg,void * userdata)134 static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
135 pa_dbusiface_client *c = userdata;
136 dbus_uint32_t idx = 0;
137
138 pa_assert(conn);
139 pa_assert(msg);
140 pa_assert(c);
141
142 idx = c->client->index;
143
144 pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
145 }
146
handle_get_driver(DBusConnection * conn,DBusMessage * msg,void * userdata)147 static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) {
148 pa_dbusiface_client *c = userdata;
149
150 pa_assert(conn);
151 pa_assert(msg);
152 pa_assert(c);
153
154 pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->client->driver);
155 }
156
handle_get_owner_module(DBusConnection * conn,DBusMessage * msg,void * userdata)157 static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) {
158 pa_dbusiface_client *c = userdata;
159 const char *owner_module = NULL;
160
161 pa_assert(conn);
162 pa_assert(msg);
163 pa_assert(c);
164
165 if (!c->client->module) {
166 pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Client %d doesn't have an owner module.", c->client->index);
167 return;
168 }
169
170 owner_module = pa_dbusiface_core_get_module_path(c->core, c->client->module);
171
172 pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &owner_module);
173 }
174
175 /* The caller frees the array, but not the strings. */
get_playback_streams(pa_dbusiface_client * c,unsigned * n)176 static const char **get_playback_streams(pa_dbusiface_client *c, unsigned *n) {
177 const char **playback_streams = NULL;
178 unsigned i = 0;
179 uint32_t idx = 0;
180 pa_sink_input *sink_input = NULL;
181
182 pa_assert(c);
183 pa_assert(n);
184
185 *n = pa_idxset_size(c->client->sink_inputs);
186
187 if (*n == 0)
188 return NULL;
189
190 playback_streams = pa_xnew(const char *, *n);
191
192 PA_IDXSET_FOREACH(sink_input, c->client->sink_inputs, idx)
193 playback_streams[i++] = pa_dbusiface_core_get_playback_stream_path(c->core, sink_input);
194
195 return playback_streams;
196 }
197
handle_get_playback_streams(DBusConnection * conn,DBusMessage * msg,void * userdata)198 static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) {
199 pa_dbusiface_client *c = userdata;
200 const char **playback_streams = NULL;
201 unsigned n_playback_streams = 0;
202
203 pa_assert(conn);
204 pa_assert(msg);
205 pa_assert(c);
206
207 playback_streams = get_playback_streams(c, &n_playback_streams);
208
209 pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams);
210
211 pa_xfree(playback_streams);
212 }
213
214 /* The caller frees the array, but not the strings. */
get_record_streams(pa_dbusiface_client * c,unsigned * n)215 static const char **get_record_streams(pa_dbusiface_client *c, unsigned *n) {
216 const char **record_streams = NULL;
217 unsigned i = 0;
218 uint32_t idx = 0;
219 pa_source_output *source_output = NULL;
220
221 pa_assert(c);
222 pa_assert(n);
223
224 *n = pa_idxset_size(c->client->source_outputs);
225
226 if (*n == 0)
227 return NULL;
228
229 record_streams = pa_xnew(const char *, *n);
230
231 PA_IDXSET_FOREACH(source_output, c->client->source_outputs, idx)
232 record_streams[i++] = pa_dbusiface_core_get_record_stream_path(c->core, source_output);
233
234 return record_streams;
235 }
236
handle_get_record_streams(DBusConnection * conn,DBusMessage * msg,void * userdata)237 static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata) {
238 pa_dbusiface_client *c = userdata;
239 const char **record_streams = NULL;
240 unsigned n_record_streams = 0;
241
242 pa_assert(conn);
243 pa_assert(msg);
244 pa_assert(c);
245
246 record_streams = get_record_streams(c, &n_record_streams);
247
248 pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams);
249
250 pa_xfree(record_streams);
251 }
252
handle_get_property_list(DBusConnection * conn,DBusMessage * msg,void * userdata)253 static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
254 pa_dbusiface_client *c = userdata;
255
256 pa_assert(conn);
257 pa_assert(msg);
258 pa_assert(c);
259
260 pa_dbus_send_proplist_variant_reply(conn, msg, c->client->proplist);
261 }
262
handle_get_all(DBusConnection * conn,DBusMessage * msg,void * userdata)263 static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
264 pa_dbusiface_client *c = userdata;
265 DBusMessage *reply = NULL;
266 DBusMessageIter msg_iter;
267 DBusMessageIter dict_iter;
268 dbus_uint32_t idx = 0;
269 const char *owner_module = NULL;
270 const char **playback_streams = NULL;
271 unsigned n_playback_streams = 0;
272 const char **record_streams = NULL;
273 unsigned n_record_streams = 0;
274
275 pa_assert(conn);
276 pa_assert(msg);
277 pa_assert(c);
278
279 idx = c->client->index;
280 if (c->client->module)
281 owner_module = pa_dbusiface_core_get_module_path(c->core, c->client->module);
282 playback_streams = get_playback_streams(c, &n_playback_streams);
283 record_streams = get_record_streams(c, &n_record_streams);
284
285 pa_assert_se((reply = dbus_message_new_method_return(msg)));
286
287 dbus_message_iter_init_append(reply, &msg_iter);
288 pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
289
290 pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
291 pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &c->client->driver);
292
293 if (owner_module)
294 pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module);
295
296 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);
297 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);
298 pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, c->client->proplist);
299
300 pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
301
302 pa_assert_se(dbus_connection_send(conn, reply, NULL));
303
304 dbus_message_unref(reply);
305
306 pa_xfree(playback_streams);
307 pa_xfree(record_streams);
308 }
309
handle_kill(DBusConnection * conn,DBusMessage * msg,void * userdata)310 static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata) {
311 pa_dbusiface_client *c = userdata;
312
313 pa_assert(conn);
314 pa_assert(msg);
315 pa_assert(c);
316
317 dbus_connection_ref(conn);
318
319 pa_client_kill(c->client);
320
321 pa_dbus_send_empty_reply(conn, msg);
322
323 dbus_connection_unref(conn);
324 }
325
handle_update_properties(DBusConnection * conn,DBusMessage * msg,void * userdata)326 static void handle_update_properties(DBusConnection *conn, DBusMessage *msg, void *userdata) {
327 pa_dbusiface_client *c = userdata;
328 DBusMessageIter msg_iter;
329 pa_proplist *property_list = NULL;
330 dbus_uint32_t update_mode = 0;
331
332 pa_assert(conn);
333 pa_assert(msg);
334 pa_assert(c);
335
336 if (pa_dbus_protocol_get_client(c->dbus_protocol, conn) != c->client) {
337 pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "Client tried to modify the property list of another client.");
338 return;
339 }
340
341 pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
342
343 if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter)))
344 return;
345
346 dbus_message_iter_get_basic(&msg_iter, &update_mode);
347
348 if (!(update_mode == PA_UPDATE_SET || update_mode == PA_UPDATE_MERGE || update_mode == PA_UPDATE_REPLACE)) {
349 pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid update mode: %u", update_mode);
350 goto finish;
351 }
352
353 pa_client_update_proplist(c->client, update_mode, property_list);
354
355 pa_dbus_send_empty_reply(conn, msg);
356
357 finish:
358 if (property_list)
359 pa_proplist_free(property_list);
360 }
361
handle_remove_properties(DBusConnection * conn,DBusMessage * msg,void * userdata)362 static void handle_remove_properties(DBusConnection *conn, DBusMessage *msg, void *userdata) {
363 pa_dbusiface_client *c = userdata;
364 char **keys = NULL;
365 int n_keys = 0;
366 bool changed = false;
367 int i = 0;
368
369 pa_assert(conn);
370 pa_assert(msg);
371 pa_assert(c);
372
373 if (pa_dbus_protocol_get_client(c->dbus_protocol, conn) != c->client) {
374 pa_dbus_send_error(conn, msg, DBUS_ERROR_ACCESS_DENIED, "Client tried to modify the property list of another client.");
375 return;
376 }
377
378 pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &keys, &n_keys, DBUS_TYPE_INVALID));
379
380 for (i = 0; i < n_keys; ++i)
381 changed |= pa_proplist_unset(c->client->proplist, keys[i]) >= 0;
382
383 pa_dbus_send_empty_reply(conn, msg);
384
385 if (changed) {
386 pa_hook_fire(&c->client->core->hooks[PA_CORE_HOOK_CLIENT_PROPLIST_CHANGED], c->client);
387 pa_subscription_post(c->client->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index);
388 }
389
390 dbus_free_string_array(keys);
391 }
392
client_proplist_changed_cb(void * hook_data,void * call_data,void * slot_data)393 static pa_hook_result_t client_proplist_changed_cb(void *hook_data, void *call_data, void *slot_data) {
394 pa_dbusiface_client *c = slot_data;
395 pa_client *client = call_data;
396 DBusMessage *signal_msg;
397
398 pa_assert(c);
399 pa_assert(client);
400
401 if (c->client != client)
402 return PA_HOOK_OK;
403
404 if (!pa_proplist_equal(c->proplist, c->client->proplist)) {
405 DBusMessageIter msg_iter;
406
407 pa_proplist_update(c->proplist, PA_UPDATE_SET, c->client->proplist);
408
409 pa_assert_se(signal_msg = dbus_message_new_signal(c->path,
410 PA_DBUSIFACE_CLIENT_INTERFACE,
411 signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
412 dbus_message_iter_init_append(signal_msg, &msg_iter);
413 pa_dbus_append_proplist(&msg_iter, c->proplist);
414
415 pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg);
416 dbus_message_unref(signal_msg);
417 }
418
419 return PA_HOOK_OK;
420 }
421
pa_dbusiface_client_new(pa_dbusiface_core * core,pa_client * client)422 pa_dbusiface_client *pa_dbusiface_client_new(pa_dbusiface_core *core, pa_client *client) {
423 pa_dbusiface_client *c = NULL;
424
425 pa_assert(core);
426 pa_assert(client);
427
428 c = pa_xnew(pa_dbusiface_client, 1);
429 c->core = core;
430 c->client = client;
431 c->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, client->index);
432 c->proplist = pa_proplist_copy(client->proplist);
433 c->dbus_protocol = pa_dbus_protocol_get(client->core);
434 c->client_proplist_changed_slot = pa_hook_connect(&client->core->hooks[PA_CORE_HOOK_CLIENT_PROPLIST_CHANGED],
435 PA_HOOK_NORMAL, client_proplist_changed_cb, c);
436
437 pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, c->path, &client_interface_info, c) >= 0);
438
439 return c;
440 }
441
pa_dbusiface_client_free(pa_dbusiface_client * c)442 void pa_dbusiface_client_free(pa_dbusiface_client *c) {
443 pa_assert(c);
444
445 pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, c->path, client_interface_info.name) >= 0);
446
447 pa_hook_slot_free(c->client_proplist_changed_slot);
448 pa_proplist_free(c->proplist);
449 pa_dbus_protocol_unref(c->dbus_protocol);
450
451 pa_xfree(c->path);
452 pa_xfree(c);
453 }
454
pa_dbusiface_client_get_path(pa_dbusiface_client * c)455 const char *pa_dbusiface_client_get_path(pa_dbusiface_client *c) {
456 pa_assert(c);
457
458 return c->path;
459 }
460