2017-08-05 15:20:02 +10:00
/****************************************
2017-10-03 13:37:32 +11:00
*
2017-08-05 15:20:02 +10:00
* theShell - Desktop Environment
* Copyright ( C ) 2017 Victor Tran
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program 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 General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
2017-10-03 13:37:32 +11:00
*
2017-08-05 15:20:02 +10:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2017-01-12 01:00:27 +11:00
# include "audiomanager.h"
AudioManager : : AudioManager ( QObject * parent ) : QObject ( parent )
{
pulseLoopApi = pa_glib_mainloop_get_api ( pa_glib_mainloop_new ( NULL ) ) ;
pa_proplist * propList = pa_proplist_new ( ) ;
pa_proplist_sets ( propList , PA_PROP_APPLICATION_NAME , " theShell " ) ;
pa_proplist_sets ( propList , PA_PROP_APPLICATION_ID , " org.thesuite.theshell " ) ;
pa_proplist_sets ( propList , PA_PROP_APPLICATION_ICON_NAME , " theshell " ) ;
pulseContext = pa_context_new_with_proplist ( pulseLoopApi , NULL , propList ) ;
pa_proplist_free ( propList ) ;
pa_context_set_state_callback ( pulseContext , & AudioManager : : pulseStateChanged , this ) ;
int connected = pa_context_connect ( pulseContext , NULL , PA_CONTEXT_NOFLAGS , NULL ) ;
if ( connected < 0 ) {
pulseAvailable = false ;
} else {
pulseAvailable = true ;
}
}
void AudioManager : : changeVolume ( int volume ) {
2017-04-24 15:49:10 +10:00
if ( currentQuietMode ! = mute ) {
pa_volume_t avgVol = pa_cvolume_avg ( & defaultSinkVolume ) ;
int onePercent = ( PA_VOLUME_NORM - PA_VOLUME_MUTED ) / 100 ;
pa_volume_t newVol = avgVol + ( onePercent * volume ) ;
if ( newVol < PA_VOLUME_MUTED ) newVol = PA_VOLUME_MUTED ;
if ( newVol > PA_VOLUME_MAX ) newVol = PA_VOLUME_MAX ;
2017-10-03 13:49:40 +11:00
if ( ! settings . value ( " sound/volumeOverdrive " , true ) . toBool ( ) & & newVol > PA_VOLUME_NORM ) {
newVol = PA_VOLUME_NORM ;
2017-10-03 13:37:32 +11:00
}
2017-04-24 15:49:10 +10:00
if ( volume < 0 ) {
if ( newVol > avgVol ) {
newVol = PA_VOLUME_MUTED ;
}
2017-01-15 00:38:39 +11:00
}
2017-04-24 15:49:10 +10:00
if ( avgVol < PA_VOLUME_NORM ) {
if ( newVol > PA_VOLUME_NORM ) {
newVol = PA_VOLUME_NORM ;
}
2017-01-15 00:38:39 +11:00
}
2017-04-24 15:49:10 +10:00
pa_cvolume newCVol = defaultSinkVolume ;
for ( int i = 0 ; i < newCVol . channels ; i + + ) {
newCVol . values [ i ] = newVol ;
}
pa_context_set_sink_mute_by_index ( pulseContext , defaultSinkIndex , false , NULL , NULL ) ;
pa_context_set_sink_volume_by_index ( pulseContext , defaultSinkIndex , & newCVol , NULL , NULL ) ;
2017-01-15 00:38:39 +11:00
}
2017-01-12 01:00:27 +11:00
}
void AudioManager : : setMasterVolume ( int volume ) {
2017-04-24 15:49:10 +10:00
if ( currentQuietMode ! = mute ) {
if ( pulseAvailable ) {
pa_volume_t setVol = PA_VOLUME_MUTED + ( ( ( float ) volume / 100 ) * ( float ) PA_VOLUME_NORM ) ;
pa_cvolume newVol = defaultSinkVolume ;
for ( int i = 0 ; i < newVol . channels ; i + + ) {
newVol . values [ i ] = setVol ;
}
pa_context_set_sink_volume_by_index ( pulseContext , defaultSinkIndex , & newVol , NULL , NULL ) ;
} else {
//Get Current Limits
QProcess * mixer = new QProcess ( this ) ;
mixer - > start ( " amixer " ) ;
mixer - > waitForFinished ( ) ;
QString output ( mixer - > readAll ( ) ) ;
2017-01-12 01:00:27 +11:00
2017-04-24 15:49:10 +10:00
bool readLine = false ;
int limit ;
for ( QString line : output . split ( " \n " ) ) {
if ( line . startsWith ( " " ) & & readLine ) {
if ( line . startsWith ( " Limits: " ) ) {
limit = line . split ( " " ) . last ( ) . toInt ( ) ;
}
2017-01-12 01:00:27 +11:00
} else {
2017-04-24 15:49:10 +10:00
if ( line . contains ( " 'Master' " ) ) {
readLine = true ;
} else {
readLine = false ;
}
2017-01-12 01:00:27 +11:00
}
}
2017-04-24 15:49:10 +10:00
mixer - > start ( " amixer set Master " + QString : : number ( limit * ( volume / ( float ) 100 ) ) + " on " ) ;
connect ( mixer , SIGNAL ( finished ( int ) ) , mixer , SLOT ( deleteLater ( ) ) ) ;
}
2017-01-12 01:00:27 +11:00
}
}
int AudioManager : : MasterVolume ( ) {
if ( pulseAvailable ) {
pa_volume_t avgVol = pa_cvolume_avg ( & defaultSinkVolume ) ;
int currentVol = ( ( float ) ( avgVol - PA_VOLUME_MUTED ) / ( float ) PA_VOLUME_NORM ) * 100 ;
return currentVol ;
} else {
//Get Current Volume
QProcess * mixer = new QProcess ( this ) ;
mixer - > start ( " amixer " ) ;
mixer - > waitForFinished ( ) ;
QString output ( mixer - > readAll ( ) ) ;
delete mixer ;
bool readLine = false ;
for ( QString line : output . split ( " \n " ) ) {
if ( line . startsWith ( " " ) & & readLine ) {
if ( line . startsWith ( " Front Left: " ) ) {
if ( line . contains ( " [off] " ) ) {
return 0 ;
} else {
QString percent = line . mid ( line . indexOf ( " \ [ " ) + 1 , 3 ) . remove ( " \ % " ) . remove ( " ] " ) ;
return percent . toInt ( ) ;
}
}
} else {
if ( line . contains ( " 'Master' " ) ) {
readLine = true ;
} else {
readLine = false ;
}
}
}
}
return 0 ;
}
void AudioManager : : pulseStateChanged ( pa_context * c , void * userdata ) {
AudioManager * currentManager = ( AudioManager * ) userdata ;
switch ( pa_context_get_state ( c ) ) {
case PA_CONTEXT_READY :
pa_context_set_subscribe_callback ( c , & AudioManager : : pulseSubscribe , currentManager ) ;
2017-01-12 16:47:49 +11:00
pa_context_subscribe ( c , ( pa_subscription_mask_t ) ( PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SINK_INPUT | PA_SUBSCRIPTION_MASK_CLIENT ) , NULL , userdata ) ;
2017-01-12 01:00:27 +11:00
pa_context_get_server_info ( c , & AudioManager : : pulseServerInfo , currentManager ) ;
pa_context_get_sink_info_list ( c , & AudioManager : : pulseGetSinks , currentManager ) ;
2017-01-12 16:47:49 +11:00
pa_context_get_source_info_list ( c , & AudioManager : : pulseGetSources , currentManager ) ;
pa_context_get_sink_input_info_list ( c , & AudioManager : : pulseGetInputSinks , currentManager ) ;
pa_context_get_client_info_list ( c , & AudioManager : : pulseGetClients , currentManager ) ;
2017-01-12 01:00:27 +11:00
}
}
void AudioManager : : pulseSubscribe ( pa_context * c , pa_subscription_event_type_t t , uint32_t index , void * userdata ) {
AudioManager * currentManager = ( AudioManager * ) userdata ;
switch ( t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK ) {
case PA_SUBSCRIPTION_EVENT_SINK :
pa_context_get_server_info ( c , & AudioManager : : pulseServerInfo , currentManager ) ;
pa_context_get_sink_info_list ( c , & AudioManager : : pulseGetSinks , currentManager ) ;
2017-01-12 16:47:49 +11:00
break ;
case PA_SUBSCRIPTION_EVENT_SINK_INPUT :
pa_context_get_sink_input_info_list ( c , & AudioManager : : pulseGetInputSinks , currentManager ) ;
break ;
case PA_SUBSCRIPTION_EVENT_SOURCE :
pa_context_get_server_info ( c , & AudioManager : : pulseServerInfo , currentManager ) ;
pa_context_get_source_info_list ( c , & AudioManager : : pulseGetSources , currentManager ) ;
break ;
case PA_SUBSCRIPTION_EVENT_CLIENT :
pa_context_get_client_info_list ( c , & AudioManager : : pulseGetClients , currentManager ) ;
2017-01-12 01:00:27 +11:00
}
}
void AudioManager : : pulseGetSinks ( pa_context * c , const pa_sink_info * i , int eol , void * userdata ) {
}
void AudioManager : : pulseServerInfo ( pa_context * c , const pa_server_info * i , void * userdata ) {
pa_context_get_sink_info_by_name ( c , i - > default_sink_name , & AudioManager : : pulseGetDefaultSink , userdata ) ;
2017-01-12 16:47:49 +11:00
2017-01-12 01:00:27 +11:00
}
void AudioManager : : pulseGetDefaultSink ( pa_context * c , const pa_sink_info * i , int eol , void * userdata ) {
if ( eol = = 0 ) {
AudioManager * currentManager = ( AudioManager * ) userdata ;
currentManager - > defaultSinkIndex = i - > index ;
currentManager - > defaultSinkVolume = i - > volume ;
}
}
2017-01-12 16:47:49 +11:00
void AudioManager : : pulseGetSources ( pa_context * c , const pa_source_info * i , int eol , void * userdata ) {
AudioManager * currentManager = ( AudioManager * ) userdata ;
if ( eol = = 0 ) {
}
}
2017-04-24 15:49:10 +10:00
void AudioManager : : attenuateStreams ( ) {
2017-01-15 00:38:39 +11:00
if ( pulseAvailable ) {
2017-04-24 15:49:10 +10:00
if ( attenuateRequests = = 0 ) {
2017-01-17 21:47:16 +11:00
for ( int source : originalStreamVolumes . keys ( ) ) {
//Set volume to 50% of original
pa_cvolume originalVolume = originalStreamVolumes . value ( source ) ;
for ( int i = 0 ; i < originalVolume . channels ; i + + ) {
2017-01-22 00:08:04 +11:00
if ( originalVolume . values [ i ] > PA_VOLUME_NORM ) {
originalVolume . values [ i ] = PA_VOLUME_NORM / 2 ;
} else {
originalVolume . values [ i ] / = 2 ;
}
2017-01-17 21:47:16 +11:00
}
pa_context_set_sink_input_volume ( pulseContext , source , & originalVolume , NULL , NULL ) ;
2017-01-15 00:38:39 +11:00
}
2017-04-24 15:49:10 +10:00
attenuateMode = true ;
2017-01-12 16:47:49 +11:00
}
2017-04-24 15:49:10 +10:00
attenuateRequests + + ;
2017-01-12 16:47:49 +11:00
}
}
2017-01-15 23:28:41 +11:00
void AudioManager : : silenceStreams ( ) {
if ( pulseAvailable ) {
for ( int source : originalStreamVolumes . keys ( ) ) {
//Set volume to 10% of original
pa_cvolume originalVolume = originalStreamVolumes . value ( source ) ;
for ( int i = 0 ; i < originalVolume . channels ; i + + ) {
originalVolume . values [ i ] / = 10 ;
}
pa_context_set_sink_input_volume ( pulseContext , source , & originalVolume , NULL , NULL ) ;
}
2017-04-24 15:49:10 +10:00
attenuateMode = true ;
2017-01-15 23:28:41 +11:00
}
}
void AudioManager : : restoreStreams ( bool immediate ) {
2017-01-15 00:38:39 +11:00
if ( pulseAvailable ) {
2017-04-24 15:49:10 +10:00
attenuateRequests - - ;
if ( attenuateRequests = = 0 ) {
2017-01-17 21:47:16 +11:00
//Sounds much better if we delay by a second
QTimer : : singleShot ( immediate ? 0 : 1000 , [ = ] ( ) {
for ( int source : originalStreamVolumes . keys ( ) ) {
//Restore volume of each stream
pa_cvolume originalVolume = originalStreamVolumes . value ( source ) ;
if ( originalVolume . channels > 0 ) {
tVariantAnimation * anim = new tVariantAnimation ;
anim - > setStartValue ( originalVolume . values [ 0 ] / 2 ) ;
2017-01-22 00:08:04 +11:00
pa_volume_t endValue = originalVolume . values [ 0 ] ;
if ( endValue > PA_VOLUME_NORM ) {
endValue = PA_VOLUME_NORM ;
}
anim - > setEndValue ( endValue ) ;
2017-01-17 21:47:16 +11:00
anim - > setDuration ( 500 ) ;
connect ( anim , & tVariantAnimation : : valueChanged , [ = ] ( QVariant value ) {
pa_cvolume originalVolume = originalStreamVolumes . value ( source ) ;
for ( int i = 0 ; i < originalVolume . channels ; i + + ) {
originalVolume . values [ i ] = value . toUInt ( ) ;
}
pa_context_set_sink_input_volume ( pulseContext , source , & originalVolume , NULL , NULL ) ;
} ) ;
connect ( anim , & tVariantAnimation : : finished , [ = ] ( ) {
2017-04-24 15:49:10 +10:00
attenuateMode = false ;
2017-01-17 21:47:16 +11:00
} ) ;
connect ( anim , SIGNAL ( finished ( ) ) , anim , SLOT ( deleteLater ( ) ) ) ;
anim - > start ( ) ;
}
2017-01-15 00:38:39 +11:00
2017-01-17 21:47:16 +11:00
}
} ) ;
2017-04-24 15:49:10 +10:00
} else if ( attenuateRequests = = - 1 ) {
attenuateRequests = 0 ;
2017-01-17 21:47:16 +11:00
}
2017-01-12 16:47:49 +11:00
}
}
void AudioManager : : pulseGetInputSinks ( pa_context * c , const pa_sink_input_info * i , int eol , void * userdata ) {
AudioManager * currentManager = ( AudioManager * ) userdata ;
if ( eol = = 0 ) {
if ( ! currentManager - > tsClientIndices . contains ( i - > client ) ) {
2017-04-24 15:49:10 +10:00
if ( currentManager - > attenuateMode ) {
2017-01-12 16:47:49 +11:00
if ( currentManager - > originalStreamVolumes . contains ( i - > index ) ) {
pa_cvolume originalVolume = i - > volume ;
for ( int i = 0 ; i < originalVolume . channels ; i + + ) {
originalVolume . values [ i ] = originalVolume . values [ i ] * 2 ;
}
currentManager - > originalStreamVolumes . insert ( i - > index , originalVolume ) ;
} else {
currentManager - > originalStreamVolumes . insert ( i - > index , i - > volume ) ;
}
} else {
currentManager - > originalStreamVolumes . insert ( i - > index , i - > volume ) ;
}
}
}
}
void AudioManager : : pulseGetClients ( pa_context * c , const pa_client_info * i , int eol , void * userdata ) {
AudioManager * currentManager = ( AudioManager * ) userdata ;
if ( eol = = 0 ) {
2017-01-15 23:28:41 +11:00
if ( QString : : fromUtf8 ( i - > name ) . toLower ( ) . contains ( " theshell " ) | | QString : : fromUtf8 ( i - > name ) . toLower ( ) . contains ( " qtpulseaudio " ) ) {
2017-01-12 16:47:49 +11:00
currentManager - > newTsClientIndices . append ( i - > index ) ;
}
} else {
currentManager - > tsClientIndices = currentManager - > newTsClientIndices ;
currentManager - > newTsClientIndices . clear ( ) ;
}
}
2017-04-24 15:49:10 +10:00
void AudioManager : : setQuietMode ( quietMode mode ) {
if ( mode ! = currentQuietMode ) {
quietMode oldQuietMode = this - > currentQuietMode ;
if ( mode = = mute ) {
2017-04-24 22:50:54 +10:00
pa_context_set_sink_mute_by_index ( pulseContext , defaultSinkIndex , 1 , NULL , NULL ) ;
2017-04-24 15:49:10 +10:00
}
this - > currentQuietMode = mode ;
emit QuietModeChanged ( mode ) ;
if ( oldQuietMode = = mute ) {
2017-04-24 22:50:54 +10:00
pa_context_set_sink_mute_by_index ( pulseContext , defaultSinkIndex , 0 , NULL , NULL ) ;
2017-04-24 15:49:10 +10:00
}
}
}
AudioManager : : quietMode AudioManager : : QuietMode ( ) {
return this - > currentQuietMode ;
}
QString AudioManager : : getCurrentQuietModeDescription ( ) {
switch ( this - > currentQuietMode ) {
case none :
return " " ;
case notifications :
2017-05-02 17:12:21 +10:00
return tr ( " Ignores any notifications from all apps. Normal sounds will still be played, and timers and reminders will still notify you, however, they won't play sounds. " ) ;
2017-04-24 15:49:10 +10:00
case mute :
2017-05-04 09:51:25 +10:00
return tr ( " Completely turns off all sounds and notifications from all apps. Not even timers or reminders will notify you. " ) ;
2017-04-24 15:49:10 +10:00
}
}