]> git.sesse.net Git - vlc/blob - src/control/event.c
libvlc_event_manager: remove exceptions
[vlc] / src / control / event.c
1 /*****************************************************************************
2  * event.c: New libvlc event control API
3  *****************************************************************************
4  * Copyright (C) 2007 the VideoLAN team
5  * $Id $
6  *
7  * Authors: Filippo Carone <filippo@carone.org>
8  *          Pierre d'Herbemont <pdherbemont # videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License along
21  * with this program; if not, write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #define  LIBVLC_EVENT_TYPES_KEEP_DEFINE
30 #include <vlc/libvlc.h>
31
32 #include "libvlc_internal.h"
33 #include "event_internal.h"
34 #include <assert.h>
35 #include <errno.h>
36
37 typedef struct libvlc_event_listeners_group_t
38 {
39     libvlc_event_type_t event_type;
40     vlc_array_t listeners;
41     bool b_sublistener_removed;
42 } libvlc_event_listeners_group_t;
43
44 /*
45  * Private functions
46  */
47
48 static bool
49 group_contains_listener( libvlc_event_listeners_group_t * group,
50                          libvlc_event_listener_t * searched_listener )
51 {
52     int i;
53     for( i = 0; i < vlc_array_count(&group->listeners); i++ )
54     {
55         if( listeners_are_equal(searched_listener, vlc_array_item_at_index(&group->listeners, i)) )
56             return true;
57     }
58     return false;
59 }
60
61 /*
62  * Internal libvlc functions
63  */
64
65 /**************************************************************************
66  *       libvlc_event_manager_new (internal) :
67  *
68  * Init an object's event manager.
69  **************************************************************************/
70 libvlc_event_manager_t *
71 libvlc_event_manager_new( void * p_obj, libvlc_instance_t * p_libvlc_inst )
72 {
73     libvlc_event_manager_t * p_em;
74
75     p_em = malloc(sizeof( libvlc_event_manager_t ));
76     if( !p_em )
77     {
78         libvlc_printerr( "Not enough memory" );
79         return NULL;
80     }
81
82     p_em->p_obj = p_obj;
83     p_em->p_obj = p_obj;
84     p_em->async_event_queue = NULL;
85     p_em->p_libvlc_instance = p_libvlc_inst;
86
87     libvlc_retain( p_libvlc_inst );
88     vlc_array_init( &p_em->listeners_groups );
89     vlc_mutex_init( &p_em->object_lock );
90     vlc_mutex_init_recursive( &p_em->event_sending_lock );
91     return p_em;
92 }
93
94 /**************************************************************************
95  *       libvlc_event_manager_release (internal) :
96  *
97  * Release an object's event manager.
98  **************************************************************************/
99 void libvlc_event_manager_release( libvlc_event_manager_t * p_em )
100 {
101     libvlc_event_listeners_group_t * p_lg;
102     int i,j ;
103
104     libvlc_event_async_fini(p_em);
105
106     vlc_mutex_destroy( &p_em->event_sending_lock );
107     vlc_mutex_destroy( &p_em->object_lock );
108
109     for( i = 0; i < vlc_array_count(&p_em->listeners_groups); i++)
110     {
111         p_lg = vlc_array_item_at_index( &p_em->listeners_groups, i );
112
113         for( j = 0; j < vlc_array_count(&p_lg->listeners); j++)
114             free( vlc_array_item_at_index( &p_lg->listeners, j ) );
115
116         vlc_array_clear( &p_lg->listeners );
117         free( p_lg );
118     }
119     vlc_array_clear( &p_em->listeners_groups );
120     libvlc_release( p_em->p_libvlc_instance );
121     free( p_em );
122 }
123
124 /**************************************************************************
125  *       libvlc_event_manager_register_event_type (internal) :
126  *
127  * Init an object's event manager.
128  **************************************************************************/
129 void libvlc_event_manager_register_event_type(
130         libvlc_event_manager_t * p_em,
131         libvlc_event_type_t event_type )
132 {
133     libvlc_event_listeners_group_t * listeners_group;
134     listeners_group = xmalloc(sizeof(libvlc_event_listeners_group_t));
135     listeners_group->event_type = event_type;
136     vlc_array_init( &listeners_group->listeners );
137
138     vlc_mutex_lock( &p_em->object_lock );
139     vlc_array_append( &p_em->listeners_groups, listeners_group );
140     vlc_mutex_unlock( &p_em->object_lock );
141 }
142
143 /**************************************************************************
144  *       libvlc_event_send (internal) :
145  *
146  * Send a callback.
147  **************************************************************************/
148 void libvlc_event_send( libvlc_event_manager_t * p_em,
149                         libvlc_event_t * p_event )
150 {
151     libvlc_event_listeners_group_t * listeners_group = NULL;
152     libvlc_event_listener_t * listener_cached;
153     libvlc_event_listener_t * listener;
154     libvlc_event_listener_t * array_listeners_cached = NULL;
155     int i, i_cached_listeners = 0;
156
157     /* Fill event with the sending object now */
158     p_event->p_obj = p_em->p_obj;
159
160     /* Here a read/write lock would be nice */
161
162     vlc_mutex_lock( &p_em->object_lock );
163     for( i = 0; i < vlc_array_count(&p_em->listeners_groups); i++)
164     {
165         listeners_group = vlc_array_item_at_index(&p_em->listeners_groups, i);
166         if( listeners_group->event_type == p_event->type )
167         {
168             if( vlc_array_count( &listeners_group->listeners ) <= 0 )
169                 break;
170
171             /* Cache a copy of the listener to avoid locking issues,
172              * and allow that edition of listeners during callbacks will garantee immediate effect. */
173             i_cached_listeners = vlc_array_count(&listeners_group->listeners);
174             array_listeners_cached = malloc(sizeof(libvlc_event_listener_t)*(i_cached_listeners));
175             if( !array_listeners_cached )
176             {
177                 vlc_mutex_unlock( &p_em->object_lock );
178                 fprintf(stderr, "Can't alloc memory in libvlc_event_send" );
179                 return;
180             }
181
182             listener_cached = array_listeners_cached;
183             for( i = 0; i < vlc_array_count(&listeners_group->listeners); i++)
184             {
185                 listener = vlc_array_item_at_index(&listeners_group->listeners, i);
186                 memcpy( listener_cached, listener, sizeof(libvlc_event_listener_t) );
187                 listener_cached++;
188             }
189             break;
190         }
191     }
192
193     if( !listeners_group )
194     {
195         free( array_listeners_cached );
196         vlc_mutex_unlock( &p_em->object_lock );
197         return;
198     }
199
200     vlc_mutex_unlock( &p_em->object_lock );
201
202     vlc_mutex_lock( &p_em->event_sending_lock );
203     listener_cached = array_listeners_cached;
204     listeners_group->b_sublistener_removed = false;
205     for( i = 0; i < i_cached_listeners; i++ )
206     {
207         if(listener_cached->is_asynchronous)
208         {
209             /* The listener wants not to block the emitter during event callback */
210             libvlc_event_async_dispatch(p_em, listener_cached, p_event);
211         }
212         else
213         {
214             /* The listener wants to block the emitter during event callback */
215             
216             listener_cached->pf_callback( p_event, listener_cached->p_user_data );
217             listener_cached++;
218             
219             if( listeners_group->b_sublistener_removed )
220             {
221                 /* If a callback was removed, this gets called */
222                 bool valid_listener;
223                 vlc_mutex_lock( &p_em->object_lock );
224                 valid_listener = group_contains_listener( listeners_group, listener_cached );
225                 vlc_mutex_unlock( &p_em->object_lock );
226                 if( !valid_listener )
227                 {
228                     listener_cached++;
229                     continue;
230                 }
231             }            
232         }
233     }
234     vlc_mutex_unlock( &p_em->event_sending_lock );
235
236     free( array_listeners_cached );
237 }
238
239 /*
240  * Public libvlc functions
241  */
242
243 /**************************************************************************
244  *       libvlc_event_type_name (public) :
245  *
246  * Get the char * name of an event type.
247  **************************************************************************/
248 static const char event_type_to_name[][libvlc_num_event_types] =
249 {
250 #define DEF(a) [libvlc_##a]=#a
251     DEFINE_LIBVLC_EVENT_TYPES
252 #undef  DEF
253 };
254
255 static const char unknown_event_name[] = "Unknown Event";
256
257 const char * libvlc_event_type_name( libvlc_event_type_t event_type )
258 {
259     if( event_type >= libvlc_num_event_types )
260         return unknown_event_name;
261     return event_type_to_name[event_type];
262 }
263
264 /**************************************************************************
265  *       event_attach (internal) :
266  *
267  * Add a callback for an event.
268  **************************************************************************/
269 static
270 int event_attach( libvlc_event_manager_t * p_event_manager,
271                   libvlc_event_type_t event_type,
272                   libvlc_callback_t pf_callback, void *p_user_data,
273                   bool is_asynchronous )
274 {
275     libvlc_event_listeners_group_t * listeners_group;
276     libvlc_event_listener_t * listener;
277     int i;
278     
279     listener = malloc(sizeof(libvlc_event_listener_t));
280     if( unlikely(listener == NULL) )
281         return ENOMEM;
282     
283     listener->event_type = event_type;
284     listener->p_user_data = p_user_data;
285     listener->pf_callback = pf_callback;
286     listener->is_asynchronous = is_asynchronous;
287     
288     vlc_mutex_lock( &p_event_manager->object_lock );
289     for( i = 0; i < vlc_array_count(&p_event_manager->listeners_groups); i++ )
290     {
291         listeners_group = vlc_array_item_at_index(&p_event_manager->listeners_groups, i);
292         if( listeners_group->event_type == listener->event_type )
293         {
294             vlc_array_append( &listeners_group->listeners, listener );
295             vlc_mutex_unlock( &p_event_manager->object_lock );
296             return 0;
297         }
298     }
299     vlc_mutex_unlock( &p_event_manager->object_lock );
300     
301     free(listener);
302     fprintf( stderr, "This object event manager doesn't know about '%s' events",
303              libvlc_event_type_name(event_type) );
304     assert(0);
305 }
306
307 /**************************************************************************
308  *       libvlc_event_attach (public) :
309  *
310  * Add a callback for an event.
311  **************************************************************************/
312 int libvlc_event_attach( libvlc_event_manager_t * p_event_manager,
313                          libvlc_event_type_t event_type,
314                          libvlc_callback_t pf_callback,
315                          void *p_user_data )
316 {
317     return event_attach(p_event_manager, event_type, pf_callback, p_user_data,
318                         false /* synchronous */);
319 }
320
321 /**************************************************************************
322  *       libvlc_event_attach (public) :
323  *
324  * Add a callback for an event.
325  **************************************************************************/
326 void libvlc_event_attach_async( libvlc_event_manager_t * p_event_manager,
327                          libvlc_event_type_t event_type,
328                          libvlc_callback_t pf_callback,
329                          void *p_user_data )
330 {
331     event_attach(p_event_manager, event_type, pf_callback, p_user_data,
332                  true /* asynchronous */);
333 }
334
335 /**************************************************************************
336  *       libvlc_event_detach (public) :
337  *
338  * Remove a callback for an event.
339  **************************************************************************/
340 void libvlc_event_detach( libvlc_event_manager_t *p_event_manager,
341                                      libvlc_event_type_t event_type,
342                                      libvlc_callback_t pf_callback,
343                                      void *p_user_data )
344 {
345     libvlc_event_listeners_group_t * listeners_group;
346     libvlc_event_listener_t * listener;
347     int i, j;
348     bool found = false;
349     
350     vlc_mutex_lock( &p_event_manager->event_sending_lock );
351     vlc_mutex_lock( &p_event_manager->object_lock );
352     for( i = 0; i < vlc_array_count(&p_event_manager->listeners_groups); i++)
353     {
354         listeners_group = vlc_array_item_at_index(&p_event_manager->listeners_groups, i);
355         if( listeners_group->event_type == event_type )
356         {
357             for( j = 0; j < vlc_array_count(&listeners_group->listeners); j++)
358             {
359                 listener = vlc_array_item_at_index(&listeners_group->listeners, j);
360                 if( listener->event_type == event_type &&
361                     listener->pf_callback == pf_callback &&
362                     listener->p_user_data == p_user_data )
363                 {
364                     /* that's our listener */
365
366                     /* Mark this group as edited so that libvlc_event_send
367                      * will recheck what listener to call */
368                     listeners_group->b_sublistener_removed = false;
369
370                     free( listener );
371                     vlc_array_remove( &listeners_group->listeners, j );
372                     found = true;
373                     break;
374                 }
375             }
376         }
377     }
378     vlc_mutex_unlock( &p_event_manager->object_lock );
379     vlc_mutex_unlock( &p_event_manager->event_sending_lock );
380
381     /* Now make sure any pending async event won't get fired after that point */
382     libvlc_event_listener_t listener_to_remove;
383     listener_to_remove.event_type  = event_type;
384     listener_to_remove.pf_callback = pf_callback;
385     listener_to_remove.p_user_data = p_user_data;
386     listener_to_remove.is_asynchronous = true;
387
388     libvlc_event_async_ensure_listener_removal(p_event_manager, &listener_to_remove);
389
390     assert(found);
391 }