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