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