2008-08-03 23:23:13 +02:00
/***
This file is part of PulseAudio .
Copyright 2008 Lennart Poettering
PulseAudio is free software ; you can redistribute it and / or modify
it under the terms of the GNU Lesser General Public License as published
2009-03-03 20:23:02 +00:00
by the Free Software Foundation ; either version 2.1 of the License ,
2008-08-03 23:23:13 +02:00
or ( at your option ) any later version .
PulseAudio is distributed in the hope that it will be useful , but
WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
General Public License for more details .
You should have received a copy of the GNU Lesser General Public License
along with PulseAudio ; if not , write to the Free Software
Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307
USA .
* * */
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
# include <unistd.h>
# include <string.h>
# include <errno.h>
# include <sys/types.h>
# include <stdio.h>
# include <stdlib.h>
# include <ctype.h>
# include <pulse/xmalloc.h>
# include <pulse/volume.h>
# include <pulse/timeval.h>
# include <pulse/util.h>
# include <pulsecore/core-error.h>
# include <pulsecore/module.h>
# include <pulsecore/core-util.h>
# include <pulsecore/modargs.h>
# include <pulsecore/log.h>
# include <pulsecore/core-subscribe.h>
# include <pulsecore/sink-input.h>
# include <pulsecore/source-output.h>
# include <pulsecore/namereg.h>
2008-08-04 19:01:13 +02:00
# include <pulsecore/protocol-native.h>
# include <pulsecore/pstream.h>
# include <pulsecore/pstream-util.h>
2009-05-14 01:24:26 +02:00
# include <pulsecore/database.h>
2008-08-03 23:23:13 +02:00
# include "module-stream-restore-symdef.h"
PA_MODULE_AUTHOR ( " Lennart Poettering " ) ;
PA_MODULE_DESCRIPTION ( " Automatically restore the volume/mute/device state of streams " ) ;
PA_MODULE_VERSION ( PACKAGE_VERSION ) ;
PA_MODULE_LOAD_ONCE ( TRUE ) ;
2009-01-28 00:19:47 +01:00
PA_MODULE_USAGE (
" restore_device=<Save/restore sinks/sources?> "
" restore_volume=<Save/restore volumes?> "
" restore_muted=<Save/restore muted states?> " ) ;
2008-08-03 23:23:13 +02:00
# define SAVE_INTERVAL 10
2009-02-04 22:26:08 +01:00
# define IDENTIFICATION_PROPERTY "module-stream-restore.id"
2008-08-03 23:23:13 +02:00
static const char * const valid_modargs [ ] = {
" restore_device " ,
" restore_volume " ,
" restore_muted " ,
NULL
} ;
struct userdata {
pa_core * core ;
2008-08-04 19:00:43 +02:00
pa_module * module ;
2008-08-03 23:23:13 +02:00
pa_subscription * subscription ;
pa_hook_slot
* sink_input_new_hook_slot ,
* sink_input_fixate_hook_slot ,
2008-08-04 19:01:13 +02:00
* source_output_new_hook_slot ,
* connection_unlink_hook_slot ;
2008-08-03 23:23:13 +02:00
pa_time_event * save_time_event ;
2009-05-14 01:24:26 +02:00
pa_database * database ;
2008-08-03 23:23:13 +02:00
pa_bool_t restore_device : 1 ;
pa_bool_t restore_volume : 1 ;
pa_bool_t restore_muted : 1 ;
2008-08-04 19:01:13 +02:00
pa_native_protocol * protocol ;
pa_idxset * subscribed ;
2008-08-03 23:23:13 +02:00
} ;
2009-04-13 22:50:24 +02:00
# define ENTRY_VERSION 2
2009-02-04 18:31:24 +01:00
2009-02-13 18:02:47 +01:00
struct entry {
2009-02-04 18:31:24 +01:00
uint8_t version ;
2009-04-13 22:50:24 +02:00
pa_bool_t muted_valid : 1 , volume_valid : 1 , device_valid : 1 ;
2009-02-04 18:31:24 +01:00
pa_bool_t muted : 1 ;
2008-08-04 19:00:43 +02:00
pa_channel_map channel_map ;
2009-04-13 22:50:24 +02:00
pa_cvolume volume ;
2009-02-04 18:31:24 +01:00
char device [ PA_NAME_MAX ] ;
2009-02-13 18:02:47 +01:00
} PA_GCC_PACKED ;
2008-08-03 23:23:13 +02:00
2008-08-04 19:01:13 +02:00
enum {
SUBCOMMAND_TEST ,
SUBCOMMAND_READ ,
SUBCOMMAND_WRITE ,
SUBCOMMAND_DELETE ,
SUBCOMMAND_SUBSCRIBE ,
SUBCOMMAND_EVENT
} ;
2008-08-03 23:23:13 +02:00
static void save_time_callback ( pa_mainloop_api * a , pa_time_event * e , const struct timeval * tv , void * userdata ) {
struct userdata * u = userdata ;
pa_assert ( a ) ;
pa_assert ( e ) ;
pa_assert ( tv ) ;
pa_assert ( u ) ;
pa_assert ( e = = u - > save_time_event ) ;
u - > core - > mainloop - > time_free ( u - > save_time_event ) ;
u - > save_time_event = NULL ;
2009-05-14 01:24:26 +02:00
pa_database_sync ( u - > database ) ;
2008-08-03 23:23:13 +02:00
pa_log_info ( " Synced. " ) ;
}
static char * get_name ( pa_proplist * p , const char * prefix ) {
const char * r ;
2009-02-04 22:26:08 +01:00
char * t ;
2008-08-03 23:23:13 +02:00
if ( ! p )
return NULL ;
2009-02-04 22:26:08 +01:00
if ( ( r = pa_proplist_gets ( p , IDENTIFICATION_PROPERTY ) ) )
return pa_xstrdup ( r ) ;
2008-08-03 23:23:13 +02:00
if ( ( r = pa_proplist_gets ( p , PA_PROP_MEDIA_ROLE ) ) )
2009-02-04 22:26:08 +01:00
t = pa_sprintf_malloc ( " %s-by-media-role:%s " , prefix , r ) ;
2008-08-03 23:23:13 +02:00
else if ( ( r = pa_proplist_gets ( p , PA_PROP_APPLICATION_ID ) ) )
2009-02-04 22:26:08 +01:00
t = pa_sprintf_malloc ( " %s-by-application-id:%s " , prefix , r ) ;
2008-08-03 23:23:13 +02:00
else if ( ( r = pa_proplist_gets ( p , PA_PROP_APPLICATION_NAME ) ) )
2009-02-04 22:26:08 +01:00
t = pa_sprintf_malloc ( " %s-by-application-name:%s " , prefix , r ) ;
2008-08-03 23:23:13 +02:00
else if ( ( r = pa_proplist_gets ( p , PA_PROP_MEDIA_NAME ) ) )
2009-02-04 22:26:08 +01:00
t = pa_sprintf_malloc ( " %s-by-media-name:%s " , prefix , r ) ;
else
t = pa_sprintf_malloc ( " %s-fallback:%s " , prefix , r ) ;
2008-08-03 23:23:13 +02:00
2009-02-04 22:26:08 +01:00
pa_proplist_sets ( p , IDENTIFICATION_PROPERTY , t ) ;
return t ;
2008-08-03 23:23:13 +02:00
}
2009-01-21 02:47:49 +01:00
static struct entry * read_entry ( struct userdata * u , const char * name ) {
2009-05-14 01:24:26 +02:00
pa_datum key , data ;
2008-08-03 23:23:13 +02:00
struct entry * e ;
pa_assert ( u ) ;
pa_assert ( name ) ;
2009-05-14 01:24:26 +02:00
key . data = ( char * ) name ;
key . size = strlen ( name ) ;
2008-08-03 23:23:13 +02:00
2009-05-14 01:24:26 +02:00
pa_zero ( data ) ;
2008-08-03 23:23:13 +02:00
2009-05-14 01:24:26 +02:00
if ( ! pa_database_get ( u - > database , & key , & data ) )
2008-08-03 23:23:13 +02:00
goto fail ;
2009-05-14 01:24:26 +02:00
if ( data . size ! = sizeof ( struct entry ) ) {
2009-01-27 23:39:39 +01:00
/* This is probably just a database upgrade, hence let's not
* consider this more than a debug message */
2009-05-14 01:24:26 +02:00
pa_log_debug ( " Database contains entry for stream %s of wrong size %lu != %lu. Probably due to uprade, ignoring. " , name , ( unsigned long ) data . size , ( unsigned long ) sizeof ( struct entry ) ) ;
2008-08-03 23:23:13 +02:00
goto fail ;
}
2009-05-14 01:24:26 +02:00
e = ( struct entry * ) data . data ;
2008-08-03 23:23:13 +02:00
2009-02-04 18:31:24 +01:00
if ( e - > version ! = ENTRY_VERSION ) {
pa_log_debug ( " Version of database entry for stream %s doesn't match our version. Probably due to upgrade, ignoring. " , name ) ;
2008-08-03 23:23:13 +02:00
goto fail ;
}
2009-02-04 18:31:24 +01:00
if ( ! memchr ( e - > device , 0 , sizeof ( e - > device ) ) ) {
pa_log_warn ( " Database contains entry for stream %s with missing NUL byte in device name " , name ) ;
2008-08-04 19:00:43 +02:00
goto fail ;
}
2009-01-27 23:39:39 +01:00
if ( e - > device_valid & & ! pa_namereg_is_valid_name ( e - > device ) ) {
pa_log_warn ( " Invalid device name stored in database for stream %s " , name ) ;
2008-08-04 19:00:43 +02:00
goto fail ;
}
2009-04-13 22:50:24 +02:00
if ( e - > volume_valid & & ! pa_channel_map_valid ( & e - > channel_map ) ) {
2009-02-04 18:31:24 +01:00
pa_log_warn ( " Invalid channel map stored in database for stream %s " , name ) ;
goto fail ;
}
2009-04-13 22:50:24 +02:00
if ( e - > volume_valid & & ( ! pa_cvolume_valid ( & e - > volume ) | | ! pa_cvolume_compatible_with_channel_map ( & e - > volume , & e - > channel_map ) ) ) {
2009-01-27 23:39:39 +01:00
pa_log_warn ( " Invalid volume stored in database for stream %s " , name ) ;
2008-08-03 23:23:13 +02:00
goto fail ;
}
return e ;
fail :
2009-05-14 01:24:26 +02:00
pa_datum_free ( & data ) ;
2008-08-03 23:23:13 +02:00
return NULL ;
}
2008-08-04 19:01:13 +02:00
static void trigger_save ( struct userdata * u ) {
struct timeval tv ;
pa_native_connection * c ;
uint32_t idx ;
for ( c = pa_idxset_first ( u - > subscribed , & idx ) ; c ; c = pa_idxset_next ( u - > subscribed , & idx ) ) {
pa_tagstruct * t ;
t = pa_tagstruct_new ( NULL , 0 ) ;
pa_tagstruct_putu32 ( t , PA_COMMAND_EXTENSION ) ;
pa_tagstruct_putu32 ( t , 0 ) ;
pa_tagstruct_putu32 ( t , u - > module - > index ) ;
pa_tagstruct_puts ( t , u - > module - > name ) ;
pa_tagstruct_putu32 ( t , SUBCOMMAND_EVENT ) ;
pa_pstream_send_tagstruct ( pa_native_connection_get_pstream ( c ) , t ) ;
}
if ( u - > save_time_event )
return ;
pa_gettimeofday ( & tv ) ;
tv . tv_sec + = SAVE_INTERVAL ;
u - > save_time_event = u - > core - > mainloop - > time_new ( u - > core - > mainloop , & tv , save_time_callback , u ) ;
}
2009-01-27 23:39:39 +01:00
static pa_bool_t entries_equal ( const struct entry * a , const struct entry * b ) {
pa_cvolume t ;
pa_assert ( a ) ;
pa_assert ( b ) ;
if ( a - > device_valid ! = b - > device_valid | |
( a - > device_valid & & strncmp ( a - > device , b - > device , sizeof ( a - > device ) ) ) )
return FALSE ;
if ( a - > muted_valid ! = b - > muted_valid | |
2009-02-04 18:32:15 +01:00
( a - > muted_valid & & ( a - > muted ! = b - > muted ) ) )
2009-01-27 23:39:39 +01:00
return FALSE ;
2009-04-13 22:50:24 +02:00
t = b - > volume ;
if ( a - > volume_valid ! = b - > volume_valid | |
( a - > volume_valid & & ! pa_cvolume_equal ( pa_cvolume_remap ( & t , & b - > channel_map , & a - > channel_map ) , & a - > volume ) ) )
2009-01-27 23:39:39 +01:00
return FALSE ;
return TRUE ;
}
2008-08-03 23:23:13 +02:00
static void subscribe_callback ( pa_core * c , pa_subscription_event_type_t t , uint32_t idx , void * userdata ) {
struct userdata * u = userdata ;
struct entry entry , * old ;
char * name ;
2009-05-14 01:24:26 +02:00
pa_datum key , data ;
2008-08-03 23:23:13 +02:00
pa_assert ( c ) ;
pa_assert ( u ) ;
if ( t ! = ( PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_NEW ) & &
t ! = ( PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_CHANGE ) & &
t ! = ( PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT | PA_SUBSCRIPTION_EVENT_NEW ) & &
t ! = ( PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT | PA_SUBSCRIPTION_EVENT_CHANGE ) )
return ;
2009-05-14 01:24:26 +02:00
pa_zero ( entry ) ;
2009-02-04 18:31:24 +01:00
entry . version = ENTRY_VERSION ;
2008-08-04 19:01:13 +02:00
2008-08-03 23:23:13 +02:00
if ( ( t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK ) = = PA_SUBSCRIPTION_EVENT_SINK_INPUT ) {
pa_sink_input * sink_input ;
if ( ! ( sink_input = pa_idxset_get_by_index ( c - > sink_inputs , idx ) ) )
return ;
if ( ! ( name = get_name ( sink_input - > proplist , " sink-input " ) ) )
return ;
2009-04-13 22:50:24 +02:00
if ( ( old = read_entry ( u , name ) ) )
entry = * old ;
2009-03-20 13:51:08 +01:00
2009-04-13 22:50:24 +02:00
if ( sink_input - > save_volume ) {
entry . channel_map = sink_input - > channel_map ;
pa_sink_input_get_volume ( sink_input , & entry . volume , FALSE ) ;
entry . volume_valid = TRUE ;
}
2009-01-27 23:39:39 +01:00
2009-04-13 22:50:24 +02:00
if ( sink_input - > save_muted ) {
entry . muted = pa_sink_input_get_mute ( sink_input ) ;
entry . muted_valid = TRUE ;
}
2009-01-27 23:39:39 +01:00
2009-04-13 22:50:24 +02:00
if ( sink_input - > save_sink ) {
pa_strlcpy ( entry . device , sink_input - > sink - > name , sizeof ( entry . device ) ) ;
entry . device_valid = TRUE ;
}
2008-08-03 23:23:13 +02:00
} else {
pa_source_output * source_output ;
pa_assert ( ( t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK ) = = PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT ) ;
if ( ! ( source_output = pa_idxset_get_by_index ( c - > source_outputs , idx ) ) )
return ;
if ( ! ( name = get_name ( source_output - > proplist , " source-output " ) ) )
return ;
2009-04-13 22:50:24 +02:00
if ( ( old = read_entry ( u , name ) ) )
entry = * old ;
2009-01-27 23:39:39 +01:00
2009-04-13 22:50:24 +02:00
if ( source_output - > save_source ) {
pa_strlcpy ( entry . device , source_output - > source - > name , sizeof ( entry . device ) ) ;
entry . device_valid = source_output - > save_source ;
}
2008-08-03 23:23:13 +02:00
}
2009-04-13 22:50:24 +02:00
if ( old ) {
2008-08-03 23:23:13 +02:00
2009-01-27 23:39:39 +01:00
if ( entries_equal ( old , & entry ) ) {
2008-08-03 23:23:13 +02:00
pa_xfree ( old ) ;
pa_xfree ( name ) ;
return ;
}
pa_xfree ( old ) ;
}
2009-05-14 01:24:26 +02:00
key . data = name ;
key . size = strlen ( name ) ;
2008-08-03 23:23:13 +02:00
2009-05-14 01:24:26 +02:00
data . data = & entry ;
data . size = sizeof ( entry ) ;
2008-08-03 23:23:13 +02:00
pa_log_info ( " Storing volume/mute/device for stream %s. " , name ) ;
2009-05-14 01:24:26 +02:00
pa_database_set ( u - > database , & key , & data , TRUE ) ;
2008-08-03 23:23:13 +02:00
pa_xfree ( name ) ;
2008-08-04 19:01:13 +02:00
trigger_save ( u ) ;
2008-08-03 23:23:13 +02:00
}
static pa_hook_result_t sink_input_new_hook_callback ( pa_core * c , pa_sink_input_new_data * new_data , struct userdata * u ) {
char * name ;
struct entry * e ;
pa_assert ( new_data ) ;
2009-01-27 23:39:39 +01:00
if ( ! u - > restore_device )
return PA_HOOK_OK ;
2008-08-03 23:23:13 +02:00
if ( ! ( name = get_name ( new_data - > proplist , " sink-input " ) ) )
return PA_HOOK_OK ;
if ( ( e = read_entry ( u , name ) ) ) {
2009-01-27 23:39:39 +01:00
if ( e - > device_valid ) {
2009-06-18 00:59:33 +02:00
pa_sink * s ;
2008-08-03 23:23:13 +02:00
2009-01-27 23:39:39 +01:00
if ( ( s = pa_namereg_get ( c , e - > device , PA_NAMEREG_SINK ) ) ) {
if ( ! new_data - > sink ) {
pa_log_info ( " Restoring device for stream %s. " , name ) ;
new_data - > sink = s ;
2009-06-18 00:56:46 +02:00
new_data - > save_sink = FALSE ;
2009-01-27 23:39:39 +01:00
} else
2009-06-18 00:59:33 +02:00
pa_log_info ( " Not restoring device for stream %s, because already set. " , name ) ;
2009-01-27 23:39:39 +01:00
}
2008-08-03 23:23:13 +02:00
}
pa_xfree ( e ) ;
}
pa_xfree ( name ) ;
return PA_HOOK_OK ;
}
static pa_hook_result_t sink_input_fixate_hook_callback ( pa_core * c , pa_sink_input_new_data * new_data , struct userdata * u ) {
char * name ;
struct entry * e ;
pa_assert ( new_data ) ;
2009-01-27 23:39:39 +01:00
if ( ! u - > restore_volume & & ! u - > restore_muted )
return PA_HOOK_OK ;
2008-08-03 23:23:13 +02:00
if ( ! ( name = get_name ( new_data - > proplist , " sink-input " ) ) )
return PA_HOOK_OK ;
if ( ( e = read_entry ( u , name ) ) ) {
2009-04-13 22:50:24 +02:00
if ( u - > restore_volume & & e - > volume_valid ) {
2008-08-18 17:49:47 +02:00
2009-02-04 18:34:08 +01:00
if ( ! new_data - > volume_is_set ) {
2009-01-27 23:39:39 +01:00
pa_cvolume v ;
2009-04-13 22:50:24 +02:00
pa_log_info ( " Restoring volume for sink input %s. " , name ) ;
2009-06-18 00:59:33 +02:00
2009-04-13 22:50:24 +02:00
v = e - > volume ;
pa_cvolume_remap ( & v , & e - > channel_map , & new_data - > channel_map ) ;
pa_sink_input_new_data_set_volume ( new_data , & v ) ;
new_data - > volume_is_absolute = FALSE ;
new_data - > save_volume = FALSE ;
2008-08-18 17:49:47 +02:00
} else
pa_log_debug ( " Not restoring volume for sink input %s, because already set. " , name ) ;
2008-08-03 23:23:13 +02:00
}
2009-01-27 23:39:39 +01:00
if ( u - > restore_muted & & e - > muted_valid ) {
2009-04-13 22:50:24 +02:00
2008-08-18 17:49:47 +02:00
if ( ! new_data - > muted_is_set ) {
pa_log_info ( " Restoring mute state for sink input %s. " , name ) ;
pa_sink_input_new_data_set_muted ( new_data , e - > muted ) ;
2009-06-18 00:56:46 +02:00
new_data - > save_muted = FALSE ;
2008-08-18 17:49:47 +02:00
} else
pa_log_debug ( " Not restoring mute state for sink input %s, because already set. " , name ) ;
2008-08-03 23:23:13 +02:00
}
pa_xfree ( e ) ;
}
pa_xfree ( name ) ;
return PA_HOOK_OK ;
}
static pa_hook_result_t source_output_new_hook_callback ( pa_core * c , pa_source_output_new_data * new_data , struct userdata * u ) {
char * name ;
struct entry * e ;
pa_assert ( new_data ) ;
2009-01-27 23:39:39 +01:00
if ( ! u - > restore_device )
return PA_HOOK_OK ;
if ( new_data - > direct_on_input )
return PA_HOOK_OK ;
2008-08-03 23:23:13 +02:00
if ( ! ( name = get_name ( new_data - > proplist , " source-output " ) ) )
return PA_HOOK_OK ;
if ( ( e = read_entry ( u , name ) ) ) {
pa_source * s ;
2009-01-27 23:39:39 +01:00
if ( e - > device_valid ) {
if ( ( s = pa_namereg_get ( c , e - > device , PA_NAMEREG_SOURCE ) ) ) {
if ( ! new_data - > source ) {
pa_log_info ( " Restoring device for stream %s. " , name ) ;
new_data - > source = s ;
2009-06-18 00:56:46 +02:00
new_data - > save_source = FALSE ;
2009-01-27 23:39:39 +01:00
} else
pa_log_info ( " Not restoring device for stream %s, because already set " , name ) ;
}
2008-08-03 23:23:13 +02:00
}
pa_xfree ( e ) ;
}
pa_xfree ( name ) ;
return PA_HOOK_OK ;
}
2008-08-04 19:01:13 +02:00
# define EXT_VERSION 1
static void apply_entry ( struct userdata * u , const char * name , struct entry * e ) {
pa_sink_input * si ;
pa_source_output * so ;
uint32_t idx ;
pa_assert ( u ) ;
pa_assert ( name ) ;
pa_assert ( e ) ;
for ( si = pa_idxset_first ( u - > core - > sink_inputs , & idx ) ; si ; si = pa_idxset_next ( u - > core - > sink_inputs , & idx ) ) {
char * n ;
pa_sink * s ;
2008-08-15 14:37:26 +02:00
if ( ! ( n = get_name ( si - > proplist , " sink-input " ) ) )
2008-08-04 19:01:13 +02:00
continue ;
2009-03-20 13:51:08 +01:00
if ( ! pa_streq ( name , n ) ) {
2008-08-04 19:01:13 +02:00
pa_xfree ( n ) ;
continue ;
}
2009-03-20 13:51:08 +01:00
pa_xfree ( n ) ;
2008-08-04 19:01:13 +02:00
2009-04-13 22:50:24 +02:00
if ( u - > restore_volume & & e - > volume_valid ) {
2009-01-27 23:39:39 +01:00
pa_cvolume v ;
2009-04-13 22:50:24 +02:00
v = e - > volume ;
pa_log_info ( " Restoring volume for sink input %s. " , name ) ;
pa_sink_input_set_volume ( si , pa_cvolume_remap ( & v , & e - > channel_map , & si - > channel_map ) , FALSE , FALSE ) ;
2008-08-04 19:01:13 +02:00
}
2009-04-13 22:50:24 +02:00
if ( u - > restore_muted & & e - > muted_valid ) {
2008-08-04 19:01:13 +02:00
pa_log_info ( " Restoring mute state for sink input %s. " , name ) ;
2009-06-18 00:56:46 +02:00
pa_sink_input_set_mute ( si , e - > muted , FALSE ) ;
2008-08-04 19:01:13 +02:00
}
if ( u - > restore_device & &
2009-01-27 23:39:39 +01:00
e - > device_valid & &
( s = pa_namereg_get ( u - > core , e - > device , PA_NAMEREG_SINK ) ) ) {
2008-08-04 19:01:13 +02:00
pa_log_info ( " Restoring device for stream %s. " , name ) ;
2009-06-18 00:56:46 +02:00
pa_sink_input_move_to ( si , s , FALSE ) ;
2008-08-04 19:01:13 +02:00
}
}
for ( so = pa_idxset_first ( u - > core - > source_outputs , & idx ) ; so ; so = pa_idxset_next ( u - > core - > source_outputs , & idx ) ) {
char * n ;
pa_source * s ;
if ( ! ( n = get_name ( so - > proplist , " source-output " ) ) )
continue ;
2009-03-20 13:51:08 +01:00
if ( ! pa_streq ( name , n ) ) {
2008-08-04 19:01:13 +02:00
pa_xfree ( n ) ;
continue ;
}
2009-03-20 13:51:08 +01:00
pa_xfree ( n ) ;
2008-08-04 19:01:13 +02:00
if ( u - > restore_device & &
2009-01-27 23:39:39 +01:00
e - > device_valid & &
2009-01-15 20:07:13 +01:00
( s = pa_namereg_get ( u - > core , e - > device , PA_NAMEREG_SOURCE ) ) ) {
2008-08-04 19:01:13 +02:00
pa_log_info ( " Restoring device for stream %s. " , name ) ;
2009-06-18 00:56:46 +02:00
pa_source_output_move_to ( so , s , FALSE ) ;
2008-08-04 19:01:13 +02:00
}
}
}
2008-08-05 19:03:11 +02:00
#if 0
static void dump_database ( struct userdata * u ) {
2009-05-14 01:24:26 +02:00
pa_datum key ;
pa_bool_t done ;
2008-08-05 19:03:11 +02:00
2009-05-14 01:24:26 +02:00
done = ! pa_database_first ( u - > database , & key , NULL ) ;
while ( ! done ) {
pa_datum next_key ;
2008-08-05 19:03:11 +02:00
struct entry * e ;
char * name ;
2009-05-14 01:24:26 +02:00
done = ! pa_database_next ( u - > database , & key , & next_key , NULL ) ;
2008-08-05 19:03:11 +02:00
2009-05-14 01:24:26 +02:00
name = pa_xstrndup ( key . data , key . size ) ;
pa_datum_free ( & key ) ;
2008-08-05 19:03:11 +02:00
if ( ( e = read_entry ( u , name ) ) ) {
char t [ 256 ] ;
pa_log ( " name=%s " , name ) ;
2009-04-13 22:50:24 +02:00
pa_log ( " device=%s %s " , e - > device , pa_yes_no ( e - > device_valid ) ) ;
2008-08-05 19:03:11 +02:00
pa_log ( " channel_map=%s " , pa_channel_map_snprint ( t , sizeof ( t ) , & e - > channel_map ) ) ;
2009-04-13 22:50:24 +02:00
pa_log ( " volume=%s %s " , pa_cvolume_snprint ( t , sizeof ( t ) , & e - > volume ) , pa_yes_no ( e - > volume_valid ) ) ;
pa_log ( " mute=%s %s " , pa_yes_no ( e - > muted ) , pa_yes_no ( e - > volume_valid ) ) ;
2008-08-05 19:03:11 +02:00
pa_xfree ( e ) ;
}
pa_xfree ( name ) ;
key = next_key ;
}
}
# endif
2008-08-04 19:01:13 +02:00
static int extension_cb ( pa_native_protocol * p , pa_module * m , pa_native_connection * c , uint32_t tag , pa_tagstruct * t ) {
struct userdata * u ;
uint32_t command ;
2008-08-09 17:04:27 +02:00
pa_tagstruct * reply = NULL ;
2008-08-04 19:01:13 +02:00
pa_assert ( p ) ;
pa_assert ( m ) ;
pa_assert ( c ) ;
pa_assert ( t ) ;
u = m - > userdata ;
if ( pa_tagstruct_getu32 ( t , & command ) < 0 )
goto fail ;
reply = pa_tagstruct_new ( NULL , 0 ) ;
pa_tagstruct_putu32 ( reply , PA_COMMAND_REPLY ) ;
pa_tagstruct_putu32 ( reply , tag ) ;
switch ( command ) {
case SUBCOMMAND_TEST : {
if ( ! pa_tagstruct_eof ( t ) )
goto fail ;
pa_tagstruct_putu32 ( reply , EXT_VERSION ) ;
break ;
}
case SUBCOMMAND_READ : {
2009-05-14 01:24:26 +02:00
pa_datum key ;
pa_bool_t done ;
2008-08-04 19:01:13 +02:00
if ( ! pa_tagstruct_eof ( t ) )
goto fail ;
2009-05-14 01:24:26 +02:00
done = ! pa_database_first ( u - > database , & key , NULL ) ;
while ( ! done ) {
pa_datum next_key ;
2008-08-04 19:01:13 +02:00
struct entry * e ;
char * name ;
2009-05-14 01:24:26 +02:00
done = ! pa_database_next ( u - > database , & key , & next_key , NULL ) ;
2008-08-04 19:01:13 +02:00
2009-05-14 01:24:26 +02:00
name = pa_xstrndup ( key . data , key . size ) ;
pa_datum_free ( & key ) ;
2008-08-04 19:01:13 +02:00
if ( ( e = read_entry ( u , name ) ) ) {
2009-01-27 23:39:39 +01:00
pa_cvolume r ;
2009-02-04 18:34:08 +01:00
pa_channel_map cm ;
2009-01-27 23:39:39 +01:00
2008-08-04 19:01:13 +02:00
pa_tagstruct_puts ( reply , name ) ;
2009-04-13 22:50:24 +02:00
pa_tagstruct_put_channel_map ( reply , e - > volume_valid ? & e - > channel_map : pa_channel_map_init ( & cm ) ) ;
pa_tagstruct_put_cvolume ( reply , e - > volume_valid ? & e - > volume : pa_cvolume_init ( & r ) ) ;
2009-01-27 23:39:39 +01:00
pa_tagstruct_puts ( reply , e - > device_valid ? e - > device : NULL ) ;
pa_tagstruct_put_boolean ( reply , e - > muted_valid ? e - > muted : FALSE ) ;
2008-08-04 19:01:13 +02:00
pa_xfree ( e ) ;
}
pa_xfree ( name ) ;
key = next_key ;
}
break ;
}
case SUBCOMMAND_WRITE : {
uint32_t mode ;
2008-08-15 14:38:18 +02:00
pa_bool_t apply_immediately = FALSE ;
2008-08-04 19:01:13 +02:00
if ( pa_tagstruct_getu32 ( t , & mode ) < 0 | |
pa_tagstruct_get_boolean ( t , & apply_immediately ) < 0 )
goto fail ;
if ( mode ! = PA_UPDATE_MERGE & &
mode ! = PA_UPDATE_REPLACE & &
mode ! = PA_UPDATE_SET )
goto fail ;
if ( mode = = PA_UPDATE_SET )
2009-05-14 01:24:26 +02:00
pa_database_clear ( u - > database ) ;
2008-08-04 19:01:13 +02:00
while ( ! pa_tagstruct_eof ( t ) ) {
const char * name , * device ;
pa_bool_t muted ;
struct entry entry ;
2009-05-14 01:24:26 +02:00
pa_datum key , data ;
2008-08-04 19:01:13 +02:00
2009-05-14 01:24:26 +02:00
pa_zero ( entry ) ;
2009-02-04 18:31:24 +01:00
entry . version = ENTRY_VERSION ;
2008-08-04 19:01:13 +02:00
if ( pa_tagstruct_gets ( t , & name ) < 0 | |
pa_tagstruct_get_channel_map ( t , & entry . channel_map ) | |
2009-04-13 22:50:24 +02:00
pa_tagstruct_get_cvolume ( t , & entry . volume ) < 0 | |
2008-08-04 19:01:13 +02:00
pa_tagstruct_gets ( t , & device ) < 0 | |
pa_tagstruct_get_boolean ( t , & muted ) < 0 )
goto fail ;
2009-02-04 18:34:08 +01:00
if ( ! name | | ! * name )
2008-08-04 19:01:13 +02:00
goto fail ;
2009-04-13 22:50:24 +02:00
entry . volume_valid = entry . volume . channels > 0 ;
2009-02-04 18:34:08 +01:00
2009-04-13 22:50:24 +02:00
if ( entry . volume_valid )
if ( ! pa_cvolume_compatible_with_channel_map ( & entry . volume , & entry . channel_map ) )
2009-02-04 18:34:08 +01:00
goto fail ;
2008-08-04 19:01:13 +02:00
entry . muted = muted ;
2009-01-27 23:39:39 +01:00
entry . muted_valid = TRUE ;
if ( device )
pa_strlcpy ( entry . device , device , sizeof ( entry . device ) ) ;
entry . device_valid = ! ! entry . device [ 0 ] ;
if ( entry . device_valid & &
! pa_namereg_is_valid_name ( entry . device ) )
goto fail ;
2008-08-04 19:01:13 +02:00
2009-05-14 01:24:26 +02:00
key . data = ( char * ) name ;
key . size = strlen ( name ) ;
2008-08-04 19:01:13 +02:00
2009-05-14 01:24:26 +02:00
data . data = & entry ;
data . size = sizeof ( entry ) ;
2008-08-04 19:01:13 +02:00
2009-05-14 01:24:26 +02:00
if ( pa_database_set ( u - > database , & key , & data , mode = = PA_UPDATE_REPLACE ) = = 0 )
2008-08-04 19:01:13 +02:00
if ( apply_immediately )
apply_entry ( u , name , & entry ) ;
}
trigger_save ( u ) ;
break ;
}
case SUBCOMMAND_DELETE :
while ( ! pa_tagstruct_eof ( t ) ) {
const char * name ;
2009-05-14 01:24:26 +02:00
pa_datum key ;
2008-08-04 19:01:13 +02:00
if ( pa_tagstruct_gets ( t , & name ) < 0 )
goto fail ;
2009-05-14 01:24:26 +02:00
key . data = ( char * ) name ;
key . size = strlen ( name ) ;
2008-08-04 19:01:13 +02:00
2009-05-14 01:24:26 +02:00
pa_database_unset ( u - > database , & key ) ;
2008-08-04 19:01:13 +02:00
}
trigger_save ( u ) ;
break ;
case SUBCOMMAND_SUBSCRIBE : {
pa_bool_t enabled ;
if ( pa_tagstruct_get_boolean ( t , & enabled ) < 0 | |
! pa_tagstruct_eof ( t ) )
goto fail ;
if ( enabled )
pa_idxset_put ( u - > subscribed , c , NULL ) ;
else
pa_idxset_remove_by_data ( u - > subscribed , c , NULL ) ;
break ;
}
default :
goto fail ;
}
pa_pstream_send_tagstruct ( pa_native_connection_get_pstream ( c ) , reply ) ;
return 0 ;
fail :
if ( reply )
pa_tagstruct_free ( reply ) ;
return - 1 ;
}
static pa_hook_result_t connection_unlink_hook_cb ( pa_native_protocol * p , pa_native_connection * c , struct userdata * u ) {
pa_assert ( p ) ;
pa_assert ( c ) ;
pa_assert ( u ) ;
pa_idxset_remove_by_data ( u - > subscribed , c , NULL ) ;
return PA_HOOK_OK ;
}
2008-08-03 23:23:13 +02:00
int pa__init ( pa_module * m ) {
pa_modargs * ma = NULL ;
struct userdata * u ;
2009-05-14 01:24:26 +02:00
char * fname ;
2008-08-03 23:23:13 +02:00
pa_sink_input * si ;
pa_source_output * so ;
uint32_t idx ;
pa_bool_t restore_device = TRUE , restore_volume = TRUE , restore_muted = TRUE ;
pa_assert ( m ) ;
if ( ! ( ma = pa_modargs_new ( m - > argument , valid_modargs ) ) ) {
pa_log ( " Failed to parse module arguments " ) ;
goto fail ;
}
if ( pa_modargs_get_value_boolean ( ma , " restore_device " , & restore_device ) < 0 | |
pa_modargs_get_value_boolean ( ma , " restore_volume " , & restore_volume ) < 0 | |
pa_modargs_get_value_boolean ( ma , " restore_muted " , & restore_muted ) < 0 ) {
pa_log ( " restore_device=, restore_volume= and restore_muted= expect boolean arguments " ) ;
goto fail ;
}
if ( ! restore_muted & & ! restore_volume & & ! restore_device )
pa_log_warn ( " Neither restoring volume, nor restoring muted, nor restoring device enabled! " ) ;
2009-06-18 00:59:33 +02:00
m - > userdata = u = pa_xnew0 ( struct userdata , 1 ) ;
2008-08-03 23:23:13 +02:00
u - > core = m - > core ;
2008-08-04 19:00:43 +02:00
u - > module = m ;
2008-08-03 23:23:13 +02:00
u - > restore_device = restore_device ;
u - > restore_volume = restore_volume ;
u - > restore_muted = restore_muted ;
2008-08-04 19:01:13 +02:00
u - > subscribed = pa_idxset_new ( pa_idxset_trivial_hash_func , pa_idxset_trivial_compare_func ) ;
u - > protocol = pa_native_protocol_get ( m - > core ) ;
pa_native_protocol_install_ext ( u - > protocol , m , extension_cb ) ;
u - > connection_unlink_hook_slot = pa_hook_connect ( & pa_native_protocol_hooks ( u - > protocol ) [ PA_NATIVE_HOOK_CONNECTION_UNLINK ] , PA_HOOK_NORMAL , ( pa_hook_cb_t ) connection_unlink_hook_cb , u ) ;
2008-08-03 23:23:13 +02:00
u - > subscription = pa_subscription_new ( m - > core , PA_SUBSCRIPTION_MASK_SINK_INPUT | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT , subscribe_callback , u ) ;
if ( restore_device ) {
u - > sink_input_new_hook_slot = pa_hook_connect ( & m - > core - > hooks [ PA_CORE_HOOK_SINK_INPUT_NEW ] , PA_HOOK_EARLY , ( pa_hook_cb_t ) sink_input_new_hook_callback , u ) ;
u - > source_output_new_hook_slot = pa_hook_connect ( & m - > core - > hooks [ PA_CORE_HOOK_SOURCE_OUTPUT_NEW ] , PA_HOOK_EARLY , ( pa_hook_cb_t ) source_output_new_hook_callback , u ) ;
}
if ( restore_volume | | restore_muted )
u - > sink_input_fixate_hook_slot = pa_hook_connect ( & m - > core - > hooks [ PA_CORE_HOOK_SINK_INPUT_FIXATE ] , PA_HOOK_EARLY , ( pa_hook_cb_t ) sink_input_fixate_hook_callback , u ) ;
2009-06-18 00:59:33 +02:00
if ( ! ( fname = pa_state_path ( " stream-volumes " , TRUE ) ) )
2008-08-03 23:23:13 +02:00
goto fail ;
2009-05-14 01:24:26 +02:00
if ( ! ( u - > database = pa_database_open ( fname , TRUE ) ) ) {
pa_log ( " Failed to open volume database '%s': %s " , fname , pa_cstrerror ( errno ) ) ;
2008-08-03 23:23:13 +02:00
pa_xfree ( fname ) ;
goto fail ;
}
pa_log_info ( " Sucessfully opened database file '%s'. " , fname ) ;
pa_xfree ( fname ) ;
for ( si = pa_idxset_first ( m - > core - > sink_inputs , & idx ) ; si ; si = pa_idxset_next ( m - > core - > sink_inputs , & idx ) )
subscribe_callback ( m - > core , PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_NEW , si - > index , u ) ;
for ( so = pa_idxset_first ( m - > core - > source_outputs , & idx ) ; so ; so = pa_idxset_next ( m - > core - > source_outputs , & idx ) )
subscribe_callback ( m - > core , PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT | PA_SUBSCRIPTION_EVENT_NEW , so - > index , u ) ;
pa_modargs_free ( ma ) ;
return 0 ;
fail :
pa__done ( m ) ;
if ( ma )
pa_modargs_free ( ma ) ;
return - 1 ;
}
void pa__done ( pa_module * m ) {
struct userdata * u ;
pa_assert ( m ) ;
if ( ! ( u = m - > userdata ) )
return ;
if ( u - > subscription )
pa_subscription_free ( u - > subscription ) ;
if ( u - > sink_input_new_hook_slot )
pa_hook_slot_free ( u - > sink_input_new_hook_slot ) ;
if ( u - > sink_input_fixate_hook_slot )
pa_hook_slot_free ( u - > sink_input_fixate_hook_slot ) ;
if ( u - > source_output_new_hook_slot )
pa_hook_slot_free ( u - > source_output_new_hook_slot ) ;
2008-08-04 19:01:13 +02:00
if ( u - > connection_unlink_hook_slot )
pa_hook_slot_free ( u - > connection_unlink_hook_slot ) ;
2008-08-03 23:23:13 +02:00
if ( u - > save_time_event )
u - > core - > mainloop - > time_free ( u - > save_time_event ) ;
2009-05-14 01:24:26 +02:00
if ( u - > database )
pa_database_close ( u - > database ) ;
2008-08-03 23:23:13 +02:00
2008-08-04 19:01:13 +02:00
if ( u - > protocol ) {
pa_native_protocol_remove_ext ( u - > protocol , m ) ;
pa_native_protocol_unref ( u - > protocol ) ;
}
if ( u - > subscribed )
pa_idxset_free ( u - > subscribed , NULL , NULL ) ;
2008-08-03 23:23:13 +02:00
pa_xfree ( u ) ;
}