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