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