]> git.sesse.net Git - vlc/blob - modules/control/dbus.c
dbus controler: cosmetics (split the meta: the ones requiered by the
[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_Hold( p_intf );
749     p_sys->p_playlist = p_playlist;
750
751     PL_LOCK;
752     var_AddCallback( p_playlist, "item-current", AllCallback, p_intf );
753     var_AddCallback( p_playlist, "intf-change", AllCallback, p_intf );
754     var_AddCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
755     var_AddCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
756     var_AddCallback( p_playlist, "random", AllCallback, p_intf );
757     var_AddCallback( p_playlist, "repeat", AllCallback, p_intf );
758     var_AddCallback( p_playlist, "loop", AllCallback, p_intf );
759     PL_UNLOCK;
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     input_thread_t  *p_input;
776
777     var_DelCallback( p_playlist, "item-current", AllCallback, p_intf );
778     var_DelCallback( p_playlist, "intf-change", AllCallback, p_intf );
779     var_DelCallback( p_playlist, "playlist-item-append", AllCallback, p_intf );
780     var_DelCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf );
781     var_DelCallback( p_playlist, "random", AllCallback, p_intf );
782     var_DelCallback( p_playlist, "repeat", AllCallback, p_intf );
783     var_DelCallback( p_playlist, "loop", AllCallback, p_intf );
784
785     p_input = playlist_CurrentInput( p_playlist );
786     if ( p_input )
787     {
788         var_DelCallback( p_input, "state", AllCallback, p_intf );
789         vlc_object_release( p_input );
790     }
791     pl_Release( p_intf );
792
793     dbus_connection_unref( p_sys->p_conn );
794
795     // Free the events array
796     for( int i = 0; i < vlc_array_count( p_sys->p_events ); i++ )
797     {
798         callback_info_t* info = vlc_array_item_at_index( p_sys->p_events, i );
799         free( info );
800     }
801     vlc_mutex_destroy( &p_sys->lock );
802     vlc_array_destroy( p_sys->p_events );
803     free( p_sys );
804 }
805
806 /*****************************************************************************
807  * Run: main loop
808  *****************************************************************************/
809
810 static void Run          ( intf_thread_t *p_intf )
811 {
812     for( ;; )
813     {
814         if( dbus_connection_get_dispatch_status(p_intf->p_sys->p_conn)
815                                              == DBUS_DISPATCH_COMPLETE )
816             msleep( INTF_IDLE_SLEEP );
817         int canc = vlc_savecancel();
818         dbus_connection_read_write_dispatch( p_intf->p_sys->p_conn, 0 );
819
820         /* Get the list of events to process
821          *
822          * We can't keep the lock on p_intf->p_sys->p_events, else we risk a
823          * deadlock:
824          * The signal functions could lock mutex X while p_events is locked;
825          * While some other function in vlc (playlist) might lock mutex X
826          * and then set a variable which would call AllCallback(), which itself
827          * needs to lock p_events to add a new event.
828          */
829         vlc_mutex_lock( &p_intf->p_sys->lock );
830         int i_events = vlc_array_count( p_intf->p_sys->p_events );
831         callback_info_t* info[i_events];
832         for( int i = i_events - 1; i >= 0; i-- )
833         {
834             info[i] = vlc_array_item_at_index( p_intf->p_sys->p_events, i );
835             vlc_array_remove( p_intf->p_sys->p_events, i );
836         }
837         vlc_mutex_unlock( &p_intf->p_sys->lock );
838
839         for( int i = 0; i < i_events; i++ )
840         {
841             switch( info[i]->signal )
842             {
843             case SIGNAL_ITEM_CURRENT:
844                 TrackChange( p_intf );
845                 break;
846             case SIGNAL_INTF_CHANGE:
847             case SIGNAL_PLAYLIST_ITEM_APPEND:
848             case SIGNAL_PLAYLIST_ITEM_DELETED:
849                 TrackListChangeEmit( p_intf, info[i]->signal, info[i]->i_node );
850                 break;
851             case SIGNAL_RANDOM:
852             case SIGNAL_REPEAT:
853             case SIGNAL_LOOP:
854                 StatusChangeEmit( p_intf );
855                 break;
856             case SIGNAL_STATE:
857                 StateChange( p_intf, info[i]->i_input_state );
858                 break;
859             default:
860                 assert(0);
861             }
862             free( info[i] );
863         }
864         vlc_restorecancel( canc );
865     }
866 }
867
868
869 // Get all the callbacks
870 static int AllCallback( vlc_object_t *p_this, const char *psz_var,
871                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
872 {
873     (void)p_this;
874     (void)oldval;
875     intf_thread_t *p_intf = (intf_thread_t*)p_data;
876
877     callback_info_t *info = malloc( sizeof( callback_info_t ) );
878     if( !info )
879         return VLC_ENOMEM;
880
881     // Wich event is it ?
882     if( !strcmp( "item-current", psz_var ) )
883         info->signal = SIGNAL_ITEM_CURRENT;
884     else if( !strcmp( "intf-change", psz_var ) )
885         info->signal = SIGNAL_INTF_CHANGE;
886     else if( !strcmp( "playlist-item-append", psz_var ) )
887     {
888         info->signal = SIGNAL_PLAYLIST_ITEM_APPEND;
889         info->i_node = ((playlist_add_t*)newval.p_address)->i_node;
890     }
891     else if( !strcmp( "playlist-item-deleted", psz_var ) )
892         info->signal = SIGNAL_PLAYLIST_ITEM_DELETED;
893     else if( !strcmp( "random", psz_var ) )
894         info->signal = SIGNAL_RANDOM;
895     else if( !strcmp( "repeat", psz_var ) )
896         info->signal = SIGNAL_REPEAT;
897     else if( !strcmp( "loop", psz_var ) )
898         info->signal = SIGNAL_LOOP;
899     else if( !strcmp( "state", psz_var ) )
900     {
901         info->signal = SIGNAL_STATE;
902         info->i_input_state = newval.i_int;
903     }
904     else
905         assert(0);
906
907     // Append the event
908     vlc_mutex_lock( &p_intf->p_sys->lock );
909     vlc_array_append( p_intf->p_sys->p_events, info );
910     vlc_mutex_unlock( &p_intf->p_sys->lock );
911     return VLC_SUCCESS;
912 }
913
914 /******************************************************************************
915  * CapsChange: player capabilities change signal
916  *****************************************************************************/
917 DBUS_SIGNAL( CapsChangeSignal )
918 {
919     SIGNAL_INIT( MPRIS_DBUS_PLAYER_PATH, "CapsChange" );
920     OUT_ARGUMENTS;
921
922     ADD_INT32( &((intf_thread_t*)p_data)->p_sys->i_caps );
923     SIGNAL_SEND;
924 }
925
926 /******************************************************************************
927  * TrackListChange: tracklist order / length change signal
928  *****************************************************************************/
929 DBUS_SIGNAL( TrackListChangeSignal )
930 { /* emit the new tracklist lengh */
931     SIGNAL_INIT( MPRIS_DBUS_TRACKLIST_PATH, "TrackListChange");
932     OUT_ARGUMENTS;
933
934     /* XXX: locking */
935     dbus_int32_t i_elements = ((intf_thread_t*)p_data)->p_sys->p_playlist->current.i_size;
936
937     ADD_INT32( &i_elements );
938     SIGNAL_SEND;
939 }
940
941 /*****************************************************************************
942  * TrackListChangeEmit: Emits the TrackListChange signal
943  *****************************************************************************/
944 /* FIXME: It is not called on tracklist reordering */
945 static int TrackListChangeEmit( intf_thread_t *p_intf, int signal, int i_node )
946 {
947     // "playlist-item-append"
948     if( signal == SIGNAL_PLAYLIST_ITEM_APPEND )
949     {
950         /* don't signal when items are added/removed in p_category */
951         playlist_t *p_playlist = p_intf->p_sys->p_playlist;
952         PL_LOCK;
953         playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_node );
954         assert( p_item );
955         while( p_item->p_parent )
956             p_item = p_item->p_parent;
957         if( p_item == p_playlist->p_root_category )
958         {
959             PL_UNLOCK;
960             return VLC_SUCCESS;
961         }
962         PL_UNLOCK;
963     }
964
965     if( p_intf->p_sys->b_dead )
966         return VLC_SUCCESS;
967
968     UpdateCaps( p_intf );
969     TrackListChangeSignal( p_intf->p_sys->p_conn, p_intf );
970     return VLC_SUCCESS;
971 }
972 /*****************************************************************************
973  * TrackChange: Playlist item change callback
974  *****************************************************************************/
975
976 DBUS_SIGNAL( TrackChangeSignal )
977 { /* emit the metadata of the new item */
978     SIGNAL_INIT( MPRIS_DBUS_PLAYER_PATH, "TrackChange" );
979     OUT_ARGUMENTS;
980
981     input_item_t *p_item = (input_item_t*) p_data;
982     GetInputMeta ( p_item, &args );
983
984     SIGNAL_SEND;
985 }
986
987 /*****************************************************************************
988  * StatusChange: Player status change signal
989  *****************************************************************************/
990
991 DBUS_SIGNAL( StatusChangeSignal )
992 { /* send the updated status info on the bus */
993     SIGNAL_INIT( MPRIS_DBUS_PLAYER_PATH, "StatusChange" );
994     OUT_ARGUMENTS;
995
996     /* we're called from a callback of input_thread_t, so it can not be
997      * destroyed before we return */
998     MarshalStatus( (intf_thread_t*) p_data, &args );
999
1000     SIGNAL_SEND;
1001 }
1002
1003 /*****************************************************************************
1004  * StateChange: callback on input "state"
1005  *****************************************************************************/
1006 //static int StateChange( vlc_object_t *p_this, const char* psz_var,
1007 //            vlc_value_t oldval, vlc_value_t newval, void *p_data )
1008 static int StateChange( intf_thread_t *p_intf, int i_input_state )
1009 {
1010     intf_sys_t          *p_sys      = p_intf->p_sys;
1011     playlist_t          *p_playlist = p_sys->p_playlist;
1012     input_thread_t      *p_input;
1013     input_item_t        *p_item;
1014
1015     if( p_intf->p_sys->b_dead )
1016         return VLC_SUCCESS;
1017
1018     UpdateCaps( p_intf );
1019
1020     if( !p_sys->b_meta_read && i_input_state == PLAYING_S )
1021     {
1022         p_input = playlist_CurrentInput( p_playlist );
1023         if( p_input )
1024         {
1025             p_item = input_GetItem( p_input );
1026             if( p_item )
1027             {
1028                 p_sys->b_meta_read = true;
1029                 TrackChangeSignal( p_sys->p_conn, p_item );
1030             }
1031             vlc_object_release( p_input );
1032         }
1033     }
1034
1035     if( i_input_state == PLAYING_S || i_input_state == PAUSE_S ||
1036         i_input_state == END_S )
1037     {
1038         StatusChangeSignal( p_sys->p_conn, p_intf );
1039     }
1040
1041     return VLC_SUCCESS;
1042 }
1043
1044 /*****************************************************************************
1045  * StatusChangeEmit: Emits the StatusChange signal
1046  *****************************************************************************/
1047 static int StatusChangeEmit( intf_thread_t * p_intf )
1048 {
1049     if( p_intf->p_sys->b_dead )
1050         return VLC_SUCCESS;
1051
1052     UpdateCaps( p_intf );
1053     StatusChangeSignal( p_intf->p_sys->p_conn, p_intf );
1054     return VLC_SUCCESS;
1055 }
1056
1057 /*****************************************************************************
1058  * TrackChange: callback on playlist "item-current"
1059  *****************************************************************************/
1060 static int TrackChange( intf_thread_t *p_intf )
1061 {
1062     intf_sys_t          *p_sys      = p_intf->p_sys;
1063     playlist_t          *p_playlist = p_sys->p_playlist;
1064     input_thread_t      *p_input    = NULL;
1065     input_item_t        *p_item     = NULL;
1066
1067     if( p_intf->p_sys->b_dead )
1068         return VLC_SUCCESS;
1069
1070     p_sys->b_meta_read = false;
1071
1072     p_input = playlist_CurrentInput( p_playlist );
1073     if( !p_input )
1074     {
1075         return VLC_SUCCESS;
1076     }
1077
1078     p_item = input_GetItem( p_input );
1079     if( !p_item )
1080     {
1081         vlc_object_release( p_input );
1082         return VLC_EGENERIC;
1083     }
1084
1085     if( input_item_IsPreparsed( p_item ) )
1086     {
1087         p_sys->b_meta_read = true;
1088         TrackChangeSignal( p_sys->p_conn, p_item );
1089     }
1090
1091     var_AddCallback( p_input, "state", AllCallback, p_intf );
1092
1093     vlc_object_release( p_input );
1094     return VLC_SUCCESS;
1095 }
1096
1097 /*****************************************************************************
1098  * UpdateCaps: update p_sys->i_caps
1099  * This function have to be called with the playlist unlocked
1100  ****************************************************************************/
1101 static int UpdateCaps( intf_thread_t* p_intf )
1102 {
1103     intf_sys_t* p_sys = p_intf->p_sys;
1104     dbus_int32_t i_caps = CAPS_CAN_HAS_TRACKLIST;
1105     playlist_t* p_playlist = p_sys->p_playlist;
1106
1107     PL_LOCK;
1108     if( p_playlist->current.i_size > 0 )
1109         i_caps |= CAPS_CAN_PLAY | CAPS_CAN_GO_PREV | CAPS_CAN_GO_NEXT;
1110     PL_UNLOCK;
1111
1112     input_thread_t* p_input = playlist_CurrentInput( p_playlist );
1113     if( p_input )
1114     {
1115         /* XXX: if UpdateCaps() is called too early, these are
1116          * unconditionnaly true */
1117         if( var_GetBool( p_input, "can-pause" ) )
1118             i_caps |= CAPS_CAN_PAUSE;
1119         if( var_GetBool( p_input, "can-seek" ) )
1120             i_caps |= CAPS_CAN_SEEK;
1121         vlc_object_release( p_input );
1122     }
1123
1124     if( p_sys->b_meta_read )
1125         i_caps |= CAPS_CAN_PROVIDE_METADATA;
1126
1127     if( i_caps != p_intf->p_sys->i_caps )
1128     {
1129         p_sys->i_caps = i_caps;
1130         CapsChangeSignal( p_intf->p_sys->p_conn, (vlc_object_t*)p_intf );
1131     }
1132
1133     return VLC_SUCCESS;
1134 }
1135
1136 /*****************************************************************************
1137  * GetInputMeta: Fill a DBusMessage with the given input item metadata
1138  *****************************************************************************/
1139
1140 #define ADD_META( entry, type, data ) \
1141     if( data ) { \
1142         dbus_message_iter_open_container( &dict, DBUS_TYPE_DICT_ENTRY, \
1143                 NULL, &dict_entry ); \
1144         dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, \
1145                 &ppsz_meta_items[entry] ); \
1146         dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, \
1147                 type##_AS_STRING, &variant ); \
1148         dbus_message_iter_append_basic( &variant, \
1149                 type, \
1150                 & data ); \
1151         dbus_message_iter_close_container( &dict_entry, &variant ); \
1152         dbus_message_iter_close_container( &dict, &dict_entry ); }
1153
1154 #define ADD_VLC_META_STRING( entry, item ) \
1155     { \
1156         char * psz = input_item_Get##item( p_input );\
1157         ADD_META( entry, DBUS_TYPE_STRING, \
1158                   psz ); \
1159         free( psz ); \
1160     }
1161
1162 static int GetInputMeta( input_item_t* p_input,
1163                         DBusMessageIter *args )
1164 {
1165     DBusMessageIter dict, dict_entry, variant;
1166     /* We need the track length to be expressed in milli-seconds
1167      * instead of µ-seconds */
1168     dbus_int64_t i_length = ( input_item_GetDuration( p_input ) / 1000 );
1169
1170     const char* ppsz_meta_items[] =
1171     {
1172     /* Official MPRIS metas */
1173     "location", "title", "artist", "album", "tracknumber", "genre",
1174     "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_VLC_META_STRING( 5,  Genre );
1190     ADD_VLC_META_STRING( 6,  Rating );
1191     ADD_VLC_META_STRING( 7,  Date );
1192     ADD_VLC_META_STRING( 8,  ArtURL );
1193
1194     ADD_VLC_META_STRING( 13, Copyright );
1195     ADD_VLC_META_STRING( 14, Description );
1196     ADD_VLC_META_STRING( 15, EncodedBy );
1197     ADD_VLC_META_STRING( 16, Language );
1198     ADD_META( 17, DBUS_TYPE_INT64, i_length );
1199     ADD_VLC_META_STRING( 18, NowPlaying );
1200     ADD_VLC_META_STRING( 19, Publisher );
1201     ADD_VLC_META_STRING( 20, Setting );
1202     ADD_VLC_META_STRING( 22, TrackID );
1203     ADD_VLC_META_STRING( 23, URL );
1204
1205     vlc_mutex_lock( &p_input->lock );
1206     if( p_input->p_meta )
1207     {
1208         int i_status = vlc_meta_GetStatus( p_input->p_meta );
1209         ADD_META( 21, DBUS_TYPE_INT32, i_status );
1210     }
1211     vlc_mutex_unlock( &p_input->lock );
1212
1213     dbus_message_iter_close_container( args, &dict );
1214     return VLC_SUCCESS;
1215 }
1216
1217 #undef ADD_META
1218 #undef ADD_VLC_META_STRING
1219
1220 /*****************************************************************************
1221  * MarshalStatus: Fill a DBusMessage with the current player status
1222  *****************************************************************************/
1223
1224 static int MarshalStatus( intf_thread_t* p_intf, DBusMessageIter* args )
1225 { /* This is NOT the right way to do that, it would be better to sore
1226      the status information in p_sys and update it on change, thus
1227      avoiding a long lock */
1228
1229     DBusMessageIter status;
1230     dbus_int32_t i_state, i_random, i_repeat, i_loop;
1231     int i_val;
1232     playlist_t* p_playlist = p_intf->p_sys->p_playlist;
1233     input_thread_t* p_input = NULL;
1234
1235     i_state = 2;
1236
1237     p_input = playlist_CurrentInput( p_playlist );
1238     if( p_input )
1239     {
1240         i_val = var_GetInteger( p_input, "state" );
1241         if( i_val >= END_S )
1242             i_state = 2;
1243         else if( i_val == PAUSE_S )
1244             i_state = 1;
1245         else if( i_val <= PLAYING_S )
1246             i_state = 0;
1247         vlc_object_release( p_input );
1248     }
1249
1250     i_random = var_CreateGetBool( p_playlist, "random" );
1251
1252     i_repeat = var_CreateGetBool( p_playlist, "repeat" );
1253
1254     i_loop = var_CreateGetBool( p_playlist, "loop" );
1255
1256     dbus_message_iter_open_container( args, DBUS_TYPE_STRUCT, NULL, &status );
1257     dbus_message_iter_append_basic( &status, DBUS_TYPE_INT32, &i_state );
1258     dbus_message_iter_append_basic( &status, DBUS_TYPE_INT32, &i_random );
1259     dbus_message_iter_append_basic( &status, DBUS_TYPE_INT32, &i_repeat );
1260     dbus_message_iter_append_basic( &status, DBUS_TYPE_INT32, &i_loop );
1261     dbus_message_iter_close_container( args, &status );
1262
1263     return VLC_SUCCESS;
1264 }