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