]> git.sesse.net Git - vlc/blob - src/control/event.c
5b57fc2742928a405e4c54ac523a81b430b8ed9b
[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
36 typedef struct libvlc_event_listeners_group_t
37 {
38     libvlc_event_type_t event_type;
39     vlc_array_t listeners;
40     bool b_sublistener_removed;
41 } libvlc_event_listeners_group_t;
42
43 /*
44  * Private functions
45  */
46
47 static bool
48 group_contains_listener( libvlc_event_listeners_group_t * group,
49                          libvlc_event_listener_t * searched_listener )
50 {
51     int i;
52     for( i = 0; i < vlc_array_count(&group->listeners); i++ )
53     {
54         if( listeners_are_equal(searched_listener, vlc_array_item_at_index(&group->listeners, i)) )
55             return true;
56     }
57     return false;
58 }
59
60 /*
61  * Internal libvlc functions
62  */
63
64 /**************************************************************************
65  *       libvlc_event_manager_new (internal) :
66  *
67  * Init an object's event manager.
68  **************************************************************************/
69 libvlc_event_manager_t *
70 libvlc_event_manager_new( void * p_obj, libvlc_instance_t * p_libvlc_inst,
71                            libvlc_exception_t *p_e )
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_exception_raise( p_e );
79         libvlc_printerr( "Not enough memory" );
80         return NULL;
81     }
82
83     p_em->p_obj = p_obj;
84     p_em->p_obj = p_obj;
85     p_em->async_event_queue = NULL;
86     p_em->p_libvlc_instance = p_libvlc_inst;
87
88     libvlc_retain( p_libvlc_inst );
89     vlc_array_init( &p_em->listeners_groups );
90     vlc_mutex_init( &p_em->object_lock );
91     vlc_mutex_init_recursive( &p_em->event_sending_lock );
92     return p_em;
93 }
94
95 /**************************************************************************
96  *       libvlc_event_manager_release (internal) :
97  *
98  * Release an object's event manager.
99  **************************************************************************/
100 void libvlc_event_manager_release( libvlc_event_manager_t * p_em )
101 {
102     libvlc_event_listeners_group_t * p_lg;
103     int i,j ;
104
105     libvlc_event_async_fini(p_em);
106
107     vlc_mutex_destroy( &p_em->event_sending_lock );
108     vlc_mutex_destroy( &p_em->object_lock );
109
110     for( i = 0; i < vlc_array_count(&p_em->listeners_groups); i++)
111     {
112         p_lg = vlc_array_item_at_index( &p_em->listeners_groups, i );
113
114         for( j = 0; j < vlc_array_count(&p_lg->listeners); j++)
115             free( vlc_array_item_at_index( &p_lg->listeners, j ) );
116
117         vlc_array_clear( &p_lg->listeners );
118         free( p_lg );
119     }
120     vlc_array_clear( &p_em->listeners_groups );
121     libvlc_release( p_em->p_libvlc_instance );
122     free( p_em );
123 }
124
125 /**************************************************************************
126  *       libvlc_event_manager_register_event_type (internal) :
127  *
128  * Init an object's event manager.
129  **************************************************************************/
130 void libvlc_event_manager_register_event_type(
131         libvlc_event_manager_t * p_em,
132         libvlc_event_type_t event_type,
133         libvlc_exception_t * p_e )
134 {
135     libvlc_event_listeners_group_t * listeners_group;
136     listeners_group = malloc(sizeof(libvlc_event_listeners_group_t));
137     if( !listeners_group )
138     {
139         libvlc_exception_raise( p_e );
140         libvlc_printerr( "Not enough memory" );
141         return;
142     }
143
144     listeners_group->event_type = event_type;
145     vlc_array_init( &listeners_group->listeners );
146
147     vlc_mutex_lock( &p_em->object_lock );
148     vlc_array_append( &p_em->listeners_groups, listeners_group );
149     vlc_mutex_unlock( &p_em->object_lock );
150 }
151
152 /**************************************************************************
153  *       libvlc_event_send (internal) :
154  *
155  * Send a callback.
156  **************************************************************************/
157 void libvlc_event_send( libvlc_event_manager_t * p_em,
158                         libvlc_event_t * p_event )
159 {
160     libvlc_event_listeners_group_t * listeners_group = NULL;
161     libvlc_event_listener_t * listener_cached;
162     libvlc_event_listener_t * listener;
163     libvlc_event_listener_t * array_listeners_cached = NULL;
164     int i, i_cached_listeners = 0;
165
166     /* Fill event with the sending object now */
167     p_event->p_obj = p_em->p_obj;
168
169     /* Here a read/write lock would be nice */
170
171     vlc_mutex_lock( &p_em->object_lock );
172     for( i = 0; i < vlc_array_count(&p_em->listeners_groups); i++)
173     {
174         listeners_group = vlc_array_item_at_index(&p_em->listeners_groups, i);
175         if( listeners_group->event_type == p_event->type )
176         {
177             if( vlc_array_count( &listeners_group->listeners ) <= 0 )
178                 break;
179
180             /* Cache a copy of the listener to avoid locking issues,
181              * and allow that edition of listeners during callbacks will garantee immediate effect. */
182             i_cached_listeners = vlc_array_count(&listeners_group->listeners);
183             array_listeners_cached = malloc(sizeof(libvlc_event_listener_t)*(i_cached_listeners));
184             if( !array_listeners_cached )
185             {
186                 vlc_mutex_unlock( &p_em->object_lock );
187                 fprintf(stderr, "Can't alloc memory in libvlc_event_send" );
188                 return;
189             }
190
191             listener_cached = array_listeners_cached;
192             for( i = 0; i < vlc_array_count(&listeners_group->listeners); i++)
193             {
194                 listener = vlc_array_item_at_index(&listeners_group->listeners, i);
195                 memcpy( listener_cached, listener, sizeof(libvlc_event_listener_t) );
196                 listener_cached++;
197             }
198             break;
199         }
200     }
201
202     if( !listeners_group )
203     {
204         free( array_listeners_cached );
205         vlc_mutex_unlock( &p_em->object_lock );
206         return;
207     }
208
209     vlc_mutex_unlock( &p_em->object_lock );
210
211     vlc_mutex_lock( &p_em->event_sending_lock );
212     listener_cached = array_listeners_cached;
213     listeners_group->b_sublistener_removed = false;
214     for( i = 0; i < i_cached_listeners; i++ )
215     {
216         if(listener_cached->is_asynchronous)
217         {
218             /* The listener wants not to block the emitter during event callback */
219             libvlc_event_async_dispatch(p_em, listener_cached, p_event);
220         }
221         else
222         {
223             /* The listener wants to block the emitter during event callback */
224             
225             listener_cached->pf_callback( p_event, listener_cached->p_user_data );
226             listener_cached++;
227             
228             if( listeners_group->b_sublistener_removed )
229             {
230                 /* If a callback was removed, this gets called */
231                 bool valid_listener;
232                 vlc_mutex_lock( &p_em->object_lock );
233                 valid_listener = group_contains_listener( listeners_group, listener_cached );
234                 vlc_mutex_unlock( &p_em->object_lock );
235                 if( !valid_listener )
236                 {
237                     listener_cached++;
238                     continue;
239                 }
240             }            
241         }
242     }
243     vlc_mutex_unlock( &p_em->event_sending_lock );
244
245     free( array_listeners_cached );
246 }
247
248 /*
249  * Public libvlc functions
250  */
251
252 /**************************************************************************
253  *       libvlc_event_type_name (public) :
254  *
255  * Get the char * name of an event type.
256  **************************************************************************/
257 static const char event_type_to_name[][libvlc_num_event_types] =
258 {
259 #define DEF(a) [libvlc_##a]=#a
260     DEFINE_LIBVLC_EVENT_TYPES
261 #undef  DEF
262 };
263
264 static const char unknown_event_name[] = "Unknown Event";
265
266 const char * libvlc_event_type_name( libvlc_event_type_t event_type )
267 {
268     if( event_type >= libvlc_num_event_types )
269         return unknown_event_name;
270     return event_type_to_name[event_type];
271 }
272
273 /**************************************************************************
274  *       event_attach (internal) :
275  *
276  * Add a callback for an event.
277  **************************************************************************/
278 static
279 void event_attach( libvlc_event_manager_t * p_event_manager,
280                          libvlc_event_type_t event_type,
281                          libvlc_callback_t pf_callback,
282                          void *p_user_data,
283                          bool is_asynchronous,
284                          libvlc_exception_t *p_e )
285 {
286     libvlc_event_listeners_group_t * listeners_group;
287     libvlc_event_listener_t * listener;
288     int i;
289     
290     listener = malloc(sizeof(libvlc_event_listener_t));
291     if( !listener )
292     {
293         libvlc_exception_raise( p_e );
294         libvlc_printerr( "Not enough memory" );
295         return;
296     }
297     
298     listener->event_type = event_type;
299     listener->p_user_data = p_user_data;
300     listener->pf_callback = pf_callback;
301     listener->is_asynchronous = is_asynchronous;
302     
303     vlc_mutex_lock( &p_event_manager->object_lock );
304     for( i = 0; i < vlc_array_count(&p_event_manager->listeners_groups); i++ )
305     {
306         listeners_group = vlc_array_item_at_index(&p_event_manager->listeners_groups, i);
307         if( listeners_group->event_type == listener->event_type )
308         {
309             vlc_array_append( &listeners_group->listeners, listener );
310             vlc_mutex_unlock( &p_event_manager->object_lock );
311             return;
312         }
313     }
314     vlc_mutex_unlock( &p_event_manager->object_lock );
315     
316     free(listener);
317     fprintf( stderr, "This object event manager doesn't know about '%s' events",
318              libvlc_event_type_name(event_type) );
319     assert(0);
320 }
321
322 /**************************************************************************
323  *       libvlc_event_attach (public) :
324  *
325  * Add a callback for an event.
326  **************************************************************************/
327 void libvlc_event_attach( libvlc_event_manager_t * p_event_manager,
328                          libvlc_event_type_t event_type,
329                          libvlc_callback_t pf_callback,
330                          void *p_user_data,
331                          libvlc_exception_t *p_e )
332 {
333     event_attach(p_event_manager, event_type, pf_callback, p_user_data, false /* synchronous */, p_e);
334 }
335
336 /**************************************************************************
337  *       libvlc_event_attach (public) :
338  *
339  * Add a callback for an event.
340  **************************************************************************/
341 void libvlc_event_attach_async( libvlc_event_manager_t * p_event_manager,
342                          libvlc_event_type_t event_type,
343                          libvlc_callback_t pf_callback,
344                          void *p_user_data,
345                          libvlc_exception_t *p_e )
346 {
347     event_attach(p_event_manager, event_type, pf_callback, p_user_data, true /* asynchronous */, p_e);
348 }
349
350 /**************************************************************************
351  *       libvlc_event_detach (public) :
352  *
353  * Remove a callback for an event.
354  **************************************************************************/
355 void libvlc_event_detach( libvlc_event_manager_t *p_event_manager,
356                                      libvlc_event_type_t event_type,
357                                      libvlc_callback_t pf_callback,
358                                      void *p_user_data )
359 {
360     libvlc_event_listeners_group_t * listeners_group;
361     libvlc_event_listener_t * listener;
362     int i, j;
363     bool found = false;
364     
365     vlc_mutex_lock( &p_event_manager->event_sending_lock );
366     vlc_mutex_lock( &p_event_manager->object_lock );
367     for( i = 0; i < vlc_array_count(&p_event_manager->listeners_groups); i++)
368     {
369         listeners_group = vlc_array_item_at_index(&p_event_manager->listeners_groups, i);
370         if( listeners_group->event_type == event_type )
371         {
372             for( j = 0; j < vlc_array_count(&listeners_group->listeners); j++)
373             {
374                 listener = vlc_array_item_at_index(&listeners_group->listeners, j);
375                 if( listener->event_type == event_type &&
376                     listener->pf_callback == pf_callback &&
377                     listener->p_user_data == p_user_data )
378                 {
379                     /* that's our listener */
380
381                     /* Mark this group as edited so that libvlc_event_send
382                      * will recheck what listener to call */
383                     listeners_group->b_sublistener_removed = false;
384
385                     free( listener );
386                     vlc_array_remove( &listeners_group->listeners, j );
387                     found = true;
388                     break;
389                 }
390             }
391         }
392     }
393     vlc_mutex_unlock( &p_event_manager->object_lock );
394     vlc_mutex_unlock( &p_event_manager->event_sending_lock );
395
396     /* Now make sure any pending async event won't get fired after that point */
397     libvlc_event_listener_t listener_to_remove;
398     listener_to_remove.event_type  = event_type;
399     listener_to_remove.pf_callback = pf_callback;
400     listener_to_remove.p_user_data = p_user_data;
401     listener_to_remove.is_asynchronous = true;
402
403     libvlc_event_async_ensure_listener_removal(p_event_manager, &listener_to_remove);
404
405     assert(found);
406 }