X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fcontrol%2Fdbus.c;h=d64ed6973efc1e603f8bcb34e46143a2e047f534;hb=61b3abff8ab709fad647c42f724136208497ccdc;hp=9efb5fa4c7baefb68c952eb31831d5fa4e0b390b;hpb=42c421605a7d2f7ccfaeb2a8b25d6565fc98279f;p=vlc diff --git a/modules/control/dbus.c b/modules/control/dbus.c index 9efb5fa4c7..d64ed6973e 100644 --- a/modules/control/dbus.c +++ b/modules/control/dbus.c @@ -1,8 +1,9 @@ /***************************************************************************** * dbus.c : D-Bus control interface ***************************************************************************** - * Copyright © 2006-2007 Rafaël Carré - * Copyright © 2007 Mirsal Ennaime + * Copyright © 2006-2008 Rafaël Carré + * Copyright © 2007-2008 Mirsal Ennaime + * Copyright © 2009 The VideoLAN team * $Id$ * * Authors: Rafaël Carré @@ -31,7 +32,7 @@ * extract: * "If you use this low-level API directly, you're signing up for some pain." * - * MPRIS Specification (still drafting on Oct, 23 of 2007): + * MPRIS Specification version 1.0 * http://wiki.xmms2.xmms.se/index.php/MPRIS */ @@ -39,16 +40,23 @@ * Preamble *****************************************************************************/ -#include +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include #include "dbus.h" -#include +#include +#include #include #include -#include -#include #include +#include + +#include + +#include /***************************************************************************** * Local prototypes. @@ -58,36 +66,85 @@ static int Open ( vlc_object_t * ); static void Close ( vlc_object_t * ); static void Run ( intf_thread_t * ); -static int StateChange( vlc_object_t *, const char *, vlc_value_t, - vlc_value_t, void * ); +static int StateChange( intf_thread_t *, int ); +static int TrackChange( intf_thread_t * ); +static int StatusChangeEmit( intf_thread_t *); +static int TrackListChangeEmit( intf_thread_t *, int, int ); -static int TrackChange( vlc_object_t *, const char *, vlc_value_t, - vlc_value_t, void * ); - -static int StatusChangeEmit( vlc_object_t *, const char *, vlc_value_t, - vlc_value_t, void * ); +static int AllCallback( vlc_object_t*, const char*, vlc_value_t, vlc_value_t, void* ); static int GetInputMeta ( input_item_t *, DBusMessageIter * ); static int MarshalStatus ( intf_thread_t *, DBusMessageIter * ); +static int UpdateCaps( intf_thread_t* ); + +/* GetCaps() capabilities */ +enum +{ + CAPS_NONE = 0, + CAPS_CAN_GO_NEXT = 1 << 0, + CAPS_CAN_GO_PREV = 1 << 1, + CAPS_CAN_PAUSE = 1 << 2, + CAPS_CAN_PLAY = 1 << 3, + CAPS_CAN_SEEK = 1 << 4, + CAPS_CAN_PROVIDE_METADATA = 1 << 5, + CAPS_CAN_HAS_TRACKLIST = 1 << 6 +}; + +// The signal that can be get from the callbacks +enum +{ + SIGNAL_ITEM_CURRENT, + SIGNAL_INTF_CHANGE, + SIGNAL_PLAYLIST_ITEM_APPEND, + SIGNAL_PLAYLIST_ITEM_DELETED, + SIGNAL_RANDOM, + SIGNAL_REPEAT, + SIGNAL_LOOP, + SIGNAL_STATE +}; struct intf_sys_t { DBusConnection *p_conn; - vlc_bool_t b_meta_read; + playlist_t *p_playlist; + bool b_meta_read; + dbus_int32_t i_caps; + bool b_dead; + vlc_array_t *p_events; + vlc_mutex_t lock; + input_thread_t *p_input; + bool b_unique; }; +typedef struct +{ + int signal; + int i_node; + int i_input_state; +} callback_info_t; + +#define INTF ((intf_thread_t *)p_this) +#define PL (INTF->p_sys->p_playlist) + + /***************************************************************************** * Module descriptor *****************************************************************************/ - -vlc_module_begin(); - set_shortname( _("dbus")); - set_category( CAT_INTERFACE ); - set_subcategory( SUBCAT_INTERFACE_CONTROL ); - set_description( _("D-Bus control interface") ); - set_capability( "interface", 0 ); - set_callbacks( Open, Close ); -vlc_module_end(); +#define DBUS_UNIQUE_TEXT N_("Unique DBUS service id (org.mpris.vlc-)") +#define DBUS_UNIQUE_LONGTEXT N_( \ + "Use a unique dbus service id to identify this VLC instance on the DBUS bus. " \ + "The process identifier (PID) is added to the service name: org.mpris.vlc-" ) + +vlc_module_begin () + set_shortname( N_("dbus")) + set_category( CAT_INTERFACE ) + set_subcategory( SUBCAT_INTERFACE_CONTROL ) + set_description( N_("D-Bus control interface") ) + set_capability( "interface", 0 ) + set_callbacks( Open, Close ) + add_bool( "dbus-unique-service-id", false, NULL, + DBUS_UNIQUE_TEXT, DBUS_UNIQUE_LONGTEXT, true ) +vlc_module_end () /***************************************************************************** * Methods @@ -98,10 +155,33 @@ vlc_module_end(); DBUS_METHOD( Quit ) { /* exits vlc */ REPLY_INIT; - playlist_t *p_playlist = pl_Yield( (vlc_object_t*) p_this ); - playlist_Stop( p_playlist ); - pl_Release( ((vlc_object_t*) p_this) ); - vlc_object_kill(((vlc_object_t*)p_this)->p_libvlc); + libvlc_Quit(INTF->p_libvlc); + REPLY_SEND; +} + +DBUS_METHOD( MprisVersion ) +{ /*implemented version of the mpris spec */ + REPLY_INIT; + OUT_ARGUMENTS; + VLC_UNUSED( p_this ); + dbus_uint16_t i_major = VLC_MPRIS_VERSION_MAJOR; + dbus_uint16_t i_minor = VLC_MPRIS_VERSION_MINOR; + DBusMessageIter version; + + if( !dbus_message_iter_open_container( &args, DBUS_TYPE_STRUCT, NULL, + &version ) ) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if( !dbus_message_iter_append_basic( &version, DBUS_TYPE_UINT16, + &i_major ) ) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if( !dbus_message_iter_append_basic( &version, DBUS_TYPE_UINT16, + &i_minor ) ) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if( !dbus_message_iter_close_container( &args, &version ) ) + return DBUS_HANDLER_RESULT_NEED_MEMORY; REPLY_SEND; } @@ -109,22 +189,17 @@ DBUS_METHOD( PositionGet ) { /* returns position in milliseconds */ REPLY_INIT; OUT_ARGUMENTS; - vlc_value_t position; dbus_int32_t i_pos; - playlist_t *p_playlist = pl_Yield( ((vlc_object_t*) p_this) ); - PL_LOCK; - input_thread_t *p_input = p_playlist->p_input; + input_thread_t *p_input = playlist_CurrentInput( PL ); if( !p_input ) i_pos = 0; else { - var_Get( p_input, "time", &position ); - i_pos = position.i_time / 1000; + i_pos = var_GetTime( p_input, "time" ) / 1000; + vlc_object_release( p_input ); } - PL_UNLOCK; - pl_Release( ((vlc_object_t*) p_this) ); ADD_INT32( &i_pos ); REPLY_SEND; } @@ -134,7 +209,6 @@ DBUS_METHOD( PositionSet ) REPLY_INIT; vlc_value_t position; - playlist_t* p_playlist = NULL; dbus_int32_t i_pos; DBusError error; @@ -146,22 +220,19 @@ DBUS_METHOD( PositionSet ) if( dbus_error_is_set( &error ) ) { - msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s\n", + msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s", error.message ); dbus_error_free( &error ); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } - p_playlist = pl_Yield( ((vlc_object_t*) p_this) ); - PL_LOCK; - input_thread_t *p_input = p_playlist->p_input; + input_thread_t *p_input = playlist_CurrentInput( PL ); if( p_input ) { - position.i_time = i_pos * 1000; + position.i_time = ((mtime_t)i_pos) * 1000; var_Set( p_input, "time", position ); + vlc_object_release( p_input ); } - PL_UNLOCK; - pl_Release( ((vlc_object_t*) p_this) ); REPLY_SEND; } @@ -171,9 +242,12 @@ DBUS_METHOD( VolumeGet ) OUT_ARGUMENTS; dbus_int32_t i_dbus_vol; audio_volume_t i_vol; + /* 2nd argument of aout_VolumeGet is int32 */ - aout_VolumeGet( (vlc_object_t*) p_this, &i_vol ); - i_dbus_vol = ( 100 * i_vol ) / AOUT_VOLUME_MAX; + aout_VolumeGet( PL, &i_vol ); + + double f_vol = 100. * i_vol / AOUT_VOLUME_MAX; + i_dbus_vol = round( f_vol ); ADD_INT32( &i_dbus_vol ); REPLY_SEND; } @@ -194,42 +268,36 @@ DBUS_METHOD( VolumeSet ) if( dbus_error_is_set( &error ) ) { - msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s\n", + msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s", error.message ); dbus_error_free( &error ); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } - i_vol = ( AOUT_VOLUME_MAX / 100 ) *i_dbus_vol; - aout_VolumeSet( (vlc_object_t*) p_this, i_vol ); - + double f_vol = AOUT_VOLUME_MAX * i_dbus_vol / 100.; + i_vol = round( f_vol ); + aout_VolumeSet( PL, i_vol ); REPLY_SEND; } DBUS_METHOD( Next ) { /* next playlist item */ REPLY_INIT; - playlist_t *p_playlist = pl_Yield( ((vlc_object_t*) p_this) ); - playlist_Next( p_playlist ); - pl_Release( ((vlc_object_t*) p_this) ); + playlist_Next( PL ); REPLY_SEND; } DBUS_METHOD( Prev ) { /* previous playlist item */ REPLY_INIT; - playlist_t *p_playlist = pl_Yield( ((vlc_object_t*) p_this) ); - playlist_Prev( p_playlist ); - pl_Release( ((vlc_object_t*) p_this) ); + playlist_Prev( PL ); REPLY_SEND; } DBUS_METHOD( Stop ) { /* stop playing */ REPLY_INIT; - playlist_t *p_playlist = pl_Yield( ((vlc_object_t*) p_this) ); - playlist_Stop( p_playlist ); - pl_Release( ((vlc_object_t*) p_this) ); + playlist_Stop( PL ); REPLY_SEND; } @@ -252,18 +320,25 @@ DBUS_METHOD( GetStatus ) DBUS_METHOD( Pause ) { REPLY_INIT; - playlist_t *p_playlist = pl_Yield( (vlc_object_t*) p_this ); - playlist_Pause( p_playlist ); - pl_Release( p_playlist ); + playlist_Pause( PL ); REPLY_SEND; } DBUS_METHOD( Play ) { REPLY_INIT; - playlist_t *p_playlist = pl_Yield( (vlc_object_t*) p_this ); - playlist_Play( p_playlist ); - pl_Release( p_playlist ); + + input_thread_t *p_input = playlist_CurrentInput( PL ); + + if( p_input ) + { + double i_pos = 0; + input_Control( p_input, INPUT_SET_POSITION, i_pos ); + vlc_object_release( p_input ); + } + else + playlist_Play( PL ); + REPLY_SEND; } @@ -271,12 +346,23 @@ DBUS_METHOD( GetCurrentMetadata ) { REPLY_INIT; OUT_ARGUMENTS; - playlist_t* p_playlist = pl_Yield( (vlc_object_t*) p_this ); + playlist_t *p_playlist = PL; + PL_LOCK; - if( p_playlist->status.p_item ) - GetInputMeta( p_playlist->status.p_item->p_input, &args ); + playlist_item_t* p_item = playlist_CurrentPlayingItem( p_playlist ); + if( p_item ) + GetInputMeta( p_item->p_input, &args ); PL_UNLOCK; - pl_Release( p_playlist ); + REPLY_SEND; +} + +DBUS_METHOD( GetCaps ) +{ + REPLY_INIT; + OUT_ARGUMENTS; + + ADD_INT32( &INTF->p_sys->i_caps ); + REPLY_SEND; } @@ -284,12 +370,19 @@ DBUS_METHOD( GetCurrentMetadata ) DBUS_METHOD( Identity ) { + VLC_UNUSED(p_this); REPLY_INIT; OUT_ARGUMENTS; - char *psz_identity = malloc( strlen( PACKAGE ) + strlen( VERSION ) + 2 ); - sprintf( psz_identity, "%s %s", PACKAGE, VERSION ); - ADD_STRING( &psz_identity ); - free( psz_identity ); + char *psz_identity; + + if( asprintf( &psz_identity, "%s %s", PACKAGE, VERSION ) != -1 ) + { + ADD_STRING( &psz_identity ); + free( psz_identity ); + } + else + return DBUS_HANDLER_RESULT_NEED_MEMORY; + REPLY_SEND; } @@ -298,10 +391,10 @@ DBUS_METHOD( Identity ) DBUS_METHOD( AddTrack ) { /* add the string to the playlist, and play it if the boolean is true */ REPLY_INIT; + OUT_ARGUMENTS; DBusError error; dbus_error_init( &error ); - playlist_t* p_playlist = NULL; char *psz_mrl; dbus_bool_t b_play; @@ -313,17 +406,18 @@ DBUS_METHOD( AddTrack ) if( dbus_error_is_set( &error ) ) { - msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s\n", + msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s", error.message ); dbus_error_free( &error ); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } - p_playlist = pl_Yield( (vlc_object_t*) p_this ); - playlist_Add( p_playlist, psz_mrl, NULL, PLAYLIST_APPEND | + playlist_Add( PL, psz_mrl, NULL, PLAYLIST_APPEND | ( ( b_play == TRUE ) ? PLAYLIST_GO : 0 ) , - PLAYLIST_END, VLC_TRUE, VLC_FALSE ); - pl_Release( p_playlist ); + PLAYLIST_END, true, false ); + + dbus_int32_t i_success = 0; + ADD_INT32( &i_success ); REPLY_SEND; } @@ -333,9 +427,11 @@ DBUS_METHOD( GetCurrentTrack ) REPLY_INIT; OUT_ARGUMENTS; - playlist_t *p_playlist = pl_Yield( (vlc_object_t*) p_this ); - dbus_int32_t i_position = p_playlist->i_current_index; - pl_Release( p_playlist ); + playlist_t *p_playlist = PL; + + PL_LOCK; + dbus_int32_t i_position = PL->i_current_index; + PL_UNLOCK; ADD_INT32( &i_position ); REPLY_SEND; @@ -349,8 +445,7 @@ DBUS_METHOD( GetMetadata ) dbus_error_init( &error ); dbus_int32_t i_position; - - playlist_t *p_playlist = pl_Yield( (vlc_object_t*) p_this ); + playlist_t *p_playlist = PL; dbus_message_get_args( p_from, &error, DBUS_TYPE_INT32, &i_position, @@ -358,18 +453,19 @@ DBUS_METHOD( GetMetadata ) if( dbus_error_is_set( &error ) ) { - msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s\n", + msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s", error.message ); dbus_error_free( &error ); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } - if( i_position <= p_playlist->items.i_size / 2 ) + PL_LOCK; + if( i_position < p_playlist->current.i_size ) { - GetInputMeta( p_playlist->items.p_elems[i_position*2-1]->p_input, &args ); + GetInputMeta( p_playlist->current.p_elems[i_position]->p_input, &args ); } - pl_Release( p_playlist ); + PL_UNLOCK; REPLY_SEND; } @@ -377,10 +473,11 @@ DBUS_METHOD( GetLength ) { REPLY_INIT; OUT_ARGUMENTS; + playlist_t *p_playlist = PL; - playlist_t *p_playlist = pl_Yield( (vlc_object_t*) p_this ); - dbus_int32_t i_elements = p_playlist->items.i_size / 2; - pl_Release( p_playlist ); + PL_LOCK; + dbus_int32_t i_elements = PL->current.i_size; + PL_UNLOCK; ADD_INT32( &i_elements ); REPLY_SEND; @@ -394,7 +491,7 @@ DBUS_METHOD( DelTrack ) dbus_error_init( &error ); dbus_int32_t i_position; - playlist_t *p_playlist = pl_Yield( (vlc_object_t*) p_this ); + playlist_t *p_playlist = PL; dbus_message_get_args( p_from, &error, DBUS_TYPE_INT32, &i_position, @@ -402,33 +499,31 @@ DBUS_METHOD( DelTrack ) if( dbus_error_is_set( &error ) ) { - msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s\n", + msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s", error.message ); dbus_error_free( &error ); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } - if( i_position >= p_playlist->items.i_size / 2 ) + PL_LOCK; + if( i_position < p_playlist->current.i_size ) { playlist_DeleteFromInput( p_playlist, - p_playlist->items.p_elems[i_position*2-1]->i_id, - VLC_FALSE ); + p_playlist->current.p_elems[i_position]->p_input, + pl_Locked ); } - - pl_Release( p_playlist ); + PL_UNLOCK; REPLY_SEND; } -DBUS_METHOD( Loop ) +DBUS_METHOD( SetLoop ) { REPLY_INIT; OUT_ARGUMENTS; DBusError error; dbus_bool_t b_loop; - vlc_value_t val; - playlist_t* p_playlist = NULL; dbus_error_init( &error ); dbus_message_get_args( p_from, &error, @@ -437,16 +532,13 @@ DBUS_METHOD( Loop ) if( dbus_error_is_set( &error ) ) { - msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s\n", + msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s", error.message ); dbus_error_free( &error ); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } - val.b_bool = ( b_loop == TRUE ) ? VLC_TRUE : VLC_FALSE ; - p_playlist = pl_Yield( (vlc_object_t*) p_this ); - var_Set ( p_playlist, "loop", val ); - pl_Release( ((vlc_object_t*) p_this) ); + var_SetBool( PL, "loop", ( b_loop == TRUE ) ); REPLY_SEND; } @@ -458,8 +550,6 @@ DBUS_METHOD( Repeat ) DBusError error; dbus_bool_t b_repeat; - vlc_value_t val; - playlist_t* p_playlist = NULL; dbus_error_init( &error ); dbus_message_get_args( p_from, &error, @@ -468,30 +558,24 @@ DBUS_METHOD( Repeat ) if( dbus_error_is_set( &error ) ) { - msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s\n", + msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s", error.message ); dbus_error_free( &error ); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } - val.b_bool = ( b_repeat == TRUE ) ? VLC_TRUE : VLC_FALSE ; - - p_playlist = pl_Yield( (vlc_object_t*) p_this ); - var_Set ( p_playlist, "repeat", val ); - pl_Release( ((vlc_object_t*) p_this) ); + var_SetBool( PL, "repeat", ( b_repeat == TRUE ) ); REPLY_SEND; } -DBUS_METHOD( Random ) +DBUS_METHOD( SetRandom ) { REPLY_INIT; OUT_ARGUMENTS; DBusError error; dbus_bool_t b_random; - vlc_value_t val; - playlist_t* p_playlist = NULL; dbus_error_init( &error ); dbus_message_get_args( p_from, &error, @@ -500,17 +584,13 @@ DBUS_METHOD( Random ) if( dbus_error_is_set( &error ) ) { - msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s\n", + msg_Err( (vlc_object_t*) p_this, "D-Bus message reading : %s", error.message ); dbus_error_free( &error ); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } - val.b_bool = ( b_random == TRUE ) ? VLC_TRUE : VLC_FALSE ; - - p_playlist = pl_Yield( (vlc_object_t*) p_this ); - var_Set ( p_playlist, "random", val ); - pl_Release( ((vlc_object_t*) p_this) ); + var_SetBool( PL, "random", ( b_random == TRUE ) ); REPLY_SEND; } @@ -520,6 +600,7 @@ DBUS_METHOD( Random ) DBUS_METHOD( handle_introspect_root ) { /* handles introspection of root object */ + VLC_UNUSED(p_this); REPLY_INIT; OUT_ARGUMENTS; ADD_STRING( &psz_introspection_xml_data_root ); @@ -528,6 +609,7 @@ DBUS_METHOD( handle_introspect_root ) DBUS_METHOD( handle_introspect_player ) { + VLC_UNUSED(p_this); REPLY_INIT; OUT_ARGUMENTS; ADD_STRING( &psz_introspection_xml_data_player ); @@ -536,6 +618,7 @@ DBUS_METHOD( handle_introspect_player ) DBUS_METHOD( handle_introspect_tracklist ) { + VLC_UNUSED(p_this); REPLY_INIT; OUT_ARGUMENTS; ADD_STRING( &psz_introspection_xml_data_tracklist ); @@ -560,6 +643,8 @@ DBUS_METHOD( handle_root ) /* here D-Bus method's names are associated to an handler */ METHOD_FUNC( "Identity", Identity ); + METHOD_FUNC( "MprisVersion", MprisVersion ); + METHOD_FUNC( "Quit", Quit ); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } @@ -569,13 +654,12 @@ DBUS_METHOD( handle_player ) { if( dbus_message_is_method_call( p_from, DBUS_INTERFACE_INTROSPECTABLE, "Introspect" ) ) - return handle_introspect_player( p_conn, p_from, p_this ); + return handle_introspect_player( p_conn, p_from, p_this ); /* here D-Bus method's names are associated to an handler */ METHOD_FUNC( "Prev", Prev ); METHOD_FUNC( "Next", Next ); - METHOD_FUNC( "Quit", Quit ); METHOD_FUNC( "Stop", Stop ); METHOD_FUNC( "Play", Play ); METHOD_FUNC( "Pause", Pause ); @@ -586,6 +670,7 @@ DBUS_METHOD( handle_player ) METHOD_FUNC( "PositionGet", PositionGet ); METHOD_FUNC( "GetStatus", GetStatus ); METHOD_FUNC( "GetMetadata", GetCurrentMetadata ); + METHOD_FUNC( "GetCaps", GetCaps ); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } @@ -603,8 +688,8 @@ DBUS_METHOD( handle_tracklist ) METHOD_FUNC( "GetLength", GetLength ); METHOD_FUNC( "AddTrack", AddTrack ); METHOD_FUNC( "DelTrack", DelTrack ); - METHOD_FUNC( "Loop", Loop ); - METHOD_FUNC( "Random", Random ); + METHOD_FUNC( "SetLoop", SetLoop ); + METHOD_FUNC( "SetRandom", SetRandom ); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } @@ -620,11 +705,30 @@ static int Open( vlc_object_t *p_this ) playlist_t *p_playlist; DBusConnection *p_conn; DBusError error; + char *psz_service_name = NULL; if( !p_sys ) return VLC_ENOMEM; - p_sys->b_meta_read = VLC_FALSE; + p_sys->b_meta_read = false; + p_sys->i_caps = CAPS_NONE; + p_sys->b_dead = false; + p_sys->p_input = NULL; + + p_sys->b_unique = var_CreateGetBool( p_intf, "dbus-unique-service-id" ); + if( p_sys->b_unique ) + { + if( asprintf( &psz_service_name, "%s-%d", + VLC_MPRIS_DBUS_SERVICE, getpid() ) < 0 ) + { + free( p_sys ); + return VLC_ENOMEM; + } + } + else + { + psz_service_name = strdup(VLC_MPRIS_DBUS_SERVICE); + } dbus_error_init( &error ); @@ -640,15 +744,18 @@ static int Open( vlc_object_t *p_this ) } /* register a well-known name on the bus */ - dbus_bus_request_name( p_conn, VLC_MPRIS_DBUS_SERVICE, 0, &error ); + dbus_bus_request_name( p_conn, psz_service_name, 0, &error ); if( dbus_error_is_set( &error ) ) { - msg_Err( p_this, "Error requesting service " VLC_MPRIS_DBUS_SERVICE - ": %s", error.message ); + msg_Err( p_this, "Error requesting service %s: %s", + psz_service_name, error.message ); dbus_error_free( &error ); + free( psz_service_name ); free( p_sys ); return VLC_EGENERIC; } + msg_Info( p_intf, "listening on dbus as: %s", psz_service_name ); + free( psz_service_name ); /* we register the objects */ dbus_connection_register_object_path( p_conn, MPRIS_DBUS_ROOT_PATH, @@ -660,18 +767,24 @@ static int Open( vlc_object_t *p_this ) dbus_connection_flush( p_conn ); - p_playlist = pl_Yield( p_intf ); - PL_LOCK; - var_AddCallback( p_playlist, "playlist-current", TrackChange, p_intf ); - var_AddCallback( p_playlist, "random", StatusChangeEmit, p_intf ); - var_AddCallback( p_playlist, "repeat", StatusChangeEmit, p_intf ); - var_AddCallback( p_playlist, "loop", StatusChangeEmit, p_intf ); - PL_UNLOCK; - pl_Release( p_playlist ); - p_intf->pf_run = Run; p_intf->p_sys = p_sys; p_sys->p_conn = p_conn; + p_sys->p_events = vlc_array_new(); + vlc_mutex_init( &p_sys->lock ); + + p_playlist = pl_Get( p_intf ); + p_sys->p_playlist = p_playlist; + + var_AddCallback( p_playlist, "item-current", AllCallback, p_intf ); + var_AddCallback( p_playlist, "intf-change", AllCallback, p_intf ); + var_AddCallback( p_playlist, "playlist-item-append", AllCallback, p_intf ); + var_AddCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf ); + var_AddCallback( p_playlist, "random", AllCallback, p_intf ); + var_AddCallback( p_playlist, "repeat", AllCallback, p_intf ); + var_AddCallback( p_playlist, "loop", AllCallback, p_intf ); + + UpdateCaps( p_intf ); return VLC_SUCCESS; } @@ -683,31 +796,34 @@ static int Open( vlc_object_t *p_this ) static void Close ( vlc_object_t *p_this ) { intf_thread_t *p_intf = (intf_thread_t*) p_this; - playlist_t *p_playlist = pl_Yield( p_intf );; - input_thread_t *p_input; - - p_this->b_dead = VLC_TRUE; - - PL_LOCK; - var_DelCallback( p_playlist, "playlist-current", TrackChange, p_intf ); - var_DelCallback( p_playlist, "random", StatusChangeEmit, p_intf ); - var_DelCallback( p_playlist, "repeat", StatusChangeEmit, p_intf ); - var_DelCallback( p_playlist, "loop", StatusChangeEmit, p_intf ); - - p_input = p_playlist->p_input; - if ( p_input ) + intf_sys_t *p_sys = p_intf->p_sys; + playlist_t *p_playlist = p_sys->p_playlist; + + var_DelCallback( p_playlist, "item-current", AllCallback, p_intf ); + var_DelCallback( p_playlist, "intf-change", AllCallback, p_intf ); + var_DelCallback( p_playlist, "playlist-item-append", AllCallback, p_intf ); + var_DelCallback( p_playlist, "playlist-item-deleted", AllCallback, p_intf ); + var_DelCallback( p_playlist, "random", AllCallback, p_intf ); + var_DelCallback( p_playlist, "repeat", AllCallback, p_intf ); + var_DelCallback( p_playlist, "loop", AllCallback, p_intf ); + + if( p_sys->p_input ) { - vlc_object_yield( p_input ); - var_DelCallback( p_input, "state", StateChange, p_intf ); - vlc_object_release( p_input ); + var_DelCallback( p_sys->p_input, "state", AllCallback, p_intf ); + vlc_object_release( p_sys->p_input ); } - PL_UNLOCK; - pl_Release( p_playlist ); - - dbus_connection_unref( p_intf->p_sys->p_conn ); + dbus_connection_unref( p_sys->p_conn ); - free( p_intf->p_sys ); + // Free the events array + for( int i = 0; i < vlc_array_count( p_sys->p_events ); i++ ) + { + callback_info_t* info = vlc_array_item_at_index( p_sys->p_events, i ); + free( info ); + } + vlc_mutex_destroy( &p_sys->lock ); + vlc_array_destroy( p_sys->p_events ); + free( p_sys ); } /***************************************************************************** @@ -716,20 +832,175 @@ static void Close ( vlc_object_t *p_this ) static void Run ( intf_thread_t *p_intf ) { - while( !intf_ShouldDie( p_intf ) ) + for( ;; ) { - msleep( INTF_IDLE_SLEEP ); + if( dbus_connection_get_dispatch_status(p_intf->p_sys->p_conn) + == DBUS_DISPATCH_COMPLETE ) + msleep( INTF_IDLE_SLEEP ); + int canc = vlc_savecancel(); dbus_connection_read_write_dispatch( p_intf->p_sys->p_conn, 0 ); + + /* Get the list of events to process + * + * We can't keep the lock on p_intf->p_sys->p_events, else we risk a + * deadlock: + * The signal functions could lock mutex X while p_events is locked; + * While some other function in vlc (playlist) might lock mutex X + * and then set a variable which would call AllCallback(), which itself + * needs to lock p_events to add a new event. + */ + vlc_mutex_lock( &p_intf->p_sys->lock ); + int i_events = vlc_array_count( p_intf->p_sys->p_events ); + callback_info_t* info[i_events]; + for( int i = i_events - 1; i >= 0; i-- ) + { + info[i] = vlc_array_item_at_index( p_intf->p_sys->p_events, i ); + vlc_array_remove( p_intf->p_sys->p_events, i ); + } + vlc_mutex_unlock( &p_intf->p_sys->lock ); + + for( int i = 0; i < i_events; i++ ) + { + switch( info[i]->signal ) + { + case SIGNAL_ITEM_CURRENT: + TrackChange( p_intf ); + break; + case SIGNAL_INTF_CHANGE: + case SIGNAL_PLAYLIST_ITEM_APPEND: + case SIGNAL_PLAYLIST_ITEM_DELETED: + TrackListChangeEmit( p_intf, info[i]->signal, info[i]->i_node ); + break; + case SIGNAL_RANDOM: + case SIGNAL_REPEAT: + case SIGNAL_LOOP: + StatusChangeEmit( p_intf ); + break; + case SIGNAL_STATE: + StateChange( p_intf, info[i]->i_input_state ); + break; + default: + assert(0); + } + free( info[i] ); + } + vlc_restorecancel( canc ); } } + +// Get all the callbacks +static int AllCallback( vlc_object_t *p_this, const char *psz_var, + vlc_value_t oldval, vlc_value_t newval, void *p_data ) +{ + (void)p_this; + (void)oldval; + intf_thread_t *p_intf = (intf_thread_t*)p_data; + + callback_info_t *info = malloc( sizeof( callback_info_t ) ); + if( !info ) + return VLC_ENOMEM; + + // Wich event is it ? + if( !strcmp( "item-current", psz_var ) ) + info->signal = SIGNAL_ITEM_CURRENT; + else if( !strcmp( "intf-change", psz_var ) ) + info->signal = SIGNAL_INTF_CHANGE; + else if( !strcmp( "playlist-item-append", psz_var ) ) + { + info->signal = SIGNAL_PLAYLIST_ITEM_APPEND; + info->i_node = ((playlist_add_t*)newval.p_address)->i_node; + } + else if( !strcmp( "playlist-item-deleted", psz_var ) ) + info->signal = SIGNAL_PLAYLIST_ITEM_DELETED; + else if( !strcmp( "random", psz_var ) ) + info->signal = SIGNAL_RANDOM; + else if( !strcmp( "repeat", psz_var ) ) + info->signal = SIGNAL_REPEAT; + else if( !strcmp( "loop", psz_var ) ) + info->signal = SIGNAL_LOOP; + else if( !strcmp( "state", psz_var ) ) + { + info->signal = SIGNAL_STATE; + info->i_input_state = newval.i_int; + } + else + assert(0); + + // Append the event + vlc_mutex_lock( &p_intf->p_sys->lock ); + vlc_array_append( p_intf->p_sys->p_events, info ); + vlc_mutex_unlock( &p_intf->p_sys->lock ); + return VLC_SUCCESS; +} + +/****************************************************************************** + * CapsChange: player capabilities change signal + *****************************************************************************/ +DBUS_SIGNAL( CapsChangeSignal ) +{ + SIGNAL_INIT( MPRIS_DBUS_PLAYER_PATH, "CapsChange" ); + OUT_ARGUMENTS; + + ADD_INT32( &((intf_thread_t*)p_data)->p_sys->i_caps ); + SIGNAL_SEND; +} + +/****************************************************************************** + * TrackListChange: tracklist order / length change signal + *****************************************************************************/ +DBUS_SIGNAL( TrackListChangeSignal ) +{ /* emit the new tracklist lengh */ + SIGNAL_INIT( MPRIS_DBUS_TRACKLIST_PATH, "TrackListChange"); + OUT_ARGUMENTS; + + playlist_t *p_playlist = ((intf_thread_t*)p_data)->p_sys->p_playlist; + PL_LOCK; + dbus_int32_t i_elements = p_playlist->current.i_size; + PL_UNLOCK; + + ADD_INT32( &i_elements ); + SIGNAL_SEND; +} + +/***************************************************************************** + * TrackListChangeEmit: Emits the TrackListChange signal + *****************************************************************************/ +/* FIXME: It is not called on tracklist reordering */ +static int TrackListChangeEmit( intf_thread_t *p_intf, int signal, int i_node ) +{ + // "playlist-item-append" + if( signal == SIGNAL_PLAYLIST_ITEM_APPEND ) + { + /* don't signal when items are added/removed in p_category */ + playlist_t *p_playlist = p_intf->p_sys->p_playlist; + PL_LOCK; + playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_node ); + assert( p_item ); + while( p_item->p_parent ) + p_item = p_item->p_parent; + if( p_item == p_playlist->p_root_category ) + { + PL_UNLOCK; + return VLC_SUCCESS; + } + PL_UNLOCK; + } + + if( p_intf->p_sys->b_dead ) + return VLC_SUCCESS; + + UpdateCaps( p_intf ); + TrackListChangeSignal( p_intf->p_sys->p_conn, p_intf ); + return VLC_SUCCESS; +} /***************************************************************************** * TrackChange: Playlist item change callback *****************************************************************************/ DBUS_SIGNAL( TrackChangeSignal ) { /* emit the metadata of the new item */ - SIGNAL_INIT( "TrackChange" ); + SIGNAL_INIT( MPRIS_DBUS_PLAYER_PATH, "TrackChange" ); OUT_ARGUMENTS; input_item_t *p_item = (input_item_t*) p_data; @@ -744,9 +1015,11 @@ DBUS_SIGNAL( TrackChangeSignal ) DBUS_SIGNAL( StatusChangeSignal ) { /* send the updated status info on the bus */ - SIGNAL_INIT( "StatusChange" ); + SIGNAL_INIT( MPRIS_DBUS_PLAYER_PATH, "StatusChange" ); OUT_ARGUMENTS; + /* we're called from a callback of input_thread_t, so it can not be + * destroyed before we return */ MarshalStatus( (intf_thread_t*) p_data, &args ); SIGNAL_SEND; @@ -755,29 +1028,39 @@ DBUS_SIGNAL( StatusChangeSignal ) /***************************************************************************** * StateChange: callback on input "state" *****************************************************************************/ -static int StateChange( vlc_object_t *p_this, const char* psz_var, - vlc_value_t oldval, vlc_value_t newval, void *p_data ) +//static int StateChange( vlc_object_t *p_this, const char* psz_var, +// vlc_value_t oldval, vlc_value_t newval, void *p_data ) +static int StateChange( intf_thread_t *p_intf, int i_input_state ) { - intf_thread_t *p_intf = ( intf_thread_t* ) p_data; intf_sys_t *p_sys = p_intf->p_sys; + playlist_t *p_playlist = p_sys->p_playlist; + input_thread_t *p_input; + input_item_t *p_item; - if( p_this->b_dead ) + if( p_intf->p_sys->b_dead ) return VLC_SUCCESS; - if( !p_sys->b_meta_read && newval.i_int == PLAYING_S ) + UpdateCaps( p_intf ); + + if( !p_sys->b_meta_read && i_input_state == PLAYING_S ) { - input_item_t *p_item = input_GetItem( (input_thread_t*)p_this ); - if( p_item ) + p_input = playlist_CurrentInput( p_playlist ); + if( p_input ) { - p_sys->b_meta_read = VLC_TRUE; - TrackChangeSignal( p_sys->p_conn, p_item ); + p_item = input_GetItem( p_input ); + if( p_item ) + { + p_sys->b_meta_read = true; + TrackChangeSignal( p_sys->p_conn, p_item ); + } + vlc_object_release( p_input ); } } - if( newval.i_int == PLAYING_S || newval.i_int == PAUSE_S || - newval.i_int == END_S ) + if( i_input_state == PLAYING_S || i_input_state == PAUSE_S || + i_input_state == END_S ) { - StatusChangeSignal( p_sys->p_conn, (void*) p_intf ); + StatusChangeSignal( p_sys->p_conn, p_intf ); } return VLC_SUCCESS; @@ -786,50 +1069,44 @@ static int StateChange( vlc_object_t *p_this, const char* psz_var, /***************************************************************************** * StatusChangeEmit: Emits the StatusChange signal *****************************************************************************/ -static int StatusChangeEmit( vlc_object_t *p_this, const char *psz_var, - vlc_value_t oldval, vlc_value_t newval, void *p_data ) +static int StatusChangeEmit( intf_thread_t * p_intf ) { - if( p_this->b_dead ) + if( p_intf->p_sys->b_dead ) return VLC_SUCCESS; - StatusChangeSignal( ((intf_thread_t*)p_data)->p_sys->p_conn, p_data ); + UpdateCaps( p_intf ); + StatusChangeSignal( p_intf->p_sys->p_conn, p_intf ); return VLC_SUCCESS; } /***************************************************************************** - * TrackChange: callback on playlist "playlist-current" + * TrackChange: callback on playlist "item-current" *****************************************************************************/ -static int TrackChange( vlc_object_t *p_this, const char *psz_var, - vlc_value_t oldval, vlc_value_t newval, void *p_data ) +static int TrackChange( intf_thread_t *p_intf ) { - intf_thread_t *p_intf = ( intf_thread_t* ) p_data; intf_sys_t *p_sys = p_intf->p_sys; - playlist_t *p_playlist; + playlist_t *p_playlist = p_sys->p_playlist; input_thread_t *p_input = NULL; input_item_t *p_item = NULL; - VLC_UNUSED( p_this ); VLC_UNUSED( psz_var ); - VLC_UNUSED( oldval ); VLC_UNUSED( newval ); - if( p_this->b_dead ) + if( p_intf->p_sys->b_dead ) return VLC_SUCCESS; - p_sys->b_meta_read = VLC_FALSE; + if( p_sys->p_input ) + { + var_DelCallback( p_sys->p_input, "state", AllCallback, p_intf ); + vlc_object_release( p_sys->p_input ); + p_sys->p_input = NULL; + } - p_playlist = pl_Yield( p_intf ); - PL_LOCK; - p_input = p_playlist->p_input; + p_sys->b_meta_read = false; + p_input = playlist_CurrentInput( p_playlist ); if( !p_input ) { - PL_UNLOCK; - pl_Release( p_playlist ); return VLC_SUCCESS; } - vlc_object_yield( p_input ); - PL_UNLOCK; - pl_Release( p_playlist ); - p_item = input_GetItem( p_input ); if( !p_item ) { @@ -839,13 +1116,52 @@ static int TrackChange( vlc_object_t *p_this, const char *psz_var, if( input_item_IsPreparsed( p_item ) ) { - p_sys->b_meta_read = VLC_TRUE; + p_sys->b_meta_read = true; TrackChangeSignal( p_sys->p_conn, p_item ); } - var_AddCallback( p_input, "state", StateChange, p_intf ); + p_sys->p_input = p_input; + var_AddCallback( p_input, "state", AllCallback, p_intf ); + + return VLC_SUCCESS; +} + +/***************************************************************************** + * UpdateCaps: update p_sys->i_caps + * This function have to be called with the playlist unlocked + ****************************************************************************/ +static int UpdateCaps( intf_thread_t* p_intf ) +{ + intf_sys_t* p_sys = p_intf->p_sys; + dbus_int32_t i_caps = CAPS_CAN_HAS_TRACKLIST; + playlist_t* p_playlist = p_sys->p_playlist; + + PL_LOCK; + if( p_playlist->current.i_size > 0 ) + i_caps |= CAPS_CAN_PLAY | CAPS_CAN_GO_PREV | CAPS_CAN_GO_NEXT; + PL_UNLOCK; + + input_thread_t* p_input = playlist_CurrentInput( p_playlist ); + if( p_input ) + { + /* XXX: if UpdateCaps() is called too early, these are + * unconditionnaly true */ + if( var_GetBool( p_input, "can-pause" ) ) + i_caps |= CAPS_CAN_PAUSE; + if( var_GetBool( p_input, "can-seek" ) ) + i_caps |= CAPS_CAN_SEEK; + vlc_object_release( p_input ); + } + + if( p_sys->b_meta_read ) + i_caps |= CAPS_CAN_PROVIDE_METADATA; + + if( i_caps != p_intf->p_sys->i_caps ) + { + p_sys->i_caps = i_caps; + CapsChangeSignal( p_intf->p_sys->p_conn, (vlc_object_t*)p_intf ); + } - vlc_object_release( p_input ); return VLC_SUCCESS; } @@ -879,47 +1195,57 @@ static int GetInputMeta( input_item_t* p_input, DBusMessageIter *args ) { DBusMessageIter dict, dict_entry, variant; - /* We need the track length to be expressed in seconds - * instead of milliseconds */ - dbus_int64_t i_length = ( input_item_GetDuration( p_input ) / 1000 ); + /** The duration of the track can be expressed in second, milli-seconds and + µ-seconds */ + dbus_int64_t i_mtime = input_item_GetDuration( p_input ); + dbus_uint32_t i_time = i_mtime / 1000000; + dbus_int64_t i_length = i_mtime / 1000; const char* ppsz_meta_items[] = { - "title", "artist", "genre", "copyright", "album", "tracknum", - "description", "rating", "date", "setting", "url", "language", - "nowplaying", "publisher", "encodedby", "arturl", "trackid", - "status", "URI", "length", "video-codec", "audio-codec", - "video-bitrate", "audio-bitrate", "audio-samplerate" + /* Official MPRIS metas */ + "location", "title", "artist", "album", "tracknumber", "time", "mtime", + "genre", "rating", "date", "arturl", + "audio-bitrate", "audio-samplerate", "video-bitrate", + /* VLC specifics metas */ + "audio-codec", "copyright", "description", "encodedby", "language", "length", + "nowplaying", "publisher", "setting", "status", "trackid", "url", + "video-codec" }; dbus_message_iter_open_container( args, DBUS_TYPE_ARRAY, "{sv}", &dict ); - ADD_VLC_META_STRING( 0, Title ); - ADD_VLC_META_STRING( 1, Artist ); - ADD_VLC_META_STRING( 2, Genre ); - ADD_VLC_META_STRING( 3, Copyright ); - ADD_VLC_META_STRING( 4, Album ); - ADD_VLC_META_STRING( 5, TrackNum ); - ADD_VLC_META_STRING( 6, Description ); - ADD_VLC_META_STRING( 7, Rating ); - ADD_VLC_META_STRING( 8, Date ); - ADD_VLC_META_STRING( 9, Setting ); - ADD_VLC_META_STRING( 10, URL ); - ADD_VLC_META_STRING( 11, Language ); - ADD_VLC_META_STRING( 12, NowPlaying ); - ADD_VLC_META_STRING( 13, Publisher ); - ADD_VLC_META_STRING( 14, EncodedBy ); - ADD_VLC_META_STRING( 15, ArtURL ); - ADD_VLC_META_STRING( 16, TrackID ); + ADD_VLC_META_STRING( 0, URI ); + ADD_VLC_META_STRING( 1, Title ); + ADD_VLC_META_STRING( 2, Artist ); + ADD_VLC_META_STRING( 3, Album ); + ADD_VLC_META_STRING( 4, TrackNum ); + ADD_META( 5, DBUS_TYPE_UINT32, i_time ); + ADD_META( 6, DBUS_TYPE_UINT32, i_mtime ); + ADD_VLC_META_STRING( 7, Genre ); + ADD_VLC_META_STRING( 8, Rating ); + ADD_VLC_META_STRING( 9, Date ); + ADD_VLC_META_STRING( 10, ArtURL ); + + ADD_VLC_META_STRING( 15, Copyright ); + ADD_VLC_META_STRING( 16, Description ); + ADD_VLC_META_STRING( 17, EncodedBy ); + ADD_VLC_META_STRING( 18, Language ); + ADD_META( 19, DBUS_TYPE_INT64, i_length ); + ADD_VLC_META_STRING( 20, NowPlaying ); + ADD_VLC_META_STRING( 21, Publisher ); + ADD_VLC_META_STRING( 22, Setting ); + ADD_VLC_META_STRING( 24, TrackID ); + ADD_VLC_META_STRING( 25, URL ); vlc_mutex_lock( &p_input->lock ); if( p_input->p_meta ) - ADD_META( 17, DBUS_TYPE_INT32, p_input->p_meta->i_status ); + { + int i_status = vlc_meta_GetStatus( p_input->p_meta ); + ADD_META( 23, DBUS_TYPE_INT32, i_status ); + } vlc_mutex_unlock( &p_input->lock ); - ADD_VLC_META_STRING( 18, URI ); - ADD_META( 19, DBUS_TYPE_INT64, i_length ); - dbus_message_iter_close_container( args, &dict ); return VLC_SUCCESS; } @@ -938,37 +1264,30 @@ static int MarshalStatus( intf_thread_t* p_intf, DBusMessageIter* args ) DBusMessageIter status; dbus_int32_t i_state, i_random, i_repeat, i_loop; - vlc_value_t val; - playlist_t* p_playlist = NULL; + int i_val; + playlist_t* p_playlist = p_intf->p_sys->p_playlist; input_thread_t* p_input = NULL; - p_playlist = pl_Yield( (vlc_object_t*) p_intf ); - PL_LOCK; - p_input = p_playlist->p_input; - i_state = 2; + + p_input = playlist_CurrentInput( p_playlist ); if( p_input ) { - var_Get( p_input, "state", &val ); - if( val.i_int >= END_S ) + i_val = var_GetInteger( p_input, "state" ); + if( i_val >= END_S ) i_state = 2; - else if( val.i_int == PAUSE_S ) + else if( i_val == PAUSE_S ) i_state = 1; - else if( val.i_int <= PLAYING_S ) + else if( i_val <= PLAYING_S ) i_state = 0; + vlc_object_release( p_input ); } - var_Get( p_playlist, "random", &val ); - i_random = val.i_int; + i_random = var_CreateGetBool( p_playlist, "random" ); - var_Get( p_playlist, "repeat", &val ); - i_repeat = val.i_int; + i_repeat = var_CreateGetBool( p_playlist, "repeat" ); - var_Get( p_playlist, "loop", &val ); - i_loop = val.i_int; - - PL_UNLOCK; - pl_Release( p_playlist ); + i_loop = var_CreateGetBool( p_playlist, "loop" ); dbus_message_iter_open_container( args, DBUS_TYPE_STRUCT, NULL, &status ); dbus_message_iter_append_basic( &status, DBUS_TYPE_INT32, &i_state ); @@ -978,5 +1297,4 @@ static int MarshalStatus( intf_thread_t* p_intf, DBusMessageIter* args ) dbus_message_iter_close_container( args, &status ); return VLC_SUCCESS; - }