1/*** 2 This file is part of PulseAudio. 3 4 Copyright 2004-2006 Lennart Poettering 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, see <http://www.gnu.org/licenses/>. 18***/ 19 20#ifdef HAVE_CONFIG_H 21#include <config.h> 22#endif 23 24#include <stdio.h> 25 26#include <pulse/xmalloc.h> 27 28#include <pulsecore/log.h> 29#include <pulsecore/macro.h> 30 31#include "core-subscribe.h" 32 33/* The subscription subsystem may be used to be notified whenever an 34 * entity (sink, source, ...) is created or deleted. Modules may 35 * register a callback function that is called whenever an event 36 * matching a subscription mask happens. The execution of the callback 37 * function is postponed to the next main loop iteration, i.e. is not 38 * called from within the stack frame the entity was created in. */ 39 40struct pa_subscription { 41 pa_core *core; 42 bool dead; 43 44 pa_subscription_cb_t callback; 45 void *userdata; 46 pa_subscription_mask_t mask; 47 48 PA_LLIST_FIELDS(pa_subscription); 49}; 50 51struct pa_subscription_event { 52 pa_core *core; 53 54 pa_subscription_event_type_t type; 55 uint32_t index; 56 57 PA_LLIST_FIELDS(pa_subscription_event); 58}; 59 60static void sched_event(pa_core *c); 61 62/* Allocate a new subscription object for the given subscription mask. Use the specified callback function and user data */ 63pa_subscription* pa_subscription_new(pa_core *c, pa_subscription_mask_t m, pa_subscription_cb_t callback, void *userdata) { 64 pa_subscription *s; 65 66 pa_assert(c); 67 pa_assert(m); 68 pa_assert(callback); 69 70 s = pa_xnew(pa_subscription, 1); 71 s->core = c; 72 s->dead = false; 73 s->callback = callback; 74 s->userdata = userdata; 75 s->mask = m; 76 77 PA_LLIST_PREPEND(pa_subscription, c->subscriptions, s); 78 return s; 79} 80 81/* Free a subscription object, effectively marking it for deletion */ 82void pa_subscription_free(pa_subscription*s) { 83 pa_assert(s); 84 pa_assert(!s->dead); 85 86 s->dead = true; 87 sched_event(s->core); 88} 89 90static void free_subscription(pa_subscription *s) { 91 pa_assert(s); 92 pa_assert(s->core); 93 94 PA_LLIST_REMOVE(pa_subscription, s->core->subscriptions, s); 95 pa_xfree(s); 96} 97 98static void free_event(pa_subscription_event *s) { 99 pa_assert(s); 100 pa_assert(s->core); 101 102 if (!s->next) 103 s->core->subscription_event_last = s->prev; 104 105 PA_LLIST_REMOVE(pa_subscription_event, s->core->subscription_event_queue, s); 106 pa_xfree(s); 107} 108 109/* Free all subscription objects */ 110void pa_subscription_free_all(pa_core *c) { 111 pa_assert(c); 112 113 while (c->subscriptions) 114 free_subscription(c->subscriptions); 115 116 while (c->subscription_event_queue) 117 free_event(c->subscription_event_queue); 118 119 if (c->subscription_defer_event) { 120 c->mainloop->defer_free(c->subscription_defer_event); 121 c->subscription_defer_event = NULL; 122 } 123} 124 125#ifdef DEBUG 126static void dump_event(const char * prefix, pa_subscription_event*e) { 127 const char * const fac_table[] = { 128 [PA_SUBSCRIPTION_EVENT_SINK] = "SINK", 129 [PA_SUBSCRIPTION_EVENT_SOURCE] = "SOURCE", 130 [PA_SUBSCRIPTION_EVENT_SINK_INPUT] = "SINK_INPUT", 131 [PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT] = "SOURCE_OUTPUT", 132 [PA_SUBSCRIPTION_EVENT_MODULE] = "MODULE", 133 [PA_SUBSCRIPTION_EVENT_CLIENT] = "CLIENT", 134 [PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE] = "SAMPLE_CACHE", 135 [PA_SUBSCRIPTION_EVENT_SERVER] = "SERVER", 136 [PA_SUBSCRIPTION_EVENT_AUTOLOAD] = "AUTOLOAD", 137 [PA_SUBSCRIPTION_EVENT_CARD] = "CARD" 138 }; 139 140 const char * const type_table[] = { 141 [PA_SUBSCRIPTION_EVENT_NEW] = "NEW", 142 [PA_SUBSCRIPTION_EVENT_CHANGE] = "CHANGE", 143 [PA_SUBSCRIPTION_EVENT_REMOVE] = "REMOVE" 144 }; 145 146 pa_log_debug("%s event (%s|%s|%u)", 147 prefix, 148 fac_table[e->type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK], 149 type_table[e->type & PA_SUBSCRIPTION_EVENT_TYPE_MASK], 150 e->index); 151} 152#endif 153 154/* Deferred callback for dispatching subscription events */ 155static void defer_cb(pa_mainloop_api *m, pa_defer_event *de, void *userdata) { 156 pa_core *c = userdata; 157 pa_subscription *s; 158 159 pa_assert(c->mainloop == m); 160 pa_assert(c); 161 pa_assert(c->subscription_defer_event == de); 162 163 c->mainloop->defer_enable(c->subscription_defer_event, 0); 164 165 /* Dispatch queued events */ 166 167 while (c->subscription_event_queue) { 168 pa_subscription_event *e = c->subscription_event_queue; 169 170 for (s = c->subscriptions; s; s = s->next) { 171 172 if (!s->dead && pa_subscription_match_flags(s->mask, e->type)) 173 s->callback(c, e->type, e->index, s->userdata); 174 } 175 176#ifdef DEBUG 177 dump_event("Dispatched", e); 178#endif 179 free_event(e); 180 } 181 182 /* Remove dead subscriptions */ 183 184 s = c->subscriptions; 185 while (s) { 186 pa_subscription *n = s->next; 187 if (s->dead) 188 free_subscription(s); 189 s = n; 190 } 191} 192 193/* Schedule an mainloop event so that a pending subscription event is dispatched */ 194static void sched_event(pa_core *c) { 195 pa_assert(c); 196 197 if (!c->subscription_defer_event) { 198 c->subscription_defer_event = c->mainloop->defer_new(c->mainloop, defer_cb, c); 199 pa_assert(c->subscription_defer_event); 200 } 201 202 c->mainloop->defer_enable(c->subscription_defer_event, 1); 203} 204 205/* Append a new subscription event to the subscription event queue and schedule a main loop event */ 206void pa_subscription_post(pa_core *c, pa_subscription_event_type_t t, uint32_t idx) { 207 pa_subscription_event *e; 208 pa_assert(c); 209 210 /* No need for queuing subscriptions of no one is listening */ 211 if (!c->subscriptions) 212 return; 213 214 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_NEW) { 215 pa_subscription_event *i, *n; 216 217 /* Check for duplicates */ 218 for (i = c->subscription_event_last; i; i = n) { 219 n = i->prev; 220 221 /* not the same object type */ 222 if (((t ^ i->type) & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)) 223 continue; 224 225 /* not the same object */ 226 if (i->index != idx) 227 continue; 228 229 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { 230 /* This object is being removed, hence there is no 231 * point in keeping the old events regarding this 232 * entry in the queue. */ 233 234 free_event(i); 235 pa_log_debug("Dropped redundant event due to remove event."); 236 continue; 237 } 238 239 if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) { 240 /* This object has changed. If a "new" or "change" event for 241 * this object is still in the queue we can exit. */ 242 243 pa_log_debug("Dropped redundant event due to change event."); 244 return; 245 } 246 } 247 } 248 249 e = pa_xnew(pa_subscription_event, 1); 250 e->core = c; 251 e->type = t; 252 e->index = idx; 253 254 PA_LLIST_INSERT_AFTER(pa_subscription_event, c->subscription_event_queue, c->subscription_event_last, e); 255 c->subscription_event_last = e; 256 257#ifdef DEBUG 258 dump_event("Queued", e); 259#endif 260 261 sched_event(c); 262} 263