dbus : fix a deadlock with p_events array
[vlc.git] / modules / control / dbus.c
1 /*****************************************************************************
2  * dbus.c : D-Bus control interface
3  *****************************************************************************
4  * Copyright © 2006-2008 Rafaël Carré
5  * Copyright © 2007-2008 Mirsal Ennaime
6  * Copyright © 2009 The VideoLAN team
7  * $Id$
8  *
9  * Authors:    Rafaël Carré <funman at videolanorg>
10  *             Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 /*
28  * D-Bus Specification:
29  *      http://dbus.freedesktop.org/doc/dbus-specification.html
30  * D-Bus low-level C API (libdbus)
31  *      http://dbus.freedesktop.org/doc/dbus/api/html/index.html
32  *  extract:
33  *   "If you use this low-level API directly, you're signing up for some pain."
34  *
35  * MPRIS Specification version 1.0
36  *      http://wiki.xmms2.xmms.se/index.php/MPRIS
37  */
38
39 /*****************************************************************************
40  * Preamble
41  *****************************************************************************/
42
43 #ifdef HAVE_CONFIG_H
44 # include "config.h"
45 #endif
46
47 #include <dbus/dbus.h>
48 #include "dbus.h"
49
50 #include <vlc_common.h>
51 #include <vlc_plugin.h>
52 #include <vlc_aout.h>
53 #include <vlc_interface.h>
54 #include <vlc_playlist.h>
55
56 #include <math.h>
57
58 #include <assert.h>
59
60 /*****************************************************************************
61  * Local prototypes.
62  *****************************************************************************/
63
64 static int  Open    ( vlc_object_t * );
65 static void Close   ( vlc_object_t * );
66 static void Run     ( intf_thread_t * );
67
68 static int StateChange( intf_thread_t *, int );
69 static int TrackChange( intf_thread_t * );
70 static int StatusChangeEmit( intf_thread_t *);
71 static int TrackListChangeEmit( intf_thread_t *, int, int );
72
73 static int AllCallback( vlc_object_t*, const char*, vlc_value_t, vlc_value_t, void* );
74
75 static int GetInputMeta ( input_item_t *, DBusMessageIter * );
76 static int MarshalStatus ( intf_thread_t *, DBusMessageIter * );
77 static int UpdateCaps( intf_thread_t* );
78
79 /* GetCaps() capabilities */
80 enum
81 {
82      CAPS_NONE                  = 0,
83      CAPS_CAN_GO_NEXT           = 1 << 0,
84      CAPS_CAN_GO_PREV           = 1 << 1,
85      CAPS_CAN_PAUSE             = 1 << 2,
86      CAPS_CAN_PLAY              = 1 << 3,
87      CAPS_CAN_SEEK              = 1 << 4,
88      CAPS_CAN_PROVIDE_METADATA  = 1 << 5,
89      CAPS_CAN_HAS_TRACKLIST     = 1 << 6
90 };
91
92 // The signal that can be get from the callbacks
93 enum
94 {
95     SIGNAL_ITEM_CURRENT,
96     SIGNAL_INTF_CHANGE,
97     SIGNAL_PLAYLIST_ITEM_APPEND,
98     SIGNAL_PLAYLIST_ITEM_DELETED,
99     SIGNAL_RANDOM,
100     SIGNAL_REPEAT,
101     SIGNAL_LOOP,
102     SIGNAL_STATE
103 };
104
105 struct intf_sys_t
106 {
107     DBusConnection *p_conn;
108     bool            b_meta_read;
109     dbus_int32_t    i_caps;
110     bool            b_dead;
111     vlc_array_t    *p_events;
112     vlc_mutex_t     lock;
113 };
114
115 typedef struct
116 {
117     int signal;
118     int i_node;
119     int i_input_state;
120 } callback_info_t;
121
122
123 /*****************************************************************************
124  * Module descriptor
125  *****************************************************************************/
126
127 vlc_module_begin ()
128     set_shortname( N_("dbus"))
129     set_category( CAT_INTERFACE )
130     set_subcategory( SUBCAT_INTERFACE_CONTROL )
131     set_description( N_("D-Bus control interface") )
132     set_capability( "interface", 0 )
133     set_callbacks( Open, Close )
134 vlc_module_end ()
135
136 /*****************************************************************************
137  * Methods
138  *****************************************************************************/
139
140 /* Player */
141
142 DBUS_METHOD( Quit )
143 { /* exits vlc */
144     REPLY_INIT;
145     libvlc_Quit(((vlc_object_t*)p_this)->p_libvlc);
146     REPLY_SEND;
147 }
148
149 DBUS_METHOD( MprisVersion )
150 { /*implemented version of the mpris spec */
151     REPLY_INIT;
152     OUT_ARGUMENTS;
153     VLC_UNUSED( p_this );
154     dbus_uint16_t i_major = VLC_MPRIS_VERSION_MAJOR;
155     dbus_uint16_t i_minor = VLC_MPRIS_VERSION_MINOR;
156     DBusMessageIter version;
157
158     if( !dbus_message_iter_open_container( &args, DBUS_TYPE_STRUCT, NULL,
159             &version ) )
160         return DBUS_HANDLER_RESULT_NEED_MEMORY;
161
162     if( !dbus_message_iter_append_basic( &version, DBUS_TYPE_UINT16,
163             &i_major ) )
164         return DBUS_HANDLER_RESULT_NEED_MEMORY;
165
166     if( !dbus_message_iter_append_basic( &version, DBUS_TYPE_UINT16,
167             &i_minor ) )
168         return DBUS_HANDLER_RESULT_NEED_MEMORY;
169
170     if( !dbus_message_iter_close_container( &args, &version ) )
171         return DBUS_HANDLER_RESULT_NEED_MEMORY;
172     REPLY_SEND;
173 }
174
175 DBUS_METHOD( PositionGet )
176 { /* returns position in milliseconds */
177     REPLY_INIT;
178     OUT_ARGUMENTS;
179     vlc_value_t position;
180     dbus_int32_t i_pos;
181
182     playlist_t *p_playlist = pl_Hold( ((vlc_object_t*) p_this) );
183     input_thread_t *p_input = playlist_CurrentInput( p_playlist );
184
185     if( !p_input )
186         i_pos = 0;
187     else
188     {
189         var_Get( p_input, "time", &position );
190         i_pos = position.i_time / 1000;
191         vlc_object_release( p_input );
192     }
193     pl_Release( ((vlc_object_t*) p_this) );
194     ADD_INT32( &i_pos );
195     REPLY_SEND;
196 }
197
198 DBUS_METHOD( PositionSet )
199 { /* set position in milliseconds */
200
201     REPLY_INIT;
202     vlc_value_t position;
203     playlist_t* p_playlist = NULL;
204     dbus_int32_t i_pos;
205
206     DBusError error;
207     dbus_error_init( &error );
208
209     dbus_message_get_args( p_from, &error,
210             DBUS_TYPE_INT32, &i_pos,
211             DBUS_TYPE_INVALID );
212
213     if( dbus_error_is_set( &error ) )
214     {
215         msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s",
216                 error.message );
217         dbus_error_free( &error );
218         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
219     }
220     p_playlist = pl_Hold( ((vlc_object_t*) p_this) );
221     input_thread_t *p_input = playlist_CurrentInput( p_playlist );
222
223     if( p_input )
224     {
225         position.i_time = ((mtime_t)i_pos) * 1000;
226         var_Set( p_input, "time", position );
227         vlc_object_release( p_input );
228     }
229     pl_Release( ((vlc_object_t*) p_this) );
230     REPLY_SEND;
231 }
232
233 DBUS_METHOD( VolumeGet )
234 { /* returns volume in percentage */
235     REPLY_INIT;
236     OUT_ARGUMENTS;
237     dbus_int32_t i_dbus_vol;
238     audio_volume_t i_vol;
239     /* 2nd argument of aout_VolumeGet is int32 */
240     aout_VolumeGet( (vlc_object_t*) p_this, &i_vol );
241     double f_vol = 100. * i_vol / AOUT_VOLUME_MAX;
242     i_dbus_vol = round( f_vol );
243     ADD_INT32( &i_dbus_vol );
244     REPLY_SEND;
245 }
246
247 DBUS_METHOD( VolumeSet )
248 { /* set volume in percentage */
249     REPLY_INIT;
250
251     DBusError error;
252     dbus_error_init( &error );
253
254     dbus_int32_t i_dbus_vol;
255     audio_volume_t i_vol;
256
257     dbus_message_get_args( p_from, &error,
258             DBUS_TYPE_INT32, &i_dbus_vol,
259             DBUS_TYPE_INVALID );
260
261     if( dbus_error_is_set( &error ) )
262     {
263         msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s",
264                 error.message );
265         dbus_error_free( &error );
266         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
267     }
268
269     double f_vol = AOUT_VOLUME_MAX * i_dbus_vol / 100.;
270     i_vol = round( f_vol );
271     aout_VolumeSet( (vlc_object_t*) p_this, i_vol );
272
273     REPLY_SEND;
274 }
275
276 DBUS_METHOD( Next )
277 { /* next playlist item */
278     REPLY_INIT;
279     playlist_t *p_playlist = pl_Hold( ((vlc_object_t*) p_this) );
280     playlist_Next( p_playlist );
281     pl_Release( ((vlc_object_t*) p_this) );
282     REPLY_SEND;
283 }
284
285 DBUS_METHOD( Prev )
286 { /* previous playlist item */
287     REPLY_INIT;
288     playlist_t *p_playlist = pl_Hold( ((vlc_object_t*) p_this) );
289     playlist_Prev( p_playlist );
290     pl_Release( ((vlc_object_t*) p_this) );
291     REPLY_SEND;
292 }
293
294 DBUS_METHOD( Stop )
295 { /* stop playing */
296     REPLY_INIT;
297     playlist_t *p_playlist = pl_Hold( ((vlc_object_t*) p_this) );
298     playlist_Stop( p_playlist );
299     pl_Release( ((vlc_object_t*) p_this) );
300     REPLY_SEND;
301 }
302
303 DBUS_METHOD( GetStatus )
304 { /* returns the current status as a struct of 4 ints */
305 /*
306     First   0 = Playing, 1 = Paused, 2 = Stopped.
307     Second  0 = Playing linearly , 1 = Playing randomly.
308     Third   0 = Go to the next element once the current has finished playing , 1 = Repeat the current element
309     Fourth  0 = Stop playing once the last element has been played, 1 = Never give up playing *
310  */
311     REPLY_INIT;
312     OUT_ARGUMENTS;
313
314     MarshalStatus( p_this, &args );
315
316     REPLY_SEND;
317 }
318
319 DBUS_METHOD( Pause )
320 {
321     REPLY_INIT;
322     playlist_t *p_playlist = pl_Hold( (vlc_object_t*) p_this );
323     playlist_Pause( p_playlist );
324     pl_Release( (vlc_object_t*) p_this );
325     REPLY_SEND;
326 }
327
328 DBUS_METHOD( Play )
329 {
330     REPLY_INIT;
331     playlist_t *p_playlist = pl_Hold( (vlc_object_t*) p_this );
332
333     input_thread_t *p_input =  playlist_CurrentInput( p_playlist );
334
335     if( p_input )
336     {
337         double i_pos = 0;
338         input_Control( p_input, INPUT_SET_POSITION, i_pos );
339         vlc_object_release( p_input );
340     }
341     else
342         playlist_Play( p_playlist );
343
344     pl_Release( (vlc_object_t*) p_this );
345     REPLY_SEND;
346 }
347
348 DBUS_METHOD( GetCurrentMetadata )
349 {
350     REPLY_INIT;
351     OUT_ARGUMENTS;
352     playlist_t* p_playlist = pl_Hold( (vlc_object_t*) p_this );
353     PL_LOCK;
354     playlist_item_t* p_item =  playlist_CurrentPlayingItem( p_playlist );
355     if( p_item )
356         GetInputMeta( p_item->p_input, &args );
357     PL_UNLOCK;
358     pl_Release( (vlc_object_t*) p_this );
359     REPLY_SEND;
360 }
361
362 DBUS_METHOD( GetCaps )
363 {
364     REPLY_INIT;
365     OUT_ARGUMENTS;
366
367     ADD_INT32( &((intf_thread_t*)p_this)->p_sys->i_caps );
368
369     REPLY_SEND;
370 }
371
372 /* Media Player information */
373
374 DBUS_METHOD( Identity )
375 {
376     VLC_UNUSED(p_this);
377     REPLY_INIT;
378     OUT_ARGUMENTS;
379     char *psz_identity;
380     if( asprintf( &psz_identity, "%s %s", PACKAGE, VERSION ) != -1 )
381     {
382         ADD_STRING( &psz_identity );
383         free( psz_identity );
384     }
385     else
386         return DBUS_HANDLER_RESULT_NEED_MEMORY;
387
388     REPLY_SEND;
389 }
390
391 /* TrackList */
392
393 DBUS_METHOD( AddTrack )
394 { /* add the string to the playlist, and play it if the boolean is true */
395     REPLY_INIT;
396     OUT_ARGUMENTS;
397
398     DBusError error;
399     dbus_error_init( &error );
400     playlist_t* p_playlist = NULL;
401
402     char *psz_mrl;
403     dbus_bool_t b_play;
404
405     dbus_message_get_args( p_from, &error,
406             DBUS_TYPE_STRING, &psz_mrl,
407             DBUS_TYPE_BOOLEAN, &b_play,
408             DBUS_TYPE_INVALID );
409
410     if( dbus_error_is_set( &error ) )
411     {
412         msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s",
413                 error.message );
414         dbus_error_free( &error );
415         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
416     }
417
418     p_playlist = pl_Hold( (vlc_object_t*) p_this );
419     playlist_Add( p_playlist, psz_mrl, NULL, PLAYLIST_APPEND |
420             ( ( b_play == TRUE ) ? PLAYLIST_GO : 0 ) ,
421             PLAYLIST_END, true, false );
422     pl_Release( (vlc_object_t*) p_this );
423
424     dbus_int32_t i_success = 0;
425     ADD_INT32( &i_success );
426
427     REPLY_SEND;
428 }
429
430 DBUS_METHOD( GetCurrentTrack )
431 {
432     REPLY_INIT;
433     OUT_ARGUMENTS;
434
435     playlist_t *p_playlist = pl_Hold( (vlc_object_t*) p_this );
436     dbus_int32_t i_position = p_playlist->i_current_index;
437     pl_Release( (vlc_object_t*) p_this );
438
439     ADD_INT32( &i_position );
440     REPLY_SEND;
441 }
442
443 DBUS_METHOD( GetMetadata )
444 {
445     REPLY_INIT;
446     OUT_ARGUMENTS;
447     DBusError error;
448     dbus_error_init( &error );
449
450     dbus_int32_t i_position;
451     playlist_t *p_playlist;
452
453     dbus_message_get_args( p_from, &error,
454            DBUS_TYPE_INT32, &i_position,
455            DBUS_TYPE_INVALID );
456
457     if( dbus_error_is_set( &error ) )
458     {
459         msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s",
460                 error.message );
461         dbus_error_free( &error );
462         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
463     }
464
465     p_playlist = pl_Hold( (vlc_object_t*) p_this );
466     PL_LOCK;
467     if( i_position < p_playlist->current.i_size )
468     {
469         GetInputMeta( p_playlist->current.p_elems[i_position]->p_input, &args );
470     }
471
472     PL_UNLOCK;
473     pl_Release( (vlc_object_t*) p_this );
474     REPLY_SEND;
475 }
476
477 DBUS_METHOD( GetLength )
478 {
479     REPLY_INIT;
480     OUT_ARGUMENTS;
481
482     playlist_t *p_playlist = pl_Hold( (vlc_object_t*) p_this );
483     dbus_int32_t i_elements = p_playlist->current.i_size;
484     pl_Release( (vlc_object_t*) p_this );
485
486     ADD_INT32( &i_elements );
487     REPLY_SEND;
488 }
489
490 DBUS_METHOD( DelTrack )
491 {
492     REPLY_INIT;
493
494     DBusError error;
495     dbus_error_init( &error );
496
497     dbus_int32_t i_position;
498     playlist_t *p_playlist;
499
500     dbus_message_get_args( p_from, &error,
501             DBUS_TYPE_INT32, &i_position,
502             DBUS_TYPE_INVALID );
503
504     if( dbus_error_is_set( &error ) )
505     {
506         msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s",
507                 error.message );
508         dbus_error_free( &error );
509         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
510     }
511
512     p_playlist = pl_Hold( (vlc_object_t*) p_this );
513     PL_LOCK;
514     if( i_position < p_playlist->current.i_size )
515     {
516         playlist_DeleteFromInput( p_playlist,
517             p_playlist->current.p_elems[i_position]->p_input,
518             pl_Locked );
519     }
520     PL_UNLOCK;
521
522     pl_Release( (vlc_object_t*) p_this );
523
524     REPLY_SEND;
525 }
526
527 DBUS_METHOD( SetLoop )
528 {
529     REPLY_INIT;
530     OUT_ARGUMENTS;
531
532     DBusError error;
533     dbus_bool_t b_loop;
534     playlist_t* p_playlist;
535
536     dbus_error_init( &error );
537     dbus_message_get_args( p_from, &error,
538             DBUS_TYPE_BOOLEAN, &b_loop,
539             DBUS_TYPE_INVALID );
540
541     if( dbus_error_is_set( &error ) )
542     {
543         msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s",
544                 error.message );
545         dbus_error_free( &error );
546         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
547     }
548
549     p_playlist = pl_Hold( (vlc_object_t*) p_this );
550     var_SetBool( p_playlist, "loop", ( b_loop == TRUE ) );
551     pl_Release( ((vlc_object_t*) p_this) );
552
553     REPLY_SEND;
554 }
555
556 DBUS_METHOD( Repeat )
557 {
558     REPLY_INIT;
559     OUT_ARGUMENTS;
560
561     DBusError error;
562     dbus_bool_t b_repeat;
563     playlist_t* p_playlist;
564
565     dbus_error_init( &error );
566     dbus_message_get_args( p_from, &error,
567             DBUS_TYPE_BOOLEAN, &b_repeat,
568             DBUS_TYPE_INVALID );
569
570     if( dbus_error_is_set( &error ) )
571     {
572         msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s",
573                 error.message );
574         dbus_error_free( &error );
575         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
576     }
577
578     p_playlist = pl_Hold( (vlc_object_t*) p_this );
579     var_SetBool( p_playlist, "repeat", ( b_repeat == TRUE ) );
580     pl_Release( ((vlc_object_t*) p_this) );
581
582     REPLY_SEND;
583 }
584
585 DBUS_METHOD( SetRandom )
586 {
587     REPLY_INIT;
588     OUT_ARGUMENTS;
589
590     DBusError error;
591     dbus_bool_t b_random;
592     playlist_t* p_playlist;
593
594     dbus_error_init( &error );
595     dbus_message_get_args( p_from, &error,
596             DBUS_TYPE_BOOLEAN, &b_random,
597             DBUS_TYPE_INVALID );
598
599     if( dbus_error_is_set( &error ) )
600     {
601         msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s",
602                 error.message );
603         dbus_error_free( &error );
604         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
605     }
606
607     p_playlist = pl_Hold( (vlc_object_t*) p_this );
608     var_SetBool( p_playlist, "random", ( b_random == TRUE ) );
609     pl_Release( ((vlc_object_t*) p_this) );
610
611     REPLY_SEND;
612 }
613 /*****************************************************************************
614  * Introspection method
615  *****************************************************************************/
616
617 DBUS_METHOD( handle_introspect_root )
618 { /* handles introspection of root object */
619     VLC_UNUSED(p_this);
620     REPLY_INIT;
621     OUT_ARGUMENTS;
622     ADD_STRING( &psz_introspection_xml_data_root );
623     REPLY_SEND;
624 }
625
626 DBUS_METHOD( handle_introspect_player )
627 {
628     VLC_UNUSED(p_this);
629     REPLY_INIT;
630     OUT_ARGUMENTS;
631     ADD_STRING( &psz_introspection_xml_data_player );
632     REPLY_SEND;
633 }
634
635 DBUS_METHOD( handle_introspect_tracklist )
636 {
637     VLC_UNUSED(p_this);
638     REPLY_INIT;
639     OUT_ARGUMENTS;
640     ADD_STRING( &psz_introspection_xml_data_tracklist );
641     REPLY_SEND;
642 }
643
644 /*****************************************************************************
645  * handle_*: answer to incoming messages
646  *****************************************************************************/
647
648 #define METHOD_FUNC( method, function ) \
649     else if( dbus_message_is_method_call( p_from, MPRIS_DBUS_INTERFACE, method ) )\
650         return function( p_conn, p_from, p_this )
651
652 DBUS_METHOD( handle_root )
653 {
654
655     if( dbus_message_is_method_call( p_from,
656                 DBUS_INTERFACE_INTROSPECTABLE, "Introspect" ) )
657         return handle_introspect_root( p_conn, p_from, p_this );
658
659     /* here D-Bus method's names are associated to an handler */
660
661     METHOD_FUNC( "Identity",                Identity );
662     METHOD_FUNC( "MprisVersion",            MprisVersion );
663     METHOD_FUNC( "Quit",                    Quit );
664
665     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
666 }
667
668
669 DBUS_METHOD( handle_player )
670 {
671     if( dbus_message_is_method_call( p_from,
672                 DBUS_INTERFACE_INTROSPECTABLE, "Introspect" ) )
673         return handle_introspect_player( p_conn, p_from, p_this );
674
675     /* here D-Bus method's names are associated to an handler */
676
677     METHOD_FUNC( "Prev",                    Prev );
678     METHOD_FUNC( "Next",                    Next );
679     METHOD_FUNC( "Stop",                    Stop );
680     METHOD_FUNC( "Play",                    Play );
681     METHOD_FUNC( "Pause",                   Pause );
682     METHOD_FUNC( "Repeat",                  Repeat );
683     METHOD_FUNC( "VolumeSet",               VolumeSet );
684     METHOD_FUNC( "VolumeGet",               VolumeGet );
685     METHOD_FUNC( "PositionSet",             PositionSet );
686     METHOD_FUNC( "PositionGet",             PositionGet );
687     METHOD_FUNC( "GetStatus",               GetStatus );
688     METHOD_FUNC( "GetMetadata",             GetCurrentMetadata );
689     METHOD_FUNC( "GetCaps",                 GetCaps );
690
691     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
692 }
693
694 DBUS_METHOD( handle_tracklist )
695 {
696     if( dbus_message_is_method_call( p_from,
697                 DBUS_INTERFACE_INTROSPECTABLE, "Introspect" ) )
698     return handle_introspect_tracklist( p_conn, p_from, p_this );
699
700     /* here D-Bus method's names are associated to an handler */
701
702     METHOD_FUNC( "GetMetadata",             GetMetadata );
703     METHOD_FUNC( "GetCurrentTrack",         GetCurrentTrack );
704     METHOD_FUNC( "GetLength",               GetLength );
705     METHOD_FUNC( "AddTrack",                AddTrack );
706     METHOD_FUNC( "DelTrack",                DelTrack );
707     METHOD_FUNC( "SetLoop",                 SetLoop );
708     METHOD_FUNC( "SetRandom",               SetRandom );
709
710     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
711 }
712
713 /*****************************************************************************
714  * Open: initialize interface
715  *****************************************************************************/
716
717 static int Open( vlc_object_t *p_this )
718 { /* initialisation of the connection */
719     intf_thread_t   *p_intf = (intf_thread_t*)p_this;
720     intf_sys_t      *p_sys  = malloc( sizeof( intf_sys_t ) );
721     playlist_t      *p_playlist;
722     DBusConnection  *p_conn;
723     DBusError       error;
724
725     if( !p_sys )
726         return VLC_ENOMEM;
727
728     p_sys->b_meta_read = false;
729     p_sys->i_caps = CAPS_NONE;
730     p_sys->b_dead = false;
731
732     dbus_error_init( &error );
733
734     /* connect to the session bus */
735     p_conn = dbus_bus_get( DBUS_BUS_SESSION, &error );
736     if( !p_conn )
737     {
738         msg_Err( p_this, "Failed to connect to the D-Bus session daemon: %s",
739                 error.message );
740         dbus_error_free( &error );
741         free( p_sys );
742         return VLC_EGENERIC;
743     }
744
745     /* register a well-known name on the bus */
746     dbus_bus_request_name( p_conn, VLC_MPRIS_DBUS_SERVICE, 0, &error );
747     if( dbus_error_is_set( &error ) )
748     {
749         msg_Err( p_this, "Error requesting service " VLC_MPRIS_DBUS_SERVICE
750                  ": %s", error.message );
751         dbus_error_free( &error );
752         free( p_sys );
753         return VLC_EGENERIC;
754     }
755
756     /* we register the objects */
757     dbus_connection_register_object_path( p_conn, MPRIS_DBUS_ROOT_PATH,
758             &vlc_dbus_root_vtable, p_this );
759     dbus_connection_register_object_path( p_conn, MPRIS_DBUS_PLAYER_PATH,
760             &vlc_dbus_player_vtable, p_this );
761     dbus_connection_register_object_path( p_conn, MPRIS_DBUS_TRACKLIST_PATH,
762             &vlc_dbus_tracklist_vtable, p_this );
763
764     dbus_connection_flush( p_conn );
765
766     p_intf->pf_run = Run;
767     p_intf->p_sys = p_sys;
768     p_sys->p_conn = p_conn;
769     p_sys->p_events = vlc_array_new();
770     vlc_mutex_init( &p_sys->lock );
771
772     p_playlist = pl_Hold( p_intf );
773     PL_LOCK;
774     var_AddCallback( p_playlist, "item-current", AllCallback, p_intf );
775     var_AddCallback( p_playlist, "intf-change", AllCallback, p_intf );
776     var_AddCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
777     var_AddCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
778     var_AddCallback( p_playlist, "random", AllCallback, p_intf );
779     var_AddCallback( p_playlist, "repeat", AllCallback, p_intf );
780     var_AddCallback( p_playlist, "loop", AllCallback, p_intf );
781     PL_UNLOCK;
782     pl_Release( p_intf );
783
784     UpdateCaps( p_intf );
785
786     return VLC_SUCCESS;
787 }
788
789 /*****************************************************************************
790  * Close: destroy interface
791  *****************************************************************************/
792
793 static void Close   ( vlc_object_t *p_this )
794 {
795     intf_thread_t   *p_intf     = (intf_thread_t*) p_this;
796     playlist_t      *p_playlist = pl_Hold( p_intf );;
797     input_thread_t  *p_input;
798
799     var_DelCallback( p_playlist, "item-current", AllCallback, p_intf );
800     var_DelCallback( p_playlist, "intf-change", AllCallback, p_intf );
801     var_DelCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
802     var_DelCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
803     var_DelCallback( p_playlist, "random", AllCallback, p_intf );
804     var_DelCallback( p_playlist, "repeat", AllCallback, p_intf );
805     var_DelCallback( p_playlist, "loop", AllCallback, p_intf );
806
807     p_input = playlist_CurrentInput( p_playlist );
808     if ( p_input )
809     {
810         var_DelCallback( p_input, "state", AllCallback, p_intf );
811         vlc_object_release( p_input );
812     }
813
814     pl_Release( p_intf );
815
816     dbus_connection_unref( p_intf->p_sys->p_conn );
817
818     // Free the events array
819     for( int i = 0; i < vlc_array_count( p_intf->p_sys->p_events ); i++ )
820     {
821         callback_info_t* info = vlc_array_item_at_index( p_intf->p_sys->p_events, i );
822         free( info );
823     }
824     vlc_mutex_destroy( &p_intf->p_sys->lock );
825     vlc_array_destroy( p_intf->p_sys->p_events );
826     free( p_intf->p_sys );
827 }
828
829 /*****************************************************************************
830  * Run: main loop
831  *****************************************************************************/
832
833 static void Run          ( intf_thread_t *p_intf )
834 {
835     for( ;; )
836     {
837         msleep( INTF_IDLE_SLEEP );
838         int canc = vlc_savecancel();
839         dbus_connection_read_write_dispatch( p_intf->p_sys->p_conn, 0 );
840
841         /* Get the list of events to process
842          *
843          * We can't keep the lock on p_intf->p_sys->p_events, else we risk a
844          * deadlock:
845          * The signal functions could lock mutex X while p_events is locked;
846          * While some other function in vlc (playlist) might lock mutex X
847          * and then set a variable which would call AllCallback(), which itself
848          * needs to lock p_events to add a new event.
849          */
850         vlc_mutex_lock( &p_intf->p_sys->lock );
851         int i_events = vlc_array_count( p_intf->p_sys->p_events );
852         callback_info_t* info[i_events];
853         for( int i = i_events - 1; i >= 0; i-- )
854         {
855             info[i] = vlc_array_item_at_index( p_intf->p_sys->p_events, i );
856             vlc_array_remove( p_intf->p_sys->p_events, i );
857         }
858         vlc_mutex_unlock( &p_intf->p_sys->lock );
859
860         for( int i = 0; i < i_events; i++ )
861         {
862             switch( info[i]->signal )
863             {
864             case SIGNAL_ITEM_CURRENT:
865                 TrackChange( p_intf );
866                 break;
867             case SIGNAL_INTF_CHANGE:
868             case SIGNAL_PLAYLIST_ITEM_APPEND:
869             case SIGNAL_PLAYLIST_ITEM_DELETED:
870                 TrackListChangeEmit( p_intf, info[i]->signal, info[i]->i_node );
871                 break;
872             case SIGNAL_RANDOM:
873             case SIGNAL_REPEAT:
874             case SIGNAL_LOOP:
875                 StatusChangeEmit( p_intf );
876                 break;
877             case SIGNAL_STATE:
878                 StateChange( p_intf, info[i]->i_input_state );
879                 break;
880             default:
881                 assert(0);
882             }
883             free( info[i] );
884         }
885         vlc_restorecancel( canc );
886     }
887 }
888
889
890 // Get all the callbacks
891 static int AllCallback( vlc_object_t *p_this, const char *psz_var,
892                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
893 {
894     (void)p_this;
895     (void)oldval;
896     intf_thread_t *p_intf = (intf_thread_t*)p_data;
897
898     callback_info_t *info = malloc( sizeof( callback_info_t ) );
899     if( !info )
900         return VLC_ENOMEM;
901
902     // Wich event is it ?
903     if( !strcmp( "item-current", psz_var ) )
904         info->signal = SIGNAL_ITEM_CURRENT;
905     else if( !strcmp( "intf-change", psz_var ) )
906         info->signal = SIGNAL_INTF_CHANGE;
907     else if( !strcmp( "playlist-item-append", psz_var ) )
908     {
909         info->signal = SIGNAL_PLAYLIST_ITEM_APPEND;
910         info->i_node = ((playlist_add_t*)newval.p_address)->i_node;
911     }
912     else if( !strcmp( "playlist-item-deleted", psz_var ) )
913         info->signal = SIGNAL_PLAYLIST_ITEM_DELETED;
914     else if( !strcmp( "random", psz_var ) )
915         info->signal = SIGNAL_RANDOM;
916     else if( !strcmp( "repeat", psz_var ) )
917         info->signal = SIGNAL_REPEAT;
918     else if( !strcmp( "loop", psz_var ) )
919         info->signal = SIGNAL_LOOP;
920     else if( !strcmp( "state", psz_var ) )
921     {
922         info->signal = SIGNAL_STATE;
923         info->i_input_state = newval.i_int;
924     }
925     else
926         assert(0);
927
928     // Append the event
929     vlc_mutex_lock( &p_intf->p_sys->lock );
930     vlc_array_append( p_intf->p_sys->p_events, info );
931     vlc_mutex_unlock( &p_intf->p_sys->lock );
932     return VLC_SUCCESS;
933 }
934
935 /******************************************************************************
936  * CapsChange: player capabilities change signal
937  *****************************************************************************/
938 DBUS_SIGNAL( CapsChangeSignal )
939 {
940     SIGNAL_INIT( "CapsChange" );
941     OUT_ARGUMENTS;
942
943     ADD_INT32( &((intf_thread_t*)p_data)->p_sys->i_caps );
944     SIGNAL_SEND;
945 }
946
947 /******************************************************************************
948  * TrackListChange: tracklist order / length change signal
949  *****************************************************************************/
950 DBUS_SIGNAL( TrackListChangeSignal )
951 { /* emit the new tracklist lengh */
952     SIGNAL_INIT("TrackListChange");
953     OUT_ARGUMENTS;
954
955     playlist_t *p_playlist = pl_Hold( (vlc_object_t*) p_data );
956     dbus_int32_t i_elements = p_playlist->current.i_size;
957     pl_Release( (vlc_object_t*) p_data );
958
959     ADD_INT32( &i_elements );
960     SIGNAL_SEND;
961 }
962
963 /*****************************************************************************
964  * TrackListChangeEmit: Emits the TrackListChange signal
965  *****************************************************************************/
966 /* FIXME: It is not called on tracklist reordering */
967 static int TrackListChangeEmit( intf_thread_t *p_intf, int signal, int i_node )
968 {
969     // "playlist-item-append"
970     if( signal == SIGNAL_PLAYLIST_ITEM_APPEND )
971     {
972         /* don't signal when items are added/removed in p_category */
973         playlist_t *p_playlist = pl_Hold( p_intf );
974         PL_LOCK;
975         playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_node );
976         assert( p_item );
977         while( p_item->p_parent )
978             p_item = p_item->p_parent;
979         if( p_item == p_playlist->p_root_category )
980         {
981             PL_UNLOCK;
982             pl_Release( p_intf );
983             return VLC_SUCCESS;
984         }
985         PL_UNLOCK;
986         pl_Release( p_intf );
987     }
988
989     if( p_intf->p_sys->b_dead )
990         return VLC_SUCCESS;
991
992     UpdateCaps( p_intf );
993     TrackListChangeSignal( p_intf->p_sys->p_conn, p_intf );
994     return VLC_SUCCESS;
995 }
996 /*****************************************************************************
997  * TrackChange: Playlist item change callback
998  *****************************************************************************/
999
1000 DBUS_SIGNAL( TrackChangeSignal )
1001 { /* emit the metadata of the new item */
1002     SIGNAL_INIT( "TrackChange" );
1003     OUT_ARGUMENTS;
1004
1005     input_item_t *p_item = (input_item_t*) p_data;
1006     GetInputMeta ( p_item, &args );
1007
1008     SIGNAL_SEND;
1009 }
1010
1011 /*****************************************************************************
1012  * StatusChange: Player status change signal
1013  *****************************************************************************/
1014
1015 DBUS_SIGNAL( StatusChangeSignal )
1016 { /* send the updated status info on the bus */
1017     SIGNAL_INIT( "StatusChange" );
1018     OUT_ARGUMENTS;
1019
1020     /* we're called from a callback of input_thread_t, so it can not be
1021      * destroyed before we return */
1022     MarshalStatus( (intf_thread_t*) p_data, &args );
1023
1024     SIGNAL_SEND;
1025 }
1026
1027 /*****************************************************************************
1028  * StateChange: callback on input "state"
1029  *****************************************************************************/
1030 //static int StateChange( vlc_object_t *p_this, const char* psz_var,
1031 //            vlc_value_t oldval, vlc_value_t newval, void *p_data )
1032 static int StateChange( intf_thread_t *p_intf, int i_input_state )
1033 {
1034     intf_sys_t          *p_sys      = p_intf->p_sys;
1035     playlist_t          *p_playlist;
1036     input_thread_t      *p_input;
1037     input_item_t        *p_item;
1038
1039     if( p_intf->p_sys->b_dead )
1040         return VLC_SUCCESS;
1041
1042     UpdateCaps( p_intf );
1043
1044     if( !p_sys->b_meta_read && i_input_state == PLAYING_S )
1045     {
1046         p_playlist = pl_Hold( p_intf );
1047         p_input = playlist_CurrentInput( p_playlist );
1048         if( p_input )
1049         {
1050             p_item = input_GetItem( p_input );
1051             if( p_item )
1052             {
1053                 p_sys->b_meta_read = true;
1054                 TrackChangeSignal( p_sys->p_conn, p_item );
1055             }
1056             vlc_object_release( p_input );
1057         }
1058         pl_Release( p_intf );
1059     }
1060
1061     if( i_input_state == PLAYING_S || i_input_state == PAUSE_S ||
1062         i_input_state == END_S )
1063     {
1064         StatusChangeSignal( p_sys->p_conn, p_intf );
1065     }
1066
1067     return VLC_SUCCESS;
1068 }
1069
1070 /*****************************************************************************
1071  * StatusChangeEmit: Emits the StatusChange signal
1072  *****************************************************************************/
1073 static int StatusChangeEmit( intf_thread_t * p_intf )
1074 {
1075     if( p_intf->p_sys->b_dead )
1076         return VLC_SUCCESS;
1077
1078     UpdateCaps( p_intf );
1079     StatusChangeSignal( p_intf->p_sys->p_conn, p_intf );
1080     return VLC_SUCCESS;
1081 }
1082
1083 /*****************************************************************************
1084  * TrackChange: callback on playlist "item-current"
1085  *****************************************************************************/
1086 static int TrackChange( intf_thread_t *p_intf )
1087 {
1088     intf_sys_t          *p_sys      = p_intf->p_sys;
1089     playlist_t          *p_playlist;
1090     input_thread_t      *p_input    = NULL;
1091     input_item_t        *p_item     = NULL;
1092
1093     if( p_intf->p_sys->b_dead )
1094         return VLC_SUCCESS;
1095
1096     p_sys->b_meta_read = false;
1097
1098     p_playlist = pl_Hold( p_intf );
1099     p_input = playlist_CurrentInput( p_playlist );
1100     if( !p_input )
1101     {
1102         pl_Release( p_intf );
1103         return VLC_SUCCESS;
1104     }
1105
1106     pl_Release( p_intf );
1107
1108     p_item = input_GetItem( p_input );
1109     if( !p_item )
1110     {
1111         vlc_object_release( p_input );
1112         return VLC_EGENERIC;
1113     }
1114
1115     if( input_item_IsPreparsed( p_item ) )
1116     {
1117         p_sys->b_meta_read = true;
1118         TrackChangeSignal( p_sys->p_conn, p_item );
1119     }
1120
1121     var_AddCallback( p_input, "state", AllCallback, p_intf );
1122
1123     vlc_object_release( p_input );
1124     return VLC_SUCCESS;
1125 }
1126
1127 /*****************************************************************************
1128  * UpdateCaps: update p_sys->i_caps
1129  * This function have to be called with the playlist unlocked
1130  ****************************************************************************/
1131 static int UpdateCaps( intf_thread_t* p_intf )
1132 {
1133     intf_sys_t* p_sys = p_intf->p_sys;
1134     dbus_int32_t i_caps = CAPS_CAN_HAS_TRACKLIST;
1135     playlist_t* p_playlist = pl_Hold( p_intf );
1136
1137     PL_LOCK;
1138     if( p_playlist->current.i_size > 0 )
1139         i_caps |= CAPS_CAN_PLAY | CAPS_CAN_GO_PREV | CAPS_CAN_GO_NEXT;
1140     PL_UNLOCK;
1141
1142     input_thread_t* p_input = playlist_CurrentInput( p_playlist );
1143     if( p_input )
1144     {
1145         /* XXX: if UpdateCaps() is called too early, these are
1146          * unconditionnaly true */
1147         if( var_GetBool( p_input, "can-pause" ) )
1148             i_caps |= CAPS_CAN_PAUSE;
1149         if( var_GetBool( p_input, "can-seek" ) )
1150             i_caps |= CAPS_CAN_SEEK;
1151         vlc_object_release( p_input );
1152     }
1153
1154     pl_Release( p_intf );
1155
1156     if( p_sys->b_meta_read )
1157         i_caps |= CAPS_CAN_PROVIDE_METADATA;
1158
1159     if( i_caps != p_intf->p_sys->i_caps )
1160     {
1161         p_sys->i_caps = i_caps;
1162         CapsChangeSignal( p_intf->p_sys->p_conn, (vlc_object_t*)p_intf );
1163     }
1164
1165     return VLC_SUCCESS;
1166 }
1167
1168 /*****************************************************************************
1169  * GetInputMeta: Fill a DBusMessage with the given input item metadata
1170  *****************************************************************************/
1171
1172 #define ADD_META( entry, type, data ) \
1173     if( data ) { \
1174         dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1175                 NULL, &dict_entry ); \
1176         dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1177                 &ppsz_meta_items[entry] ); \
1178         dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1179                 type##_AS_STRING, &variant ); \
1180         dbus_message_iter_append_basic( &variant, \
1181                 type, \
1182                 & data ); \
1183         dbus_message_iter_close_container( &dict_entry, &variant ); \
1184         dbus_message_iter_close_container( &dict, &dict_entry ); }
1185
1186 #define ADD_VLC_META_STRING( entry, item ) \
1187     { \
1188         char * psz = input_item_Get##item( p_input );\
1189         ADD_META( entry, DBUS_TYPE_STRING, \
1190                   psz ); \
1191         free( psz ); \
1192     }
1193
1194 static int GetInputMeta( input_item_t* p_input,
1195                         DBusMessageIter *args )
1196 {
1197     DBusMessageIter dict, dict_entry, variant;
1198     /* We need the track length to be expressed in milli-seconds
1199      * instead of µ-seconds */
1200     dbus_int64_t i_length = ( input_item_GetDuration( p_input ) / 1000 );
1201
1202     const char* ppsz_meta_items[] =
1203     {
1204     "title", "artist", "genre", "copyright", "album", "tracknum",
1205     "description", "rating", "date", "setting", "url", "language",
1206     "nowplaying", "publisher", "encodedby", "arturl", "trackid",
1207     "status", "location", "length", "video-codec", "audio-codec",
1208     "video-bitrate", "audio-bitrate", "audio-samplerate"
1209     };
1210
1211     dbus_message_iter_open_container( args, DBUS_TYPE_ARRAY, "{sv}", &dict );
1212
1213     ADD_VLC_META_STRING( 0,  Title );
1214     ADD_VLC_META_STRING( 1,  Artist );
1215     ADD_VLC_META_STRING( 2,  Genre );
1216     ADD_VLC_META_STRING( 3,  Copyright );
1217     ADD_VLC_META_STRING( 4,  Album );
1218     ADD_VLC_META_STRING( 5,  TrackNum );
1219     ADD_VLC_META_STRING( 6,  Description );
1220     ADD_VLC_META_STRING( 7,  Rating );
1221     ADD_VLC_META_STRING( 8,  Date );
1222     ADD_VLC_META_STRING( 9,  Setting );
1223     ADD_VLC_META_STRING( 10, URL );
1224     ADD_VLC_META_STRING( 11, Language );
1225     ADD_VLC_META_STRING( 12, NowPlaying );
1226     ADD_VLC_META_STRING( 13, Publisher );
1227     ADD_VLC_META_STRING( 14, EncodedBy );
1228     ADD_VLC_META_STRING( 15, ArtURL );
1229     ADD_VLC_META_STRING( 16, TrackID );
1230
1231     vlc_mutex_lock( &p_input->lock );
1232     if( p_input->p_meta )
1233         ADD_META( 17, DBUS_TYPE_INT32, p_input->p_meta->i_status );
1234     vlc_mutex_unlock( &p_input->lock );
1235
1236     ADD_VLC_META_STRING( 18, URI );
1237     ADD_META( 19, DBUS_TYPE_INT64, i_length );
1238
1239     dbus_message_iter_close_container( args, &dict );
1240     return VLC_SUCCESS;
1241 }
1242
1243 #undef ADD_META
1244 #undef ADD_VLC_META_STRING
1245
1246 /*****************************************************************************
1247  * MarshalStatus: Fill a DBusMessage with the current player status
1248  *****************************************************************************/
1249
1250 static int MarshalStatus( intf_thread_t* p_intf, DBusMessageIter* args )
1251 { /* This is NOT the right way to do that, it would be better to sore
1252      the status information in p_sys and update it on change, thus
1253      avoiding a long lock */
1254
1255     DBusMessageIter status;
1256     dbus_int32_t i_state, i_random, i_repeat, i_loop;
1257     vlc_value_t val;
1258     playlist_t* p_playlist = NULL;
1259     input_thread_t* p_input = NULL;
1260
1261     p_playlist = pl_Hold( p_intf );
1262
1263     i_state = 2;
1264
1265     p_input = playlist_CurrentInput( p_playlist );
1266     if( p_input )
1267     {
1268         var_Get( p_input, "state", &val );
1269         if( val.i_int >= END_S )
1270             i_state = 2;
1271         else if( val.i_int == PAUSE_S )
1272             i_state = 1;
1273         else if( val.i_int <= PLAYING_S )
1274             i_state = 0;
1275         vlc_object_release( p_input );
1276     }
1277
1278     i_random = var_CreateGetBool( p_playlist, "random" );
1279
1280     i_repeat = var_CreateGetBool( p_playlist, "repeat" );
1281
1282     i_loop = var_CreateGetBool( p_playlist, "loop" );
1283
1284     pl_Release( p_intf );
1285
1286     dbus_message_iter_open_container( args, DBUS_TYPE_STRUCT, NULL, &status );
1287     dbus_message_iter_append_basic( &status, DBUS_TYPE_INT32, &i_state );
1288     dbus_message_iter_append_basic( &status, DBUS_TYPE_INT32, &i_random );
1289     dbus_message_iter_append_basic( &status, DBUS_TYPE_INT32, &i_repeat );
1290     dbus_message_iter_append_basic( &status, DBUS_TYPE_INT32, &i_loop );
1291     dbus_message_iter_close_container( args, &status );
1292
1293     return VLC_SUCCESS;
1294 }