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