]> git.sesse.net Git - vlc/blob - src/misc/events.c
misc/events.c: Fix a case about a callback non being called.
[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 #ifdef HAVE_CONFIG_H
32 # include "config.h"
33 #endif
34
35 #include <vlc/vlc.h>
36
37 #include <assert.h>
38
39 #include <vlc_events.h>
40 #include <vlc_arrays.h>
41
42 /*****************************************************************************
43  * Documentation : Read vlc_events.h
44  *****************************************************************************/
45
46 //#define DEBUG_EVENT
47
48 /*****************************************************************************
49  *  Private types.
50  *****************************************************************************/
51
52 typedef struct vlc_event_listener_t
53 {
54     void *               p_user_data;
55     vlc_event_callback_t pf_callback;
56 #ifdef DEBUG_EVENT
57     char *               psz_debug_name;
58 #endif
59 } vlc_event_listener_t;
60
61 typedef struct vlc_event_listeners_group_t
62 {
63     vlc_event_type_t    event_type;
64     DECL_ARRAY(struct vlc_event_listener_t *) listeners;
65
66    /* Used in vlc_event_send() to make sure to behave
67       Correctly when vlc_event_detach was called during
68       a callback */
69     vlc_bool_t          b_sublistener_removed;
70                                          
71 } vlc_event_listeners_group_t;
72
73 #ifdef DEBUG_EVENT
74 static const char * ppsz_event_type_to_name[] =
75 {
76     [vlc_InputItemMetaChanged]          = "vlc_InputItemMetaChanged",
77     [vlc_InputItemSubItemAdded]         = "vlc_InputItemSubItemAdded",
78     [vlc_ServicesDiscoveryItemAdded]    = "vlc_ServicesDiscoveryItemAdded",
79     [vlc_ServicesDiscoveryItemRemoved]  = "vlc_ServicesDiscoveryItemRemoved"
80 };
81 #endif
82
83 static vlc_bool_t
84 listeners_are_equal( vlc_event_listener_t * listener1,
85                      vlc_event_listener_t * listener2 )
86 {
87     return listener1->pf_callback == listener2->pf_callback &&
88            listener1->p_user_data == listener2->p_user_data;
89 }
90
91 static vlc_bool_t
92 group_contains_listener( vlc_event_listeners_group_t * group,
93                          vlc_event_listener_t * searched_listener )
94 {
95     vlc_event_listener_t * listener;
96     FOREACH_ARRAY( listener, group->listeners )
97         if( listeners_are_equal(searched_listener, listener) )
98             return VLC_TRUE;
99     FOREACH_END()
100     return VLC_FALSE;
101 }
102
103 /*****************************************************************************
104  *
105  *****************************************************************************/
106
107 /**
108  * Initialize event manager object
109  * p_obj is the object that contains the event manager. But not
110  * necessarily a vlc_object_t (an input_item_t is not a vlc_object_t
111  * for instance).
112  * p_parent_obj gives a libvlc instance
113  */
114 int vlc_event_manager_init( vlc_event_manager_t * p_em, void * p_obj,
115                             vlc_object_t * p_parent_obj )
116 {
117     p_em->p_obj = p_obj;
118     p_em->p_parent_object = p_parent_obj;
119     vlc_mutex_init( p_parent_obj, &p_em->object_lock );
120
121     /* We need a recursive lock here, because we need to be able
122      * to call libvlc_event_detach even if vlc_event_send is in
123      * the call frame.
124      * This ensures that after libvlc_event_detach, the callback
125      * will never gets triggered.
126      * */
127     vlc_mutex_init_recursive( p_parent_obj, &p_em->event_sending_lock );
128     ARRAY_INIT( p_em->listeners_groups );
129     return VLC_SUCCESS;
130 }
131
132 /**
133  * Destroy the event manager
134  */
135 void vlc_event_manager_fini( vlc_event_manager_t * p_em )
136 {
137     struct vlc_event_listeners_group_t * listeners_group;
138     struct vlc_event_listener_t * listener;
139
140     vlc_mutex_destroy( &p_em->object_lock );
141     vlc_mutex_destroy( &p_em->event_sending_lock );
142
143     FOREACH_ARRAY( listeners_group, p_em->listeners_groups )
144         FOREACH_ARRAY( listener, listeners_group->listeners )
145             free( listener );
146         FOREACH_END()
147         ARRAY_RESET( listeners_group->listeners );
148         free( listeners_group );
149     FOREACH_END()
150     ARRAY_RESET( p_em->listeners_groups );
151 }
152
153 /**
154  * Destroy the event manager
155  */
156 int vlc_event_manager_register_event_type(
157         vlc_event_manager_t * p_em,
158         vlc_event_type_t event_type )
159 {
160     vlc_event_listeners_group_t * listeners_group;
161     listeners_group = malloc(sizeof(vlc_event_listeners_group_t));
162
163     if( !listeners_group )
164         return VLC_ENOMEM;
165
166     listeners_group->event_type = event_type;
167     ARRAY_INIT( listeners_group->listeners );
168  
169     vlc_mutex_lock( &p_em->object_lock );
170     ARRAY_APPEND( p_em->listeners_groups, listeners_group );
171     vlc_mutex_unlock( &p_em->object_lock );
172
173     return VLC_SUCCESS;
174 }
175
176 /**
177  * Send an event to the listener attached to this p_em.
178  */
179 void vlc_event_send( vlc_event_manager_t * p_em,
180                      vlc_event_t * p_event )
181 {
182     vlc_event_listeners_group_t * listeners_group = NULL;
183     vlc_event_listener_t * listener;
184     vlc_event_listener_t * array_of_cached_listeners = NULL;
185     vlc_event_listener_t * cached_listener;
186     int i, i_cached_listeners = 0;
187
188     /* Fill event with the sending object now */
189     p_event->p_obj = p_em->p_obj;
190
191     vlc_mutex_lock( &p_em->object_lock );
192     FOREACH_ARRAY( listeners_group, p_em->listeners_groups )
193         if( listeners_group->event_type == p_event->type )
194         {
195             if( listeners_group->listeners.i_size <= 0 )
196                 break;
197
198             /* Save the function to call */
199             i_cached_listeners = listeners_group->listeners.i_size;
200             array_of_cached_listeners = malloc(
201                     sizeof(vlc_event_listener_t)*i_cached_listeners );
202             if( !array_of_cached_listeners )
203             {
204                 msg_Err( p_em->p_parent_object, "Not enough memory in vlc_event_send" );
205                 vlc_mutex_unlock( &p_em->object_lock );
206                 return;
207             }
208
209             cached_listener = array_of_cached_listeners;
210             FOREACH_ARRAY( listener, listeners_group->listeners )
211                 memcpy( cached_listener, listener, sizeof(vlc_event_listener_t));
212 #ifdef DEBUG_EVENT
213                 cached_listener->psz_debug_name = strdup(cached_listener->psz_debug_name);
214 #endif
215                 cached_listener++;
216             FOREACH_END()
217
218             break;
219         }
220     FOREACH_END()
221     vlc_mutex_unlock( &p_em->object_lock );
222  
223     /* Call the function attached */
224     cached_listener = array_of_cached_listeners;
225
226     if( !listeners_group || !array_of_cached_listeners )
227     {
228         free( array_of_cached_listeners );
229         return;
230     }
231
232     vlc_mutex_lock( &p_em->event_sending_lock ) ;
233
234     /* Track item removed from *this* thread, with a simple flag */
235     listeners_group->b_sublistener_removed = VLC_FALSE;
236
237     for( i = 0; i < i_cached_listeners; i++ )
238     {
239 #ifdef DEBUG_EVENT
240         msg_Dbg( p_em->p_parent_object,
241                     "Calling '%s' with a '%s' event (data %p)",
242                     cached_listener->psz_debug_name,
243                     ppsz_event_type_to_name[p_event->type],
244                     cached_listener->p_user_data );
245         free(cached_listener->psz_debug_name);
246 #endif
247         /* No need to lock on listeners_group, a listener group can't be removed */
248         if( listeners_group->b_sublistener_removed )
249         {
250             /* If a callback was removed, this gets called */
251             vlc_bool_t valid_listener;
252             vlc_mutex_lock( &p_em->object_lock );
253             valid_listener = group_contains_listener( listeners_group, cached_listener );
254             vlc_mutex_unlock( &p_em->object_lock );
255             if( !valid_listener )
256             {
257 #ifdef DEBUG_EVENT
258                 msg_Dbg( p_em->p_parent_object, "Callback was removed during execution" );
259 #endif
260                 cached_listener++;
261                 continue;
262             }
263         }
264         cached_listener->pf_callback( p_event, cached_listener->p_user_data );
265         cached_listener++;
266     }
267     vlc_mutex_unlock( &p_em->event_sending_lock );
268
269     free( array_of_cached_listeners );
270 }
271
272 /**
273  * Add a callback for an event.
274  */
275 int __vlc_event_attach( vlc_event_manager_t * p_em,
276                         vlc_event_type_t event_type,
277                         vlc_event_callback_t pf_callback,
278                         void *p_user_data,
279                         const char * psz_debug_name )
280 {
281     vlc_event_listeners_group_t * listeners_group;
282     vlc_event_listener_t * listener;
283     listener = malloc(sizeof(vlc_event_listener_t));
284     if( !listener )
285         return VLC_ENOMEM;
286  
287     listener->p_user_data = p_user_data;
288     listener->pf_callback = pf_callback;
289 #ifdef DEBUG_EVENT
290     listener->psz_debug_name = strdup( psz_debug_name );
291 #else
292     (void)psz_debug_name;
293 #endif
294
295     vlc_mutex_lock( &p_em->object_lock );
296     FOREACH_ARRAY( listeners_group, p_em->listeners_groups )
297         if( listeners_group->event_type == event_type )
298         {
299             ARRAY_APPEND( listeners_group->listeners, listener );
300 #ifdef DEBUG_EVENT
301                 msg_Dbg( p_em->p_parent_object,
302                     "Listening to '%s' event with '%s' (data %p)",
303                     ppsz_event_type_to_name[event_type],
304                     listener->psz_debug_name,
305                     listener->p_user_data );
306 #endif
307             vlc_mutex_unlock( &p_em->object_lock );
308             return VLC_SUCCESS;
309         }
310     FOREACH_END()
311     vlc_mutex_unlock( &p_em->object_lock );
312
313     msg_Err( p_em->p_parent_object, "Can't attach to an object event manager event" );
314     free(listener);
315     return VLC_EGENERIC;
316 }
317
318 /**
319  * Remove a callback for an event.
320  */
321
322 int vlc_event_detach( vlc_event_manager_t *p_em,
323                       vlc_event_type_t event_type,
324                       vlc_event_callback_t pf_callback,
325                       void *p_user_data )
326 {
327     vlc_event_listeners_group_t * listeners_group;
328     struct vlc_event_listener_t * listener;
329
330     vlc_mutex_lock( &p_em->object_lock );
331     vlc_mutex_lock( &p_em->event_sending_lock );
332     FOREACH_ARRAY( listeners_group, p_em->listeners_groups )
333         if( listeners_group->event_type == event_type )
334         {
335             FOREACH_ARRAY( listener, listeners_group->listeners )
336                 if( listener->pf_callback == pf_callback &&
337                     listener->p_user_data == p_user_data )
338                 {
339                     /* Tell vlc_event_send, we did remove an item from that group,
340                        in case vlc_event_send is in our caller stack  */
341                     listeners_group->b_sublistener_removed = VLC_TRUE;
342
343                     /* that's our listener */
344                     ARRAY_REMOVE( listeners_group->listeners,
345                         fe_idx /* This comes from the macro (and that's why
346                                   I hate macro) */ );
347 #ifdef DEBUG_EVENT
348                     msg_Dbg( p_em->p_parent_object,
349                         "Detaching '%s' from '%s' event (data %p)",
350                         listener->psz_debug_name,
351                         ppsz_event_type_to_name[event_type],
352                         listener->p_user_data );
353
354                     free( listener->psz_debug_name );
355 #endif
356                     free( listener );
357                     vlc_mutex_unlock( &p_em->event_sending_lock );
358                     vlc_mutex_unlock( &p_em->object_lock );
359                     return VLC_SUCCESS;
360                 }
361             FOREACH_END()
362         }
363     FOREACH_END()
364     vlc_mutex_unlock( &p_em->event_sending_lock );
365     vlc_mutex_unlock( &p_em->object_lock );
366
367     msg_Warn( p_em->p_parent_object, "Can't detach to an object event manager event" );
368
369     return VLC_EGENERIC;
370 }
371