]> git.sesse.net Git - vlc/blob - src/misc/events.c
misc/events.c: Make sure we don't leak.
[vlc] / src / misc / events.c
1 /*****************************************************************************
2  * events.c: events interface
3  * This library provides an interface to the send and receive events.
4  * It is more lightweight than variable based callback.
5  * Methode
6  *****************************************************************************
7  * Copyright (C) 1998-2005 the VideoLAN team
8  * $Id$
9  *
10  * Authors: Pierre d'Herbemont <pdherbemont # videolan.org >
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 /*****************************************************************************
28  * Preamble
29  *****************************************************************************/
30
31 #include <vlc/vlc.h>
32
33 #include <assert.h>
34
35 #include <vlc_events.h>
36 #include <vlc_arrays.h>
37
38 /*****************************************************************************
39  * Documentation : Read vlc_events.h
40  *****************************************************************************/
41
42 //#define DEBUG_EVENT
43
44 /*****************************************************************************
45  *  Private types.
46  *****************************************************************************/
47
48 typedef struct vlc_event_listener_t
49 {
50     void *               p_user_data;
51     vlc_event_callback_t pf_callback;
52 #ifdef DEBUG_EVENT
53     char *               psz_debug_name;
54 #endif
55 } vlc_event_listener_t;
56
57 typedef struct vlc_event_listeners_group_t
58 {
59     vlc_event_type_t    event_type;
60     DECL_ARRAY(struct vlc_event_listener_t *) listeners;
61
62    /* Used in vlc_event_send() to make sure to behave
63       Correctly when vlc_event_detach was called during
64       a callback */
65     vlc_bool_t          b_sublistener_removed;
66                                          
67 } vlc_event_listeners_group_t;
68
69 #ifdef DEBUG_EVENT
70 static const char * ppsz_event_type_to_name[] =
71 {
72     [vlc_InputItemMetaChanged]          = "vlc_InputItemMetaChanged",
73     [vlc_InputItemSubItemAdded]         = "vlc_InputItemSubItemAdded",
74     [vlc_ServicesDiscoveryItemAdded]    = "vlc_ServicesDiscoveryItemAdded",
75     [vlc_ServicesDiscoveryItemRemoved]  = "vlc_ServicesDiscoveryItemRemoved"
76 };
77 #endif
78
79 static vlc_bool_t
80 group_contains_listener( vlc_event_listeners_group_t * group,
81                          vlc_event_listener_t * searched_listener )
82 {
83     vlc_event_listener_t * listener;
84     FOREACH_ARRAY( listener, group->listeners )
85         if( searched_listener == listener )
86             return VLC_TRUE;
87     FOREACH_END()
88     return VLC_FALSE;
89 }
90
91 /*****************************************************************************
92  *
93  *****************************************************************************/
94
95 /**
96  * Initialize event manager object
97  * p_obj is the object that contains the event manager. But not
98  * necessarily a vlc_object_t (an input_item_t is not a vlc_object_t
99  * for instance).
100  * p_parent_obj gives a libvlc instance
101  */
102 int vlc_event_manager_init( vlc_event_manager_t * p_em, void * p_obj,
103                             vlc_object_t * p_parent_obj )
104 {
105     p_em->p_obj = p_obj;
106     p_em->p_parent_object = p_parent_obj;
107     vlc_mutex_init( p_parent_obj, &p_em->object_lock );
108
109     /* We need a recursive lock here, because we need to be able
110      * to call libvlc_event_detach even if vlc_event_send is in
111      * the call frame.
112      * This ensures that after libvlc_event_detach, the callback
113      * will never gets triggered.
114      * */
115     vlc_mutex_init_recursive( p_parent_obj, &p_em->event_sending_lock );
116     ARRAY_INIT( p_em->listeners_groups );
117     return VLC_SUCCESS;
118 }
119
120 /**
121  * Destroy the event manager
122  */
123 void vlc_event_manager_fini( vlc_event_manager_t * p_em )
124 {
125     struct vlc_event_listeners_group_t * listeners_group;
126     struct vlc_event_listener_t * listener;
127
128     vlc_mutex_destroy( &p_em->object_lock );
129     vlc_mutex_destroy( &p_em->event_sending_lock );
130
131     FOREACH_ARRAY( listeners_group, p_em->listeners_groups )
132         FOREACH_ARRAY( listener, listeners_group->listeners )
133             free( listener );
134         FOREACH_END()
135         ARRAY_RESET( listeners_group->listeners );
136         free( listeners_group );
137     FOREACH_END()
138     ARRAY_RESET( p_em->listeners_groups );
139 }
140
141 /**
142  * Destroy the event manager
143  */
144 int vlc_event_manager_register_event_type(
145         vlc_event_manager_t * p_em,
146         vlc_event_type_t event_type )
147 {
148     vlc_event_listeners_group_t * listeners_group;
149     listeners_group = malloc(sizeof(vlc_event_listeners_group_t));
150
151     if( !listeners_group )
152         return VLC_ENOMEM;
153
154     listeners_group->event_type = event_type;
155     ARRAY_INIT( listeners_group->listeners );
156  
157     vlc_mutex_lock( &p_em->object_lock );
158     ARRAY_APPEND( p_em->listeners_groups, listeners_group );
159     vlc_mutex_unlock( &p_em->object_lock );
160
161     return VLC_SUCCESS;
162 }
163
164 /**
165  * Send an event to the listener attached to this p_em.
166  */
167 void vlc_event_send( vlc_event_manager_t * p_em,
168                      vlc_event_t * p_event )
169 {
170     vlc_event_listeners_group_t * listeners_group = NULL;
171     vlc_event_listener_t * listener;
172     vlc_event_listener_t * array_of_cached_listeners = NULL;
173     vlc_event_listener_t * cached_listener;
174     int i, i_cached_listeners = 0;
175
176     /* Fill event with the sending object now */
177     p_event->p_obj = p_em->p_obj;
178
179     vlc_mutex_lock( &p_em->object_lock );
180     FOREACH_ARRAY( listeners_group, p_em->listeners_groups )
181         if( listeners_group->event_type == p_event->type )
182         {
183             if( listeners_group->listeners.i_size <= 0 )
184                 break;
185
186             /* Save the function to call */
187             i_cached_listeners = listeners_group->listeners.i_size;
188             array_of_cached_listeners = malloc(
189                     sizeof(vlc_event_listener_t)*i_cached_listeners );
190             if( !array_of_cached_listeners )
191             {
192                 msg_Err( p_em->p_parent_object, "Not enough memory in vlc_event_send" );
193                 vlc_mutex_unlock( &p_em->object_lock );
194                 return;
195             }
196
197             cached_listener = array_of_cached_listeners;
198             FOREACH_ARRAY( listener, listeners_group->listeners )
199                 memcpy( cached_listener, listener, sizeof(vlc_event_listener_t));
200 #ifdef DEBUG_EVENT
201                 cached_listener->psz_debug_name = strdup(cached_listener->psz_debug_name);
202 #endif
203                 cached_listener++;
204             FOREACH_END()
205
206             break;
207         }
208     FOREACH_END()
209     vlc_mutex_unlock( &p_em->object_lock );
210  
211     /* Call the function attached */
212     cached_listener = array_of_cached_listeners;
213
214     if( !listeners_group || !array_of_cached_listeners )
215     {
216         free( array_of_cached_listeners );
217         return;
218     }
219
220     vlc_mutex_lock( &p_em->event_sending_lock ) ;
221
222     /* Track item removed from *this* thread, with a simple flag */
223     listeners_group->b_sublistener_removed = VLC_FALSE;
224
225     for( i = 0; i < i_cached_listeners; i++ )
226     {
227 #ifdef DEBUG_EVENT
228         msg_Dbg( p_em->p_parent_object,
229                     "Calling '%s' with a '%s' event (data %p)",
230                     cached_listener->psz_debug_name,
231                     ppsz_event_type_to_name[p_event->type],
232                     cached_listener->p_user_data );
233         free(cached_listener->psz_debug_name);
234 #endif
235         /* No need to lock on listeners_group, a listener group can't be removed */
236         if( listeners_group->b_sublistener_removed )
237         {
238             /* If a callback was removed, this gets called */
239             vlc_bool_t valid_listener;
240             vlc_mutex_lock( &p_em->object_lock );
241             valid_listener = group_contains_listener( listeners_group, cached_listener );
242             vlc_mutex_unlock( &p_em->object_lock );
243             if( !valid_listener )
244             {
245 #ifdef DEBUG_EVENT
246                 msg_Dbg( p_em->p_parent_object, "Callback was removed during execution" );
247 #endif
248                 cached_listener++;
249                 continue;
250             }
251         }
252         cached_listener->pf_callback( p_event, cached_listener->p_user_data );
253         cached_listener++;
254     }
255     vlc_mutex_unlock( &p_em->event_sending_lock );
256
257     free( array_of_cached_listeners );
258 }
259
260 /**
261  * Add a callback for an event.
262  */
263 int __vlc_event_attach( vlc_event_manager_t * p_em,
264                         vlc_event_type_t event_type,
265                         vlc_event_callback_t pf_callback,
266                         void *p_user_data,
267                         const char * psz_debug_name )
268 {
269     vlc_event_listeners_group_t * listeners_group;
270     vlc_event_listener_t * listener;
271     listener = malloc(sizeof(vlc_event_listener_t));
272     if( !listener )
273         return VLC_ENOMEM;
274  
275     listener->p_user_data = p_user_data;
276     listener->pf_callback = pf_callback;
277 #ifdef DEBUG_EVENT
278     listener->psz_debug_name = strdup( psz_debug_name );
279 #else
280     (void)psz_debug_name;
281 #endif
282
283     vlc_mutex_lock( &p_em->object_lock );
284     FOREACH_ARRAY( listeners_group, p_em->listeners_groups )
285         if( listeners_group->event_type == event_type )
286         {
287             ARRAY_APPEND( listeners_group->listeners, listener );
288 #ifdef DEBUG_EVENT
289                 msg_Dbg( p_em->p_parent_object,
290                     "Listening to '%s' event with '%s' (data %p)",
291                     ppsz_event_type_to_name[event_type],
292                     listener->psz_debug_name,
293                     listener->p_user_data );
294 #endif
295             vlc_mutex_unlock( &p_em->object_lock );
296             return VLC_SUCCESS;
297         }
298     FOREACH_END()
299     vlc_mutex_unlock( &p_em->object_lock );
300
301     msg_Err( p_em->p_parent_object, "Can't attach to an object event manager event" );
302     free(listener);
303     return VLC_EGENERIC;
304 }
305
306 /**
307  * Remove a callback for an event.
308  */
309
310 int vlc_event_detach( vlc_event_manager_t *p_em,
311                       vlc_event_type_t event_type,
312                       vlc_event_callback_t pf_callback,
313                       void *p_user_data )
314 {
315     vlc_event_listeners_group_t * listeners_group;
316     struct vlc_event_listener_t * listener;
317
318     vlc_mutex_lock( &p_em->object_lock );
319     vlc_mutex_lock( &p_em->event_sending_lock );
320     FOREACH_ARRAY( listeners_group, p_em->listeners_groups )
321         if( listeners_group->event_type == event_type )
322         {
323             FOREACH_ARRAY( listener, listeners_group->listeners )
324                 if( listener->pf_callback == pf_callback &&
325                     listener->p_user_data == p_user_data )
326                 {
327                     /* Tell vlc_event_send, we did remove an item from that group,
328                        in case vlc_event_send is in our caller stack  */
329                     listeners_group->b_sublistener_removed = VLC_TRUE;
330
331                     /* that's our listener */
332                     ARRAY_REMOVE( listeners_group->listeners,
333                         fe_idx /* This comes from the macro (and that's why
334                                   I hate macro) */ );
335 #ifdef DEBUG_EVENT
336                     msg_Dbg( p_em->p_parent_object,
337                         "Detaching '%s' from '%s' event (data %p)",
338                         listener->psz_debug_name,
339                         ppsz_event_type_to_name[event_type],
340                         listener->p_user_data );
341
342                     free( listener->psz_debug_name );
343 #endif
344                     free( listener );
345                     vlc_mutex_unlock( &p_em->event_sending_lock );
346                     vlc_mutex_unlock( &p_em->object_lock );
347                     return VLC_SUCCESS;
348                 }
349             FOREACH_END()
350         }
351     FOREACH_END()
352     vlc_mutex_unlock( &p_em->event_sending_lock );
353     vlc_mutex_unlock( &p_em->object_lock );
354
355     msg_Warn( p_em->p_parent_object, "Can't detach to an object event manager event" );
356
357     return VLC_EGENERIC;
358 }
359