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