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