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