]> code.delx.au - pulseaudio/blob - src/modules/dbus/iface-client.c
dbus-protocol: Implement argument type checking for normal methods.
[pulseaudio] / src / modules / dbus / iface-client.c
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, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 USA.
21 ***/
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <dbus/dbus.h>
28
29 #include <pulsecore/core-util.h>
30 #include <pulsecore/dbus-util.h>
31 #include <pulsecore/protocol-dbus.h>
32
33 #include "iface-client.h"
34
35 #define OBJECT_NAME "client"
36
37 struct pa_dbusiface_client {
38 pa_dbusiface_core *core;
39
40 pa_client *client;
41 char *path;
42 pa_proplist *proplist;
43
44 pa_dbus_protocol *dbus_protocol;
45 pa_subscription *subscription;
46 };
47
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);
54
55 static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
56
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);
60
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,
68 PROPERTY_HANDLER_MAX
69 };
70
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 }
78 };
79
80 enum method_handler_index {
81 METHOD_HANDLER_KILL,
82 METHOD_HANDLER_UPDATE_PROPERTIES,
83 METHOD_HANDLER_REMOVE_PROPERTIES,
84 METHOD_HANDLER_MAX
85 };
86
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" } };
89
90 static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
91 [METHOD_HANDLER_KILL] = {
92 .method_name = "Kill",
93 .arguments = NULL,
94 .n_arguments = 0,
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 }
106 };
107
108 enum signal_index {
109 SIGNAL_PROPERTY_LIST_UPDATED,
110 SIGNAL_CLIENT_EVENT,
111 SIGNAL_MAX
112 };
113
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 } };
117
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 }
122 };
123
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,
131 .signals = signals,
132 .n_signals = SIGNAL_MAX
133 };
134
135 static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
136 pa_dbusiface_client *c = userdata;
137 dbus_uint32_t idx = 0;
138
139 pa_assert(conn);
140 pa_assert(msg);
141 pa_assert(c);
142
143 idx = c->client->index;
144
145 pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
146 }
147
148 static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) {
149 pa_dbusiface_client *c = userdata;
150
151 pa_assert(conn);
152 pa_assert(msg);
153 pa_assert(c);
154
155 pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->client->driver);
156 }
157
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;
161
162 pa_assert(conn);
163 pa_assert(msg);
164 pa_assert(c);
165
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);
168 return;
169 }
170
171 owner_module = pa_dbusiface_core_get_module_path(c->core, c->client->module);
172
173 pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &owner_module);
174 }
175
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;
179 unsigned i = 0;
180 uint32_t idx = 0;
181 pa_sink_input *sink_input = NULL;
182
183 pa_assert(c);
184 pa_assert(n);
185
186 *n = pa_idxset_size(c->client->sink_inputs);
187
188 if (*n == 0)
189 return NULL;
190
191 playback_streams = pa_xnew(const char *, *n);
192
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);
195
196 return playback_streams;
197 }
198
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;
203
204 pa_assert(conn);
205 pa_assert(msg);
206 pa_assert(c);
207
208 playback_streams = get_playback_streams(c, &n_playback_streams);
209
210 pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, playback_streams, n_playback_streams);
211
212 pa_xfree(playback_streams);
213 }
214
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;
218 unsigned i = 0;
219 uint32_t idx = 0;
220 pa_source_output *source_output = NULL;
221
222 pa_assert(c);
223 pa_assert(n);
224
225 *n = pa_idxset_size(c->client->source_outputs);
226
227 if (*n == 0)
228 return NULL;
229
230 record_streams = pa_xnew(const char *, *n);
231
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);
234
235 return record_streams;
236 }
237
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;
242
243 pa_assert(conn);
244 pa_assert(msg);
245 pa_assert(c);
246
247 record_streams = get_record_streams(c, &n_record_streams);
248
249 pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, record_streams, n_record_streams);
250
251 pa_xfree(record_streams);
252 }
253
254 static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
255 pa_dbusiface_client *c = userdata;
256
257 pa_assert(conn);
258 pa_assert(msg);
259 pa_assert(c);
260
261 pa_dbus_send_proplist_variant_reply(conn, msg, c->client->proplist);
262 }
263
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;
275
276 pa_assert(conn);
277 pa_assert(msg);
278 pa_assert(c);
279
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);
285
286 pa_assert_se((reply = dbus_message_new_method_return(msg)));
287
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));
290
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);
293
294 if (owner_module)
295 pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module);
296
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);
300
301 pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
302
303 pa_assert_se(dbus_connection_send(conn, reply, NULL));
304
305 dbus_message_unref(reply);
306
307 pa_xfree(playback_streams);
308 pa_xfree(record_streams);
309 }
310
311 static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata) {
312 pa_dbusiface_client *c = userdata;
313
314 pa_assert(conn);
315 pa_assert(msg);
316 pa_assert(c);
317
318 dbus_connection_ref(conn);
319
320 pa_client_kill(c->client);
321
322 pa_dbus_send_empty_reply(conn, msg);
323
324 dbus_connection_unref(conn);
325 }
326
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;
332
333 pa_assert(conn);
334 pa_assert(msg);
335 pa_assert(c);
336
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.");
339 return;
340 }
341
342 pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
343
344 if (!(property_list = pa_dbus_get_proplist_arg(conn, msg, &msg_iter)))
345 return;
346
347 dbus_message_iter_get_basic(&msg_iter, &update_mode);
348
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);
351 goto finish;
352 }
353
354 pa_client_update_proplist(c->client, update_mode, property_list);
355
356 pa_dbus_send_empty_reply(conn, msg);
357
358 finish:
359 if (property_list)
360 pa_proplist_free(property_list);
361 }
362
363 static void handle_remove_properties(DBusConnection *conn, DBusMessage *msg, void *userdata) {
364 pa_dbusiface_client *c = userdata;
365 char **keys = NULL;
366 int n_keys = 0;
367 pa_bool_t changed = FALSE;
368 int i = 0;
369
370 pa_assert(conn);
371 pa_assert(msg);
372 pa_assert(c);
373
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.");
376 return;
377 }
378
379 pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &keys, &n_keys, DBUS_TYPE_INVALID));
380
381 for (i = 0; i < n_keys; ++i)
382 changed |= pa_proplist_unset(c->client->proplist, keys[i]) >= 0;
383
384 pa_dbus_send_empty_reply(conn, msg);
385
386 if (changed)
387 pa_subscription_post(c->client->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->client->index);
388
389 dbus_free_string_array(keys);
390 }
391
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 = NULL;
395
396 pa_assert(core);
397 pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CLIENT);
398 pa_assert(c);
399
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)
403 return;
404
405 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
406 return;
407
408 if (!pa_proplist_equal(c->proplist, c->client->proplist)) {
409 DBusMessageIter msg_iter;
410
411 pa_proplist_update(c->proplist, PA_UPDATE_SET, c->client->proplist);
412
413 pa_assert_se(signal = 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_iter);
417 pa_dbus_append_proplist(&msg_iter, c->proplist);
418
419 pa_dbus_protocol_send_signal(c->dbus_protocol, signal);
420 dbus_message_unref(signal);
421 signal = NULL;
422 }
423 }
424
425 pa_dbusiface_client *pa_dbusiface_client_new(pa_dbusiface_core *core, pa_client *client) {
426 pa_dbusiface_client *c = NULL;
427
428 pa_assert(core);
429 pa_assert(client);
430
431 c = pa_xnew(pa_dbusiface_client, 1);
432 c->core = core;
433 c->client = client;
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);
438
439 pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, c->path, &client_interface_info, c) >= 0);
440
441 return c;
442 }
443
444 void pa_dbusiface_client_free(pa_dbusiface_client *c) {
445 pa_assert(c);
446
447 pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, c->path, client_interface_info.name) >= 0);
448
449 pa_dbus_protocol_unref(c->dbus_protocol);
450
451 pa_xfree(c->path);
452 pa_xfree(c);
453 }
454
455 const char *pa_dbusiface_client_get_path(pa_dbusiface_client *c) {
456 pa_assert(c);
457
458 return c->path;
459 }