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
35struct 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
47static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
48static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata);
49static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata);
50static void handle_get_playback_streams(DBusConnection *conn, DBusMessage *msg, void *userdata);
51static void handle_get_record_streams(DBusConnection *conn, DBusMessage *msg, void *userdata);
52static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
53
54static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
55
56static void handle_kill(DBusConnection *conn, DBusMessage *msg, void *userdata);
57static void handle_update_properties(DBusConnection *conn, DBusMessage *msg, void *userdata);
58static void handle_remove_properties(DBusConnection *conn, DBusMessage *msg, void *userdata);
59
60enum 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
70static 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
79enum method_handler_index {
80    METHOD_HANDLER_KILL,
81    METHOD_HANDLER_UPDATE_PROPERTIES,
82    METHOD_HANDLER_REMOVE_PROPERTIES,
83    METHOD_HANDLER_MAX
84};
85
86static pa_dbus_arg_info update_properties_args[] = { { "property_list", "a{say}", "in" }, { "update_mode", "u", "in" } };
87static pa_dbus_arg_info remove_properties_args[] = { { "keys", "as", "in" } };
88
89static 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
107enum signal_index {
108    SIGNAL_PROPERTY_LIST_UPDATED,
109    SIGNAL_CLIENT_EVENT,
110    SIGNAL_MAX
111};
112
113static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } };
114static pa_dbus_arg_info client_event_args[]          = { { "name",          "s",      NULL },
115                                                         { "property_list", "a{say}", NULL } };
116
117static 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
123static 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
134static 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
147static 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
157static 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. */
176static 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
198static 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. */
215static 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
237static 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
253static 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
263static 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
310static 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
326static 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
357finish:
358    if (property_list)
359        pa_proplist_free(property_list);
360}
361
362static 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
393static 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
422pa_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
442void 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
455const char *pa_dbusiface_client_get_path(pa_dbusiface_client *c) {
456    pa_assert(c);
457
458    return c->path;
459}
460