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