]> code.delx.au - pulseaudio/blob - src/modules/reserve.c
bluetooth: Release MediaEnpoint if card profile is set to Off
[pulseaudio] / src / modules / reserve.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: t -*-*/
2
3 /***
4 Copyright 2009 Lennart Poettering
5
6 Permission is hereby granted, free of charge, to any person
7 obtaining a copy of this software and associated documentation files
8 (the "Software"), to deal in the Software without restriction,
9 including without limitation the rights to use, copy, modify, merge,
10 publish, distribute, sublicense, and/or sell copies of the Software,
11 and to permit persons to whom the Software is furnished to do so,
12 subject to the following conditions:
13
14 The above copyright notice and this permission notice shall be
15 included in all copies or substantial portions of the Software.
16
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
25 ***/
26
27 #include <string.h>
28 #include <unistd.h>
29 #include <errno.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <assert.h>
33
34 #include "reserve.h"
35
36 struct rd_device {
37 int ref;
38
39 char *device_name;
40 char *application_name;
41 char *application_device_name;
42 char *service_name;
43 char *object_path;
44 int32_t priority;
45
46 DBusConnection *connection;
47
48 unsigned owning:1;
49 unsigned registered:1;
50 unsigned filtering:1;
51 unsigned gave_up:1;
52
53 rd_request_cb_t request_cb;
54 void *userdata;
55 };
56
57 #define SERVICE_PREFIX "org.freedesktop.ReserveDevice1."
58 #define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/"
59
60 static const char introspection[] =
61 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
62 "<node>"
63 " <!-- If you are looking for documentation make sure to check out\n"
64 " http://git.0pointer.de/?p=reserve.git;a=blob;f=reserve.txt -->\n"
65 " <interface name=\"org.freedesktop.ReserveDevice1\">"
66 " <method name=\"RequestRelease\">"
67 " <arg name=\"priority\" type=\"i\" direction=\"in\"/>"
68 " <arg name=\"result\" type=\"b\" direction=\"out\"/>"
69 " </method>"
70 " <property name=\"Priority\" type=\"i\" access=\"read\"/>"
71 " <property name=\"ApplicationName\" type=\"s\" access=\"read\"/>"
72 " <property name=\"ApplicationDeviceName\" type=\"s\" access=\"read\"/>"
73 " </interface>"
74 " <interface name=\"org.freedesktop.DBus.Properties\">"
75 " <method name=\"Get\">"
76 " <arg name=\"interface\" direction=\"in\" type=\"s\"/>"
77 " <arg name=\"property\" direction=\"in\" type=\"s\"/>"
78 " <arg name=\"value\" direction=\"out\" type=\"v\"/>"
79 " </method>"
80 " </interface>"
81 " <interface name=\"org.freedesktop.DBus.Introspectable\">"
82 " <method name=\"Introspect\">"
83 " <arg name=\"data\" type=\"s\" direction=\"out\"/>"
84 " </method>"
85 " </interface>"
86 "</node>";
87
88 static dbus_bool_t add_variant(
89 DBusMessage *m,
90 int type,
91 const void *data) {
92
93 DBusMessageIter iter, sub;
94 char t[2];
95
96 t[0] = (char) type;
97 t[1] = 0;
98
99 dbus_message_iter_init_append(m, &iter);
100
101 if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, t, &sub))
102 return FALSE;
103
104 if (!dbus_message_iter_append_basic(&sub, type, data))
105 return FALSE;
106
107 if (!dbus_message_iter_close_container(&iter, &sub))
108 return FALSE;
109
110 return TRUE;
111 }
112
113 static DBusHandlerResult object_handler(
114 DBusConnection *c,
115 DBusMessage *m,
116 void *userdata) {
117
118 rd_device *d;
119 DBusError error;
120 DBusMessage *reply = NULL;
121
122 dbus_error_init(&error);
123
124 d = userdata;
125 assert(d->ref >= 1);
126
127 if (dbus_message_is_method_call(
128 m,
129 "org.freedesktop.ReserveDevice1",
130 "RequestRelease")) {
131
132 int32_t priority;
133 dbus_bool_t ret;
134
135 if (!dbus_message_get_args(
136 m,
137 &error,
138 DBUS_TYPE_INT32, &priority,
139 DBUS_TYPE_INVALID))
140 goto invalid;
141
142 ret = FALSE;
143
144 if (priority > d->priority && d->request_cb) {
145 d->ref++;
146
147 if (d->request_cb(d, 0) > 0) {
148 ret = TRUE;
149 d->gave_up = 1;
150 }
151
152 rd_release(d);
153 }
154
155 if (!(reply = dbus_message_new_method_return(m)))
156 goto oom;
157
158 if (!dbus_message_append_args(
159 reply,
160 DBUS_TYPE_BOOLEAN, &ret,
161 DBUS_TYPE_INVALID))
162 goto oom;
163
164 if (!dbus_connection_send(c, reply, NULL))
165 goto oom;
166
167 dbus_message_unref(reply);
168
169 return DBUS_HANDLER_RESULT_HANDLED;
170
171 } else if (dbus_message_is_method_call(
172 m,
173 "org.freedesktop.DBus.Properties",
174 "Get")) {
175
176 const char *interface, *property;
177
178 if (!dbus_message_get_args(
179 m,
180 &error,
181 DBUS_TYPE_STRING, &interface,
182 DBUS_TYPE_STRING, &property,
183 DBUS_TYPE_INVALID))
184 goto invalid;
185
186 if (strcmp(interface, "org.freedesktop.ReserveDevice1") == 0) {
187 const char *empty = "";
188
189 if (strcmp(property, "ApplicationName") == 0 && d->application_name) {
190 if (!(reply = dbus_message_new_method_return(m)))
191 goto oom;
192
193 if (!add_variant(
194 reply,
195 DBUS_TYPE_STRING,
196 d->application_name ? (const char**) &d->application_name : &empty))
197 goto oom;
198
199 } else if (strcmp(property, "ApplicationDeviceName") == 0) {
200 if (!(reply = dbus_message_new_method_return(m)))
201 goto oom;
202
203 if (!add_variant(
204 reply,
205 DBUS_TYPE_STRING,
206 d->application_device_name ? (const char**) &d->application_device_name : &empty))
207 goto oom;
208
209 } else if (strcmp(property, "Priority") == 0) {
210 if (!(reply = dbus_message_new_method_return(m)))
211 goto oom;
212
213 if (!add_variant(
214 reply,
215 DBUS_TYPE_INT32,
216 &d->priority))
217 goto oom;
218 } else {
219 if (!(reply = dbus_message_new_error_printf(
220 m,
221 DBUS_ERROR_UNKNOWN_METHOD,
222 "Unknown property %s",
223 property)))
224 goto oom;
225 }
226
227 if (!dbus_connection_send(c, reply, NULL))
228 goto oom;
229
230 dbus_message_unref(reply);
231
232 return DBUS_HANDLER_RESULT_HANDLED;
233 }
234
235 } else if (dbus_message_is_method_call(
236 m,
237 "org.freedesktop.DBus.Introspectable",
238 "Introspect")) {
239 const char *i = introspection;
240
241 if (!(reply = dbus_message_new_method_return(m)))
242 goto oom;
243
244 if (!dbus_message_append_args(
245 reply,
246 DBUS_TYPE_STRING,
247 &i,
248 DBUS_TYPE_INVALID))
249 goto oom;
250
251 if (!dbus_connection_send(c, reply, NULL))
252 goto oom;
253
254 dbus_message_unref(reply);
255
256 return DBUS_HANDLER_RESULT_HANDLED;
257 }
258
259 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
260
261 invalid:
262 if (reply)
263 dbus_message_unref(reply);
264
265 if (!(reply = dbus_message_new_error(
266 m,
267 DBUS_ERROR_INVALID_ARGS,
268 "Invalid arguments")))
269 goto oom;
270
271 if (!dbus_connection_send(c, reply, NULL))
272 goto oom;
273
274 dbus_message_unref(reply);
275
276 dbus_error_free(&error);
277
278 return DBUS_HANDLER_RESULT_HANDLED;
279
280 oom:
281 if (reply)
282 dbus_message_unref(reply);
283
284 dbus_error_free(&error);
285
286 return DBUS_HANDLER_RESULT_NEED_MEMORY;
287 }
288
289 static DBusHandlerResult filter_handler(
290 DBusConnection *c,
291 DBusMessage *m,
292 void *userdata) {
293
294 rd_device *d;
295 DBusError error;
296
297 dbus_error_init(&error);
298
299 d = userdata;
300 assert(d->ref >= 1);
301
302 if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) {
303 const char *name;
304
305 if (!dbus_message_get_args(
306 m,
307 &error,
308 DBUS_TYPE_STRING, &name,
309 DBUS_TYPE_INVALID))
310 goto invalid;
311
312 if (strcmp(name, d->service_name) == 0 && d->owning) {
313 d->owning = 0;
314
315 if (!d->gave_up) {
316 d->ref++;
317
318 if (d->request_cb)
319 d->request_cb(d, 1);
320 d->gave_up = 1;
321
322 rd_release(d);
323 }
324
325 }
326 }
327
328 invalid:
329 dbus_error_free(&error);
330
331 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
332 }
333
334
335 static const struct DBusObjectPathVTable vtable ={
336 .message_function = object_handler
337 };
338
339 int rd_acquire(
340 rd_device **_d,
341 DBusConnection *connection,
342 const char *device_name,
343 const char *application_name,
344 int32_t priority,
345 rd_request_cb_t request_cb,
346 DBusError *error) {
347
348 rd_device *d = NULL;
349 int r, k;
350 DBusError _error;
351 DBusMessage *m = NULL, *reply = NULL;
352 dbus_bool_t good;
353
354 if (!error)
355 error = &_error;
356
357 dbus_error_init(error);
358
359 if (!_d)
360 return -EINVAL;
361
362 if (!connection)
363 return -EINVAL;
364
365 if (!device_name)
366 return -EINVAL;
367
368 if (!request_cb && priority != INT32_MAX)
369 return -EINVAL;
370
371 if (!(d = calloc(sizeof(rd_device), 1)))
372 return -ENOMEM;
373
374 d->ref = 1;
375
376 if (!(d->device_name = strdup(device_name))) {
377 r = -ENOMEM;
378 goto fail;
379 }
380
381 if (!(d->application_name = strdup(application_name))) {
382 r = -ENOMEM;
383 goto fail;
384 }
385
386 d->priority = priority;
387 d->connection = dbus_connection_ref(connection);
388 d->request_cb = request_cb;
389
390 if (!(d->service_name = malloc(sizeof(SERVICE_PREFIX) + strlen(device_name)))) {
391 r = -ENOMEM;
392 goto fail;
393 }
394 sprintf(d->service_name, SERVICE_PREFIX "%s", d->device_name);
395
396 if (!(d->object_path = malloc(sizeof(OBJECT_PREFIX) + strlen(device_name)))) {
397 r = -ENOMEM;
398 goto fail;
399 }
400 sprintf(d->object_path, OBJECT_PREFIX "%s", d->device_name);
401
402 if ((k = dbus_bus_request_name(
403 d->connection,
404 d->service_name,
405 DBUS_NAME_FLAG_DO_NOT_QUEUE|
406 (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0),
407 error)) < 0) {
408 r = -EIO;
409 goto fail;
410 }
411
412 if (k == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
413 goto success;
414
415 if (k != DBUS_REQUEST_NAME_REPLY_EXISTS) {
416 r = -EIO;
417 goto fail;
418 }
419
420 if (priority <= INT32_MIN) {
421 r = -EBUSY;
422 goto fail;
423 }
424
425 if (!(m = dbus_message_new_method_call(
426 d->service_name,
427 d->object_path,
428 "org.freedesktop.ReserveDevice1",
429 "RequestRelease"))) {
430 r = -ENOMEM;
431 goto fail;
432 }
433
434 if (!dbus_message_append_args(
435 m,
436 DBUS_TYPE_INT32, &d->priority,
437 DBUS_TYPE_INVALID)) {
438 r = -ENOMEM;
439 goto fail;
440 }
441
442 if (!(reply = dbus_connection_send_with_reply_and_block(
443 d->connection,
444 m,
445 5000, /* 5s */
446 error))) {
447
448 if (dbus_error_has_name(error, DBUS_ERROR_TIMED_OUT) ||
449 dbus_error_has_name(error, DBUS_ERROR_UNKNOWN_METHOD) ||
450 dbus_error_has_name(error, DBUS_ERROR_NO_REPLY)) {
451 /* This must be treated as denied. */
452 r = -EBUSY;
453 goto fail;
454 }
455
456 r = -EIO;
457 goto fail;
458 }
459
460 if (!dbus_message_get_args(
461 reply,
462 error,
463 DBUS_TYPE_BOOLEAN, &good,
464 DBUS_TYPE_INVALID)) {
465 r = -EIO;
466 goto fail;
467 }
468
469 if (!good) {
470 r = -EBUSY;
471 goto fail;
472 }
473
474 if ((k = dbus_bus_request_name(
475 d->connection,
476 d->service_name,
477 DBUS_NAME_FLAG_DO_NOT_QUEUE|
478 (priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0)|
479 DBUS_NAME_FLAG_REPLACE_EXISTING,
480 error)) < 0) {
481 r = -EIO;
482 goto fail;
483 }
484
485 if (k != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
486 r = -EIO;
487 goto fail;
488 }
489
490 success:
491 d->owning = 1;
492
493 if (!(dbus_connection_register_object_path(
494 d->connection,
495 d->object_path,
496 &vtable,
497 d))) {
498 r = -ENOMEM;
499 goto fail;
500 }
501
502 d->registered = 1;
503
504 if (!dbus_connection_add_filter(
505 d->connection,
506 filter_handler,
507 d,
508 NULL)) {
509 r = -ENOMEM;
510 goto fail;
511 }
512
513 d->filtering = 1;
514
515 *_d = d;
516 return 0;
517
518 fail:
519 if (m)
520 dbus_message_unref(m);
521
522 if (reply)
523 dbus_message_unref(reply);
524
525 if (&_error == error)
526 dbus_error_free(&_error);
527
528 if (d)
529 rd_release(d);
530
531 return r;
532 }
533
534 void rd_release(
535 rd_device *d) {
536
537 if (!d)
538 return;
539
540 assert(d->ref > 0);
541
542 if (--d->ref > 0)
543 return;
544
545
546 if (d->filtering)
547 dbus_connection_remove_filter(
548 d->connection,
549 filter_handler,
550 d);
551
552 if (d->registered)
553 dbus_connection_unregister_object_path(
554 d->connection,
555 d->object_path);
556
557 if (d->owning)
558 dbus_bus_release_name(
559 d->connection,
560 d->service_name,
561 NULL);
562
563 free(d->device_name);
564 free(d->application_name);
565 free(d->application_device_name);
566 free(d->service_name);
567 free(d->object_path);
568
569 if (d->connection)
570 dbus_connection_unref(d->connection);
571
572 free(d);
573 }
574
575 int rd_set_application_device_name(rd_device *d, const char *n) {
576 char *t;
577
578 if (!d)
579 return -EINVAL;
580
581 assert(d->ref > 0);
582
583 if (!(t = strdup(n)))
584 return -ENOMEM;
585
586 free(d->application_device_name);
587 d->application_device_name = t;
588 return 0;
589 }
590
591 void rd_set_userdata(rd_device *d, void *userdata) {
592
593 if (!d)
594 return;
595
596 assert(d->ref > 0);
597 d->userdata = userdata;
598 }
599
600 void* rd_get_userdata(rd_device *d) {
601
602 if (!d)
603 return NULL;
604
605 assert(d->ref > 0);
606
607 return d->userdata;
608 }