]> code.delx.au - pulseaudio/blob - src/modules/dbus/iface-card.c
dbus: Support dynamically created card profiles
[pulseaudio] / src / modules / dbus / iface-card.c
1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2009 Tanu Kaskinen
5
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 2.1 of the License,
9 or (at your option) any later version.
10
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with PulseAudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19 USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <dbus/dbus.h>
27
28 #include <pulsecore/core-util.h>
29 #include <pulsecore/dbus-util.h>
30 #include <pulsecore/protocol-dbus.h>
31
32 #include "iface-card-profile.h"
33
34 #include "iface-card.h"
35
36 #define OBJECT_NAME "card"
37
38 static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
39 static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
40 static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata);
41 static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata);
42 static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata);
43 static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata);
44 static void handle_get_profiles(DBusConnection *conn, DBusMessage *msg, void *userdata);
45 static void handle_get_active_profile(DBusConnection *conn, DBusMessage *msg, void *userdata);
46 static void handle_set_active_profile(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
47 static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata);
48
49 static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
50
51 static void handle_get_profile_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
52
53 struct pa_dbusiface_card {
54 pa_dbusiface_core *core;
55
56 pa_card *card;
57 char *path;
58 pa_hashmap *profiles;
59 uint32_t next_profile_index;
60 pa_card_profile *active_profile;
61 pa_proplist *proplist;
62
63 pa_hook_slot *card_profile_added_slot;
64
65 pa_dbus_protocol *dbus_protocol;
66 pa_subscription *subscription;
67 };
68
69 enum property_handler_index {
70 PROPERTY_HANDLER_INDEX,
71 PROPERTY_HANDLER_NAME,
72 PROPERTY_HANDLER_DRIVER,
73 PROPERTY_HANDLER_OWNER_MODULE,
74 PROPERTY_HANDLER_SINKS,
75 PROPERTY_HANDLER_SOURCES,
76 PROPERTY_HANDLER_PROFILES,
77 PROPERTY_HANDLER_ACTIVE_PROFILE,
78 PROPERTY_HANDLER_PROPERTY_LIST,
79 PROPERTY_HANDLER_MAX
80 };
81
82 static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
83 [PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_get_index, .set_cb = NULL },
84 [PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_get_name, .set_cb = NULL },
85 [PROPERTY_HANDLER_DRIVER] = { .property_name = "Driver", .type = "s", .get_cb = handle_get_driver, .set_cb = NULL },
86 [PROPERTY_HANDLER_OWNER_MODULE] = { .property_name = "OwnerModule", .type = "o", .get_cb = handle_get_owner_module, .set_cb = NULL },
87 [PROPERTY_HANDLER_SINKS] = { .property_name = "Sinks", .type = "ao", .get_cb = handle_get_sinks, .set_cb = NULL },
88 [PROPERTY_HANDLER_SOURCES] = { .property_name = "Sources", .type = "ao", .get_cb = handle_get_sources, .set_cb = NULL },
89 [PROPERTY_HANDLER_PROFILES] = { .property_name = "Profiles", .type = "ao", .get_cb = handle_get_profiles, .set_cb = NULL },
90 [PROPERTY_HANDLER_ACTIVE_PROFILE] = { .property_name = "ActiveProfile", .type = "o", .get_cb = handle_get_active_profile, .set_cb = handle_set_active_profile },
91 [PROPERTY_HANDLER_PROPERTY_LIST] = { .property_name = "PropertyList", .type = "a{say}", .get_cb = handle_get_property_list, .set_cb = NULL }
92 };
93
94 enum method_handler_index {
95 METHOD_HANDLER_GET_PROFILE_BY_NAME,
96 METHOD_HANDLER_MAX
97 };
98
99 static pa_dbus_arg_info get_profile_by_name_args[] = { { "name", "s", "in" }, { "profile", "o", "out" } };
100
101 static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
102 [METHOD_HANDLER_GET_PROFILE_BY_NAME] = {
103 .method_name = "GetProfileByName",
104 .arguments = get_profile_by_name_args,
105 .n_arguments = sizeof(get_profile_by_name_args) / sizeof(pa_dbus_arg_info),
106 .receive_cb = handle_get_profile_by_name }
107 };
108
109 enum signal_index {
110 SIGNAL_ACTIVE_PROFILE_UPDATED,
111 SIGNAL_PROPERTY_LIST_UPDATED,
112 SIGNAL_MAX
113 };
114
115 static pa_dbus_arg_info active_profile_updated_args[] = { { "profile", "o", NULL } };
116 static pa_dbus_arg_info property_list_updated_args[] = { { "property_list", "a{say}", NULL } };
117
118 static pa_dbus_signal_info signals[SIGNAL_MAX] = {
119 [SIGNAL_ACTIVE_PROFILE_UPDATED] = { .name = "ActiveProfileUpdated", .arguments = active_profile_updated_args, .n_arguments = 1 },
120 [SIGNAL_PROPERTY_LIST_UPDATED] = { .name = "PropertyListUpdated", .arguments = property_list_updated_args, .n_arguments = 1 }
121 };
122
123 static pa_dbus_interface_info card_interface_info = {
124 .name = PA_DBUSIFACE_CARD_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
134 static void handle_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
135 pa_dbusiface_card *c = userdata;
136 dbus_uint32_t idx;
137
138 pa_assert(conn);
139 pa_assert(msg);
140 pa_assert(c);
141
142 idx = c->card->index;
143
144 pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &idx);
145 }
146
147 static void handle_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
148 pa_dbusiface_card *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->card->name);
155 }
156
157 static void handle_get_driver(DBusConnection *conn, DBusMessage *msg, void *userdata) {
158 pa_dbusiface_card *c = userdata;
159
160 pa_assert(conn);
161 pa_assert(msg);
162 pa_assert(c);
163
164 pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &c->card->driver);
165 }
166
167 static void handle_get_owner_module(DBusConnection *conn, DBusMessage *msg, void *userdata) {
168 pa_dbusiface_card *c = userdata;
169 const char *owner_module;
170
171 pa_assert(conn);
172 pa_assert(msg);
173 pa_assert(c);
174
175 if (!c->card->module) {
176 pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "Card %s doesn't have an owner module.", c->card->name);
177 return;
178 }
179
180 owner_module = pa_dbusiface_core_get_module_path(c->core, c->card->module);
181
182 pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &owner_module);
183 }
184
185 /* The caller frees the array, but not the strings. */
186 static const char **get_sinks(pa_dbusiface_card *c, unsigned *n) {
187 const char **sinks = NULL;
188 unsigned i = 0;
189 uint32_t idx = 0;
190 pa_sink *sink = NULL;
191
192 pa_assert(c);
193 pa_assert(n);
194
195 *n = pa_idxset_size(c->card->sinks);
196
197 if (*n == 0)
198 return NULL;
199
200 sinks = pa_xnew(const char *, *n);
201
202 PA_IDXSET_FOREACH(sink, c->card->sinks, idx) {
203 sinks[i] = pa_dbusiface_core_get_sink_path(c->core, sink);
204 ++i;
205 }
206
207 return sinks;
208 }
209
210 static void handle_get_sinks(DBusConnection *conn, DBusMessage *msg, void *userdata) {
211 pa_dbusiface_card *c = userdata;
212 const char **sinks;
213 unsigned n_sinks;
214
215 pa_assert(conn);
216 pa_assert(msg);
217 pa_assert(c);
218
219 sinks = get_sinks(c, &n_sinks);
220
221 pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks);
222
223 pa_xfree(sinks);
224 }
225
226 /* The caller frees the array, but not the strings. */
227 static const char **get_sources(pa_dbusiface_card *c, unsigned *n) {
228 const char **sources = NULL;
229 unsigned i = 0;
230 uint32_t idx = 0;
231 pa_source *source = NULL;
232
233 pa_assert(c);
234 pa_assert(n);
235
236 *n = pa_idxset_size(c->card->sources);
237
238 if (*n == 0)
239 return NULL;
240
241 sources = pa_xnew(const char *, *n);
242
243 PA_IDXSET_FOREACH(source, c->card->sinks, idx) {
244 sources[i] = pa_dbusiface_core_get_source_path(c->core, source);
245 ++i;
246 }
247
248 return sources;
249 }
250
251 static void handle_get_sources(DBusConnection *conn, DBusMessage *msg, void *userdata) {
252 pa_dbusiface_card *c = userdata;
253 const char **sources;
254 unsigned n_sources;
255
256 pa_assert(conn);
257 pa_assert(msg);
258 pa_assert(c);
259
260 sources = get_sources(c, &n_sources);
261
262 pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, sources, n_sources);
263
264 pa_xfree(sources);
265 }
266
267 /* The caller frees the array, but not the strings. */
268 static const char **get_profiles(pa_dbusiface_card *c, unsigned *n) {
269 const char **profiles;
270 unsigned i = 0;
271 void *state = NULL;
272 pa_dbusiface_card_profile *profile;
273
274 pa_assert(c);
275 pa_assert(n);
276
277 *n = pa_hashmap_size(c->profiles);
278
279 if (*n == 0)
280 return NULL;
281
282 profiles = pa_xnew(const char *, *n);
283
284 PA_HASHMAP_FOREACH(profile, c->profiles, state)
285 profiles[i++] = pa_dbusiface_card_profile_get_path(profile);
286
287 return profiles;
288 }
289
290 static void handle_get_profiles(DBusConnection *conn, DBusMessage *msg, void *userdata) {
291 pa_dbusiface_card *c = userdata;
292 const char **profiles;
293 unsigned n_profiles;
294
295 pa_assert(conn);
296 pa_assert(msg);
297 pa_assert(c);
298
299 profiles = get_profiles(c, &n_profiles);
300
301 pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, profiles, n_profiles);
302
303 pa_xfree(profiles);
304 }
305
306 static void handle_get_active_profile(DBusConnection *conn, DBusMessage *msg, void *userdata) {
307 pa_dbusiface_card *c = userdata;
308 const char *active_profile;
309
310 pa_assert(conn);
311 pa_assert(msg);
312 pa_assert(c);
313
314 active_profile = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name));
315 pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &active_profile);
316 }
317
318 static void handle_set_active_profile(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
319 pa_dbusiface_card *c = userdata;
320 const char *new_active_path;
321 pa_dbusiface_card_profile *new_active;
322 int r;
323
324 pa_assert(conn);
325 pa_assert(msg);
326 pa_assert(iter);
327 pa_assert(c);
328
329 dbus_message_iter_get_basic(iter, &new_active_path);
330
331 if (!(new_active = pa_hashmap_get(c->profiles, new_active_path))) {
332 pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such profile.", new_active_path);
333 return;
334 }
335
336 if ((r = pa_card_set_profile(c->card, pa_dbusiface_card_profile_get_name(new_active), TRUE)) < 0) {
337 pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
338 "Internal error in PulseAudio: pa_card_set_profile() failed with error code %i.", r);
339 return;
340 }
341
342 pa_dbus_send_empty_reply(conn, msg);
343 }
344
345 static void handle_get_property_list(DBusConnection *conn, DBusMessage *msg, void *userdata) {
346 pa_dbusiface_card *c = userdata;
347
348 pa_assert(conn);
349 pa_assert(msg);
350 pa_assert(c);
351
352 pa_dbus_send_proplist_variant_reply(conn, msg, c->proplist);
353 }
354
355 static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
356 pa_dbusiface_card *c = userdata;
357 DBusMessage *reply = NULL;
358 DBusMessageIter msg_iter;
359 DBusMessageIter dict_iter;
360 dbus_uint32_t idx;
361 const char *owner_module = NULL;
362 const char **sinks = NULL;
363 unsigned n_sinks = 0;
364 const char **sources = NULL;
365 unsigned n_sources = 0;
366 const char **profiles = NULL;
367 unsigned n_profiles = 0;
368 const char *active_profile = NULL;
369
370 pa_assert(conn);
371 pa_assert(msg);
372 pa_assert(c);
373
374 idx = c->card->index;
375 if (c->card->module)
376 owner_module = pa_dbusiface_core_get_module_path(c->core, c->card->module);
377 sinks = get_sinks(c, &n_sinks);
378 sources = get_sources(c, &n_sources);
379 profiles = get_profiles(c, &n_profiles);
380 active_profile = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name));
381
382 pa_assert_se((reply = dbus_message_new_method_return(msg)));
383
384 dbus_message_iter_init_append(reply, &msg_iter);
385 pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
386
387 pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &idx);
388 pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &c->card->name);
389 pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_DRIVER].property_name, DBUS_TYPE_STRING, &c->card->driver);
390
391 if (owner_module)
392 pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_OWNER_MODULE].property_name, DBUS_TYPE_OBJECT_PATH, &owner_module);
393
394 pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SINKS].property_name, DBUS_TYPE_OBJECT_PATH, sinks, n_sinks);
395 pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_SOURCES].property_name, DBUS_TYPE_OBJECT_PATH, sources, n_sources);
396 pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROFILES].property_name, DBUS_TYPE_OBJECT_PATH, profiles, n_profiles);
397 pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ACTIVE_PROFILE].property_name, DBUS_TYPE_OBJECT_PATH, &active_profile);
398
399 pa_dbus_append_proplist_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_PROPERTY_LIST].property_name, c->proplist);
400
401 pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
402
403 pa_assert_se(dbus_connection_send(conn, reply, NULL));
404
405 dbus_message_unref(reply);
406
407 pa_xfree(sinks);
408 pa_xfree(sources);
409 pa_xfree(profiles);
410 }
411
412 static void handle_get_profile_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
413 pa_dbusiface_card *c = userdata;
414 const char *profile_name = NULL;
415 pa_dbusiface_card_profile *profile = NULL;
416 const char *profile_path = NULL;
417
418 pa_assert(conn);
419 pa_assert(msg);
420 pa_assert(c);
421
422 pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &profile_name, DBUS_TYPE_INVALID));
423
424 if (!(profile = pa_hashmap_get(c->profiles, profile_name))) {
425 pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "%s: No such profile on card %s.", profile_name, c->card->name);
426 return;
427 }
428
429 profile_path = pa_dbusiface_card_profile_get_path(profile);
430
431 pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &profile_path);
432 }
433
434 static void subscription_cb(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
435 pa_dbusiface_card *c = userdata;
436 DBusMessage *signal_msg = NULL;
437
438 pa_assert(core);
439 pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_CARD);
440 pa_assert(c);
441
442 /* We can't use idx != c->card->index, because the c->card pointer may
443 * be stale at this point. */
444 if (pa_idxset_get_by_index(core->cards, idx) != c->card)
445 return;
446
447 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE)
448 return;
449
450 if (c->active_profile != c->card->active_profile) {
451 const char *object_path;
452
453 c->active_profile = c->card->active_profile;
454 object_path = pa_dbusiface_card_profile_get_path(pa_hashmap_get(c->profiles, c->active_profile->name));
455
456 pa_assert_se(signal_msg = dbus_message_new_signal(c->path,
457 PA_DBUSIFACE_CARD_INTERFACE,
458 signals[SIGNAL_ACTIVE_PROFILE_UPDATED].name));
459 pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID));
460
461 pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg);
462 dbus_message_unref(signal_msg);
463 signal_msg = NULL;
464 }
465
466 if (!pa_proplist_equal(c->proplist, c->card->proplist)) {
467 DBusMessageIter msg_iter;
468
469 pa_proplist_update(c->proplist, PA_UPDATE_SET, c->card->proplist);
470
471 pa_assert_se(signal_msg = dbus_message_new_signal(c->path,
472 PA_DBUSIFACE_CARD_INTERFACE,
473 signals[SIGNAL_PROPERTY_LIST_UPDATED].name));
474 dbus_message_iter_init_append(signal_msg, &msg_iter);
475 pa_dbus_append_proplist(&msg_iter, c->proplist);
476
477 pa_dbus_protocol_send_signal(c->dbus_protocol, signal_msg);
478 dbus_message_unref(signal_msg);
479 signal_msg = NULL;
480 }
481 }
482
483 static pa_hook_result_t card_profile_added_cb(void *hook_data, void *call_data, void *slot_data) {
484 pa_core *core = hook_data;
485 pa_dbusiface_card *c = slot_data;
486 pa_card_profile *profile = call_data;
487 pa_dbusiface_card_profile *p;
488
489 if (profile->card != c->card)
490 return PA_HOOK_OK;
491
492 p = pa_dbusiface_card_profile_new(c, core, profile, c->next_profile_index++);
493 pa_assert_se(pa_hashmap_put(c->profiles, pa_dbusiface_card_profile_get_name(p), p) >= 0);
494
495 return PA_HOOK_OK;
496 }
497
498 pa_dbusiface_card *pa_dbusiface_card_new(pa_dbusiface_core *core, pa_card *card) {
499 pa_dbusiface_card *c = NULL;
500 pa_card_profile *profile;
501 void *state;
502
503 pa_assert(core);
504 pa_assert(card);
505
506 c = pa_xnew0(pa_dbusiface_card, 1);
507 c->core = core;
508 c->card = card;
509 c->path = pa_sprintf_malloc("%s/%s%u", PA_DBUS_CORE_OBJECT_PATH, OBJECT_NAME, card->index);
510 c->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
511 c->next_profile_index = 0;
512 c->active_profile = card->active_profile;
513 c->proplist = pa_proplist_copy(card->proplist);
514 c->dbus_protocol = pa_dbus_protocol_get(card->core);
515 c->subscription = pa_subscription_new(card->core, PA_SUBSCRIPTION_MASK_CARD, subscription_cb, c);
516
517 PA_HASHMAP_FOREACH(profile, card->profiles, state) {
518 pa_dbusiface_card_profile *p = pa_dbusiface_card_profile_new(c, card->core, profile, c->next_profile_index++);
519 pa_hashmap_put(c->profiles, pa_dbusiface_card_profile_get_name(p), p);
520 }
521
522 pa_assert_se(pa_dbus_protocol_add_interface(c->dbus_protocol, c->path, &card_interface_info, c) >= 0);
523
524 c->card_profile_added_slot = pa_hook_connect(&card->core->hooks[PA_CORE_HOOK_CARD_PROFILE_ADDED], PA_HOOK_NORMAL,
525 card_profile_added_cb, c);
526
527 return c;
528 }
529
530 static void profile_free_cb(void *p, void *userdata) {
531 pa_dbusiface_card_profile *profile = p;
532
533 pa_assert(profile);
534
535 pa_dbusiface_card_profile_free(profile);
536 }
537
538 void pa_dbusiface_card_free(pa_dbusiface_card *c) {
539 pa_assert(c);
540
541 pa_assert_se(pa_dbus_protocol_remove_interface(c->dbus_protocol, c->path, card_interface_info.name) >= 0);
542
543 pa_hook_slot_free(c->card_profile_added_slot);
544
545 pa_hashmap_free(c->profiles, profile_free_cb, NULL);
546 pa_proplist_free(c->proplist);
547 pa_dbus_protocol_unref(c->dbus_protocol);
548 pa_subscription_free(c->subscription);
549
550 pa_xfree(c->path);
551 pa_xfree(c);
552 }
553
554 const char *pa_dbusiface_card_get_path(pa_dbusiface_card *c) {
555 pa_assert(c);
556
557 return c->path;
558 }