LGPL
[vlc.git] / lib / event.c
1 /*****************************************************************************
2  * event.c: New libvlc event control API
3  *****************************************************************************
4  * Copyright (C) 2007-2010 VLC authors and VideoLAN
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 it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 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     vlc_mutex_lock( &p_em->event_sending_lock );
161     vlc_mutex_lock( &p_em->object_lock );
162     for( i = 0; i < vlc_array_count(&p_em->listeners_groups); i++)
163     {
164         listeners_group = vlc_array_item_at_index(&p_em->listeners_groups, i);
165         if( listeners_group->event_type == p_event->type )
166         {
167             if( vlc_array_count( &listeners_group->listeners ) <= 0 )
168                 break;
169
170             /* Cache a copy of the listener to avoid locking issues,
171              * and allow that edition of listeners during callbacks will garantee immediate effect. */
172             i_cached_listeners = vlc_array_count(&listeners_group->listeners);
173             array_listeners_cached = malloc(sizeof(libvlc_event_listener_t)*(i_cached_listeners));
174             if( !array_listeners_cached )
175             {
176                 vlc_mutex_unlock( &p_em->object_lock );
177                 vlc_mutex_unlock( &p_em->event_sending_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         vlc_mutex_unlock( &p_em->event_sending_lock );
198         return;
199     }
200
201     /* Track item removed from *this* thread, with a simple flag. Indeed
202      * event_sending_lock is a recursive lock. This has the advantage of
203      * allowing to remove an event listener from within a callback */
204     listeners_group->b_sublistener_removed = false;
205
206     vlc_mutex_unlock( &p_em->object_lock );
207
208     listener_cached = array_listeners_cached;
209     for( i = 0; i < i_cached_listeners; i++ )
210     {
211         if(listener_cached->is_asynchronous)
212         {
213             /* The listener wants not to block the emitter during event callback */
214             libvlc_event_async_dispatch(p_em, listener_cached, p_event);
215         }
216         else
217         {
218             /* The listener wants to block the emitter during event callback */
219
220             listener_cached->pf_callback( p_event, listener_cached->p_user_data );
221             listener_cached++;
222
223             if( listeners_group->b_sublistener_removed )
224             {
225                 /* If a callback was removed, this gets called */
226                 bool valid_listener;
227                 vlc_mutex_lock( &p_em->object_lock );
228                 valid_listener = group_contains_listener( listeners_group, listener_cached );
229                 vlc_mutex_unlock( &p_em->object_lock );
230                 if( !valid_listener )
231                 {
232                     listener_cached++;
233                     continue;
234                 }
235             }
236         }
237     }
238     vlc_mutex_unlock( &p_em->event_sending_lock );
239
240     free( array_listeners_cached );
241 }
242
243 /*
244  * Public libvlc functions
245  */
246
247 #define DEF( a ) { libvlc_##a, #a, },
248
249 typedef struct
250 {
251     int type;
252     const char name[40];
253 } event_name_t;
254
255 static const event_name_t event_list[] = {
256     DEF(MediaMetaChanged)
257     DEF(MediaSubItemAdded)
258     DEF(MediaDurationChanged)
259     DEF(MediaParsedChanged)
260     DEF(MediaFreed)
261     DEF(MediaStateChanged)
262
263     DEF(MediaPlayerMediaChanged)
264     DEF(MediaPlayerNothingSpecial)
265     DEF(MediaPlayerOpening)
266     DEF(MediaPlayerBuffering)
267     DEF(MediaPlayerPlaying)
268     DEF(MediaPlayerPaused)
269     DEF(MediaPlayerStopped)
270     DEF(MediaPlayerForward)
271     DEF(MediaPlayerBackward)
272     DEF(MediaPlayerEndReached)
273     DEF(MediaPlayerEncounteredError)
274     DEF(MediaPlayerTimeChanged)
275     DEF(MediaPlayerPositionChanged)
276     DEF(MediaPlayerSeekableChanged)
277     DEF(MediaPlayerPausableChanged)
278     DEF(MediaPlayerTitleChanged)
279     DEF(MediaPlayerSnapshotTaken)
280     DEF(MediaPlayerLengthChanged)
281
282     DEF(MediaListItemAdded)
283     DEF(MediaListWillAddItem)
284     DEF(MediaListItemDeleted)
285     DEF(MediaListWillDeleteItem)
286
287     DEF(MediaListViewItemAdded)
288     DEF(MediaListViewWillAddItem)
289     DEF(MediaListViewItemDeleted)
290     DEF(MediaListViewWillDeleteItem)
291
292     DEF(MediaListPlayerPlayed)
293     DEF(MediaListPlayerNextItemSet)
294     DEF(MediaListPlayerStopped)
295
296     DEF(MediaDiscovererStarted)
297     DEF(MediaDiscovererEnded)
298
299     DEF(VlmMediaAdded)
300     DEF(VlmMediaRemoved)
301     DEF(VlmMediaChanged)
302     DEF(VlmMediaInstanceStarted)
303     DEF(VlmMediaInstanceStopped)
304     DEF(VlmMediaInstanceStatusInit)
305     DEF(VlmMediaInstanceStatusOpening)
306 };
307 #undef DEF
308
309 static const char unknown_event_name[] = "Unknown Event";
310
311 static int evcmp( const void *a, const void *b )
312 {
313     return (*(const int *)a) - ((event_name_t *)b)->type;
314 }
315
316 const char * libvlc_event_type_name( int event_type )
317 {
318     const event_name_t *p;
319
320     p = bsearch( &event_type, event_list,
321                  sizeof(event_list)/sizeof(event_list[0]), sizeof(*p),
322                  evcmp );
323     return p ? p->name : unknown_event_name;
324 }
325
326 /**************************************************************************
327  *       event_attach (internal) :
328  *
329  * Add a callback for an event.
330  **************************************************************************/
331 static
332 int event_attach( libvlc_event_manager_t * p_event_manager,
333                   libvlc_event_type_t event_type,
334                   libvlc_callback_t pf_callback, void *p_user_data,
335                   bool is_asynchronous )
336 {
337     libvlc_event_listeners_group_t * listeners_group;
338     libvlc_event_listener_t * listener;
339     int i;
340
341     listener = malloc(sizeof(libvlc_event_listener_t));
342     if( unlikely(listener == NULL) )
343         return ENOMEM;
344
345     listener->event_type = event_type;
346     listener->p_user_data = p_user_data;
347     listener->pf_callback = pf_callback;
348     listener->is_asynchronous = is_asynchronous;
349
350     vlc_mutex_lock( &p_event_manager->object_lock );
351     for( i = 0; i < vlc_array_count(&p_event_manager->listeners_groups); i++ )
352     {
353         listeners_group = vlc_array_item_at_index(&p_event_manager->listeners_groups, i);
354         if( listeners_group->event_type == listener->event_type )
355         {
356             vlc_array_append( &listeners_group->listeners, listener );
357             vlc_mutex_unlock( &p_event_manager->object_lock );
358             return 0;
359         }
360     }
361     vlc_mutex_unlock( &p_event_manager->object_lock );
362
363     free(listener);
364     fprintf( stderr, "This object event manager doesn't know about '%s' events",
365              libvlc_event_type_name(event_type) );
366     assert(0);
367     return -1;
368 }
369
370 /**************************************************************************
371  *       libvlc_event_attach (public) :
372  *
373  * Add a callback for an event.
374  **************************************************************************/
375 int libvlc_event_attach( libvlc_event_manager_t * p_event_manager,
376                          libvlc_event_type_t event_type,
377                          libvlc_callback_t pf_callback,
378                          void *p_user_data )
379 {
380     return event_attach(p_event_manager, event_type, pf_callback, p_user_data,
381                         false /* synchronous */);
382 }
383
384 /**************************************************************************
385  *       libvlc_event_attach (public) :
386  *
387  * Add a callback for an event.
388  **************************************************************************/
389 void libvlc_event_attach_async( libvlc_event_manager_t * p_event_manager,
390                          libvlc_event_type_t event_type,
391                          libvlc_callback_t pf_callback,
392                          void *p_user_data )
393 {
394     event_attach(p_event_manager, event_type, pf_callback, p_user_data,
395                  true /* asynchronous */);
396 }
397
398 /**************************************************************************
399  *       libvlc_event_detach (public) :
400  *
401  * Remove a callback for an event.
402  **************************************************************************/
403 void libvlc_event_detach( libvlc_event_manager_t *p_event_manager,
404                                      libvlc_event_type_t event_type,
405                                      libvlc_callback_t pf_callback,
406                                      void *p_user_data )
407 {
408     libvlc_event_listeners_group_t * listeners_group;
409     libvlc_event_listener_t * listener;
410     int i, j;
411     bool found = false;
412
413     vlc_mutex_lock( &p_event_manager->event_sending_lock );
414     vlc_mutex_lock( &p_event_manager->object_lock );
415     for( i = 0; i < vlc_array_count(&p_event_manager->listeners_groups); i++)
416     {
417         listeners_group = vlc_array_item_at_index(&p_event_manager->listeners_groups, i);
418         if( listeners_group->event_type == event_type )
419         {
420             for( j = 0; j < vlc_array_count(&listeners_group->listeners); j++)
421             {
422                 listener = vlc_array_item_at_index(&listeners_group->listeners, j);
423                 if( listener->event_type == event_type &&
424                     listener->pf_callback == pf_callback &&
425                     listener->p_user_data == p_user_data )
426                 {
427                     /* that's our listener */
428
429                     /* Mark this group as edited so that libvlc_event_send
430                      * will recheck what listener to call */
431                     listeners_group->b_sublistener_removed = true;
432
433                     free( listener );
434                     vlc_array_remove( &listeners_group->listeners, j );
435                     found = true;
436                     break;
437                 }
438             }
439         }
440     }
441     vlc_mutex_unlock( &p_event_manager->object_lock );
442     vlc_mutex_unlock( &p_event_manager->event_sending_lock );
443
444     /* Now make sure any pending async event won't get fired after that point */
445     libvlc_event_listener_t listener_to_remove;
446     listener_to_remove.event_type  = event_type;
447     listener_to_remove.pf_callback = pf_callback;
448     listener_to_remove.p_user_data = p_user_data;
449     listener_to_remove.is_asynchronous = true;
450
451     libvlc_event_async_ensure_listener_removal(p_event_manager, &listener_to_remove);
452
453     assert(found);
454 }