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