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