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