@ -16,45 +16,362 @@
# include "kscreenintegration.h"
# include "workspace.h"
# include <QFile>
# include <QJsonArray>
# include <QJsonDocument>
# include <QJsonObject>
# include <QOrientationReading>
namespace KWin
{
std : : pair < OutputConfiguration , QVector < Output * > > OutputConfigurationStore : : queryConfig ( const QVector < Output * > & outputs , bool isLidClosed )
OutputConfigurationStore : : OutputConfigurationStore ( )
{
load ( ) ;
}
OutputConfigurationStore : : ~ OutputConfigurationStore ( )
{
save ( ) ;
}
std : : optional < std : : tuple < OutputConfiguration , QVector < Output * > , OutputConfigurationStore : : ConfigType > > OutputConfigurationStore : : queryConfig ( const QVector < Output * > & outputs , bool isLidClosed , QOrientationReading * orientation , bool isTabletMode )
{
QVector < Output * > relevantOutputs ;
std : : copy_if ( outputs . begin ( ) , outputs . end ( ) , std : : back_inserter ( relevantOutputs ) , [ ] ( Output * output ) {
return ! output - > isNonDesktop ( ) & & ! output - > isPlaceholder ( ) ;
} ) ;
if ( relevantOutputs . isEmpty ( ) ) {
return std : : nullopt ;
}
if ( const auto opt = findSetup ( relevantOutputs , isLidClosed ) ) {
const auto & [ setup , outputStates ] = * opt ;
auto [ config , order ] = setupToConfig ( setup , outputStates ) ;
applyOrientationReading ( config , relevantOutputs , orientation , isTabletMode ) ;
storeConfig ( relevantOutputs , isLidClosed , config , order ) ;
return std : : make_tuple ( config , order , ConfigType : : Preexisting ) ;
}
if ( auto kscreenConfig = KScreenIntegration : : readOutputConfig ( relevantOutputs , KScreenIntegration : : connectedOutputsHash ( relevantOutputs , isLidClosed ) ) ) {
auto & [ config , order ] = * kscreenConfig ;
applyOrientationReading ( config , relevantOutputs , orientation , isTabletMode ) ;
storeConfig ( relevantOutputs , isLidClosed , config , order ) ;
return std : : make_tuple ( config , order , ConfigType : : Preexisting ) ;
}
auto [ config , order ] = generateConfig ( relevantOutputs , isLidClosed ) ;
applyOrientationReading ( config , relevantOutputs , orientation , isTabletMode ) ;
storeConfig ( relevantOutputs , isLidClosed , config , order ) ;
return std : : make_tuple ( config , order , ConfigType : : Generated ) ;
}
void OutputConfigurationStore : : applyOrientationReading ( OutputConfiguration & config , const QVector < Output * > & outputs , QOrientationReading * orientation , bool isTabletMode )
{
if ( ! isAutoRotateActive ( outputs , isTabletMode ) | | ! orientation | | orientation - > orientation ( ) = = QOrientationReading : : Orientation : : Undefined ) {
return ;
}
const auto output = std : : find_if ( outputs . begin ( ) , outputs . end ( ) , [ & config ] ( Output * output ) {
return output - > isInternal ( ) & & config . changeSet ( output ) - > enabled . value_or ( output - > isEnabled ( ) ) ;
} ) ;
if ( output = = outputs . end ( ) ) {
return ;
}
// TODO move other outputs to matching positions
const auto changeset = config . changeSet ( * output ) ;
switch ( orientation - > orientation ( ) ) {
case QOrientationReading : : Orientation : : TopUp :
changeset - > transform = OutputTransform : : Kind : : Normal ;
return ;
case QOrientationReading : : Orientation : : TopDown :
changeset - > transform = OutputTransform : : Kind : : Rotated180 ;
return ;
case QOrientationReading : : Orientation : : LeftUp :
changeset - > transform = OutputTransform : : Kind : : Rotated90 ;
return ;
case QOrientationReading : : Orientation : : RightUp :
changeset - > transform = OutputTransform : : Kind : : Rotated270 ;
return ;
case QOrientationReading : : Orientation : : FaceUp :
case QOrientationReading : : Orientation : : FaceDown :
case QOrientationReading : : Orientation : : Undefined :
return ;
}
}
std : : optional < std : : pair < OutputConfigurationStore : : Setup * , std : : unordered_map < Output * , size_t > > > OutputConfigurationStore : : findSetup ( const QVector < Output * > & outputs , bool lidClosed )
{
std : : unordered_map < Output * , size_t > outputStates ;
for ( Output * output : outputs ) {
if ( auto opt = findOutput ( output , outputs ) ) {
outputStates [ output ] = * opt ;
} else {
return std : : nullopt ;
}
}
const auto setup = std : : find_if ( m_setups . begin ( ) , m_setups . end ( ) , [ lidClosed , & outputStates ] ( const auto & setup ) {
if ( setup . lidClosed ! = lidClosed | | size_t ( setup . outputs . size ( ) ) ! = outputStates . size ( ) ) {
return false ;
}
return std : : all_of ( outputStates . begin ( ) , outputStates . end ( ) , [ & setup ] ( const auto & outputIt ) {
return std : : any_of ( setup . outputs . begin ( ) , setup . outputs . end ( ) , [ & outputIt ] ( const auto & outputInfo ) {
return outputInfo . outputIndex = = outputIt . second ;
} ) ;
} ) ;
} ) ;
if ( setup = = m_setups . end ( ) ) {
return std : : nullopt ;
} else {
return std : : make_pair ( & ( * setup ) , outputStates ) ;
}
}
std : : optional < size_t > OutputConfigurationStore : : findOutput ( Output * output , const QVector < Output * > & allOutputs ) const
{
// TODO to make use of isLidClosed, move config writing into KWin
// Currently, the interactions of KWin's config generation on lid close with KScreen's config writing
// causes settings changes that shouldn't be happening
isLidClosed = false ;
const auto kscreenConfig = KScreenIntegration : : readOutputConfig ( outputs , KScreenIntegration : : connectedOutputsHash ( outputs , isLidClosed ) ) ;
if ( kscreenConfig ) {
return kscreenConfig . value ( ) ;
const bool hasDuplicate = std : : any_of ( allOutputs . begin ( ) , allOutputs . end ( ) , [ output ] ( Output * otherOutput ) {
return otherOutput ! = output & & otherOutput - > edid ( ) . identifier ( ) = = output - > edid ( ) . identifier ( ) ;
} ) ;
const auto it = std : : find_if ( m_outputs . begin ( ) , m_outputs . end ( ) , [ hasDuplicate , output ] ( const auto & outputState ) {
return outputState . edidIdentifier = = output - > edid ( ) . identifier ( )
& & ( ! hasDuplicate | | outputState . connectorName = = output - > name ( ) ) ;
} ) ;
if ( it ! = m_outputs . end ( ) ) {
return std : : distance ( m_outputs . begin ( ) , it ) ;
} else {
// no config file atm -> generate a new one
return generateConfig ( outputs , isLidClosed ) ;
return std : : nullopt ;
}
}
std : : pair < OutputConfiguration , QVector < Output * > > OutputConfigurationStore : : generateConfig ( const QVector < Output * > & outputs , bool isLidClosed ) const
void OutputConfigurationStore : : storeConfig ( const QVector < Output * > & allOutputs , bool isLidClosed , const OutputConfiguration & config , const QVector < Output * > & outputOrder )
{
QVector < Output * > relevantOutputs ;
std : : copy_if ( allOutputs . begin ( ) , allOutputs . end ( ) , std : : back_inserter ( relevantOutputs ) , [ ] ( Output * output ) {
return ! output - > isNonDesktop ( ) & & ! output - > isPlaceholder ( ) ;
} ) ;
if ( relevantOutputs . isEmpty ( ) ) {
return ;
}
const auto opt = findSetup ( relevantOutputs , isLidClosed ) ;
Setup * setup = nullptr ;
if ( opt ) {
setup = opt - > first ;
} else {
m_setups . push_back ( Setup { } ) ;
setup = & m_setups . back ( ) ;
setup - > lidClosed = isLidClosed ;
}
for ( Output * output : relevantOutputs ) {
auto outputIndex = findOutput ( output , outputOrder ) ;
if ( ! outputIndex ) {
m_outputs . push_back ( OutputState { } ) ;
outputIndex = m_outputs . size ( ) - 1 ;
}
auto outputIt = std : : find_if ( setup - > outputs . begin ( ) , setup - > outputs . end ( ) , [ outputIndex ] ( const auto & output ) {
return output . outputIndex = = outputIndex ;
} ) ;
if ( outputIt = = setup - > outputs . end ( ) ) {
setup - > outputs . push_back ( SetupState { } ) ;
outputIt = setup - > outputs . end ( ) - 1 ;
}
if ( const auto changeSet = config . constChangeSet ( output ) ) {
std : : shared_ptr < OutputMode > mode = changeSet - > mode . value_or ( output - > currentMode ( ) ) . lock ( ) ;
if ( ! mode ) {
mode = output - > currentMode ( ) ;
}
m_outputs [ * outputIndex ] = OutputState {
. edidIdentifier = output - > edid ( ) . identifier ( ) ,
. connectorName = output - > name ( ) ,
. mode = ModeData {
. size = mode - > size ( ) ,
. refreshRate = mode - > refreshRate ( ) ,
} ,
. scale = changeSet - > scale . value_or ( output - > scale ( ) ) ,
. transform = changeSet - > transform . value_or ( output - > transform ( ) ) ,
. overscan = changeSet - > overscan . value_or ( output - > overscan ( ) ) ,
. rgbRange = changeSet - > rgbRange . value_or ( output - > rgbRange ( ) ) ,
. vrrPolicy = changeSet - > vrrPolicy . value_or ( output - > vrrPolicy ( ) ) ,
. highDynamicRange = changeSet - > highDynamicRange . value_or ( output - > highDynamicRange ( ) ) ,
. sdrBrightness = changeSet - > sdrBrightness . value_or ( output - > sdrBrightness ( ) ) ,
. wideColorGamut = changeSet - > wideColorGamut . value_or ( output - > wideColorGamut ( ) ) ,
. autoRotation = changeSet - > autoRotationPolicy . value_or ( output - > autoRotationPolicy ( ) ) ,
} ;
* outputIt = SetupState {
. outputIndex = * outputIndex ,
. position = changeSet - > pos . value_or ( output - > geometry ( ) . topLeft ( ) ) ,
. enabled = changeSet - > enabled . value_or ( output - > isEnabled ( ) ) ,
. priority = int ( outputOrder . indexOf ( output ) ) ,
} ;
} else {
const auto mode = output - > currentMode ( ) ;
m_outputs [ * outputIndex ] = OutputState {
. edidIdentifier = output - > edid ( ) . identifier ( ) ,
. connectorName = output - > name ( ) ,
. mode = ModeData {
. size = mode - > size ( ) ,
. refreshRate = mode - > refreshRate ( ) ,
} ,
. scale = output - > scale ( ) ,
. transform = output - > transform ( ) ,
. overscan = output - > overscan ( ) ,
. rgbRange = output - > rgbRange ( ) ,
. vrrPolicy = output - > vrrPolicy ( ) ,
. highDynamicRange = output - > highDynamicRange ( ) ,
. sdrBrightness = output - > sdrBrightness ( ) ,
. wideColorGamut = output - > wideColorGamut ( ) ,
. autoRotation = output - > autoRotationPolicy ( ) ,
} ;
* outputIt = SetupState {
. outputIndex = * outputIndex ,
. position = output - > geometry ( ) . topLeft ( ) ,
. enabled = output - > isEnabled ( ) ,
. priority = int ( outputOrder . indexOf ( output ) ) ,
} ;
}
}
save ( ) ;
}
std : : pair < OutputConfiguration , QVector < Output * > > OutputConfigurationStore : : setupToConfig ( Setup * setup , const std : : unordered_map < Output * , size_t > & outputMap ) const
{
OutputConfiguration ret ;
QVector < std : : pair < Output * , size_t > > priorities ;
for ( const auto & [ output , outputIndex ] : outputMap ) {
const OutputState & state = m_outputs [ outputIndex ] ;
const auto & setupState = * std : : find_if ( setup - > outputs . begin ( ) , setup - > outputs . end ( ) , [ outputIndex = outputIndex ] ( const auto & state ) {
return state . outputIndex = = outputIndex ;
} ) ;
const auto modes = output - > modes ( ) ;
const auto mode = std : : find_if ( modes . begin ( ) , modes . end ( ) , [ & state ] ( const auto & mode ) {
return state . mode
& & mode - > size ( ) = = state . mode - > size
& & mode - > refreshRate ( ) = = state . mode - > refreshRate ;
} ) ;
* ret . changeSet ( output ) = OutputChangeSet {
. mode = mode = = modes . end ( ) ? std : : nullopt : std : : optional ( * mode ) ,
. enabled = setupState . enabled ,
. pos = setupState . position ,
. scale = state . scale ,
. transform = state . transform ,
. overscan = state . overscan ,
. rgbRange = state . rgbRange ,
. vrrPolicy = state . vrrPolicy ,
. highDynamicRange = state . highDynamicRange ,
. sdrBrightness = state . sdrBrightness ,
. wideColorGamut = state . wideColorGamut ,
. autoRotationPolicy = state . autoRotation ,
} ;
if ( setupState . enabled ) {
priorities . push_back ( std : : make_pair ( output , setupState . priority ) ) ;
}
}
std : : sort ( priorities . begin ( ) , priorities . end ( ) , [ ] ( const auto & left , const auto & right ) {
return left . second < right . second ;
} ) ;
QVector < Output * > order ;
std : : transform ( priorities . begin ( ) , priorities . end ( ) , std : : back_inserter ( order ) , [ ] ( const auto & pair ) {
return pair . first ;
} ) ;
return std : : make_pair ( ret , order ) ;
}
std : : optional < std : : pair < OutputConfiguration , QVector < Output * > > > OutputConfigurationStore : : generateLidClosedConfig ( const QVector < Output * > & outputs )
{
const auto internalIt = std : : find_if ( outputs . begin ( ) , outputs . end ( ) , [ ] ( Output * output ) {
return output - > isInternal ( ) ;
} ) ;
if ( internalIt = = outputs . end ( ) ) {
return std : : nullopt ;
}
const auto setup = findSetup ( outputs , false ) ;
if ( ! setup ) {
return std : : nullopt ;
}
Output * const internalOutput = * internalIt ;
auto [ config , order ] = setupToConfig ( setup - > first , setup - > second ) ;
auto internalChangeset = config . changeSet ( internalOutput ) ;
internalChangeset - > enabled = false ;
order . removeOne ( internalOutput ) ;
const bool anyEnabled = std : : any_of ( outputs . begin ( ) , outputs . end ( ) , [ & config = config ] ( Output * output ) {
return config . changeSet ( output ) - > enabled . value_or ( output - > isEnabled ( ) ) ;
} ) ;
if ( ! anyEnabled ) {
return std : : nullopt ;
}
const auto getSize = [ ] ( OutputChangeSet * changeset , Output * output ) {
auto mode = changeset - > mode ? changeset - > mode - > lock ( ) : nullptr ;
if ( ! mode ) {
mode = output - > currentMode ( ) ;
}
const auto scale = changeset - > scale . value_or ( output - > scale ( ) ) ;
return QSize ( std : : ceil ( mode - > size ( ) . width ( ) / scale ) , std : : ceil ( mode - > size ( ) . height ( ) / scale ) ) ;
} ;
const QPoint internalPos = internalChangeset - > pos . value_or ( internalOutput - > geometry ( ) . topLeft ( ) ) ;
const QSize internalSize = getSize ( internalChangeset . get ( ) , internalOutput ) ;
for ( Output * otherOutput : outputs ) {
auto changeset = config . changeSet ( otherOutput ) ;
QPoint otherPos = changeset - > pos . value_or ( otherOutput - > geometry ( ) . topLeft ( ) ) ;
if ( otherPos . x ( ) > = internalPos . x ( ) + internalSize . width ( ) ) {
otherPos . rx ( ) - = std : : floor ( internalSize . width ( ) ) ;
}
if ( otherPos . y ( ) > = internalPos . y ( ) + internalSize . height ( ) ) {
otherPos . ry ( ) - = std : : floor ( internalSize . height ( ) ) ;
}
// make sure this doesn't make outputs overlap, which is neither supported nor expected by users
const QSize otherSize = getSize ( changeset . get ( ) , otherOutput ) ;
const bool overlap = std : : any_of ( outputs . begin ( ) , outputs . end ( ) , [ & , & config = config ] ( Output * output ) {
if ( otherOutput = = output ) {
return false ;
}
const auto changeset = config . changeSet ( output ) ;
const QPoint pos = changeset - > pos . value_or ( output - > geometry ( ) . topLeft ( ) ) ;
return QRect ( pos , otherSize ) . intersects ( QRect ( otherPos , getSize ( changeset . get ( ) , output ) ) ) ;
} ) ;
if ( ! overlap ) {
changeset - > pos = otherPos ;
}
}
return std : : make_pair ( config , order ) ;
}
std : : pair < OutputConfiguration , QVector < Output * > > OutputConfigurationStore : : generateConfig ( const QVector < Output * > & outputs , bool isLidClosed )
{
if ( isLidClosed ) {
if ( const auto closedConfig = generateLidClosedConfig ( outputs ) ) {
return * closedConfig ;
}
}
OutputConfiguration ret ;
QVector < Output * > outputOrder ;
QPoint pos ( 0 , 0 ) ;
for ( const auto output : outputs ) {
const auto mode = chooseMode ( output ) ;
const double scale = chooseScale ( output , mode . get ( ) ) ;
const auto outputIndex = findOutput ( output , outputs ) ;
const bool enable = ! isLidClosed | | ! output - > isInternal ( ) ;
* ret . changeSet ( output ) = {
const OutputState existingData = outputIndex ? m_outputs [ * outputIndex ] : OutputState { } ;
const auto modes = output - > modes ( ) ;
const auto modeIt = std : : find_if ( modes . begin ( ) , modes . end ( ) , [ & existingData ] ( const auto & mode ) {
return existingData . mode
& & mode - > size ( ) = = existingData . mode - > size
& & mode - > refreshRate ( ) = = existingData . mode - > refreshRate ;
} ) ;
const auto mode = modeIt = = modes . end ( ) ? output - > currentMode ( ) : * modeIt ;
const auto changeset = ret . changeSet ( output ) ;
* changeset = {
. mode = mode ,
. enabled = enable ,
. pos = pos ,
. scale = scale ,
. transform = output - > panelOrientation ( ) ,
. overscan = 0 ,
. rgbRange = Output : : RgbRange : : Automatic ,
. vrrPolicy = RenderLoop : : VrrPolicy : : Automatic ,
. scale = existingData . scale . value_or ( chooseScale ( output , mode . get ( ) ) ) ,
. transform = existingData . transform . value_or ( output - > panelOrientation ( ) ) ,
. overscan = existingData . overscan . value_or ( 0 ) ,
. rgbRange = existingData . rgbRange . value_or ( Output : : RgbRange : : Automatic ) ,
. vrrPolicy = existingData . vrrPolicy . value_or ( RenderLoop : : VrrPolicy : : Automatic ) ,
. highDynamicRange = existingData . highDynamicRange . value_or ( false ) ,
. sdrBrightness = existingData . sdrBrightness . value_or ( 200 ) ,
. wideColorGamut = existingData . wideColorGamut . value_or ( false ) ,
. autoRotationPolicy = existingData . autoRotation . value_or ( Output : : AutoRotationPolicy : : InTabletMode ) ,
} ;
pos . setX ( pos . x ( ) + mode - > size ( ) . width ( ) / scale ) ;
if ( enable ) {
pos . setX ( std : : ceil ( pos . x ( ) + changeset - > mode . value_or ( output - > currentMode ( ) ) . lock ( ) - > size ( ) . width ( ) / changeset - > scale . value_or ( output - > scale ( ) ) ) ) ;
outputOrder . push_back ( output ) ;
}
}
@ -150,4 +467,376 @@ double OutputConfigurationStore::targetDpi(Output *output) const
}
}
void OutputConfigurationStore : : load ( )
{
const QString jsonPath = QStandardPaths : : locate ( QStandardPaths : : ConfigLocation , QStringLiteral ( " kwinoutputconfig.json " ) ) ;
if ( jsonPath . isEmpty ( ) ) {
return ;
}
QFile f ( jsonPath ) ;
if ( ! f . open ( QIODevice : : ReadOnly ) ) {
qCWarning ( KWIN_CORE ) < < " Could not open file " < < jsonPath ;
return ;
}
QJsonParseError error ;
const auto doc = QJsonDocument : : fromJson ( f . readAll ( ) , & error ) ;
if ( error . error ! = QJsonParseError : : NoError ) {
qCWarning ( KWIN_CORE ) < < " Failed to parse " < < jsonPath < < error . errorString ( ) ;
return ;
}
const auto array = doc . array ( ) ;
std : : vector < QJsonObject > objects ;
std : : transform ( array . begin ( ) , array . end ( ) , std : : back_inserter ( objects ) , [ ] ( const auto & json ) {
return json . toObject ( ) ;
} ) ;
const auto outputsIt = std : : find_if ( objects . begin ( ) , objects . end ( ) , [ ] ( const auto & obj ) {
return obj [ " name " ] . toString ( ) = = " outputs " & & obj [ " data " ] . isArray ( ) ;
} ) ;
const auto setupsIt = std : : find_if ( objects . begin ( ) , objects . end ( ) , [ ] ( const auto & obj ) {
return obj [ " name " ] . toString ( ) = = " setups " & & obj [ " data " ] . isArray ( ) ;
} ) ;
if ( outputsIt = = objects . end ( ) | | setupsIt = = objects . end ( ) ) {
return ;
}
const auto outputs = ( * outputsIt ) [ " data " ] . toArray ( ) ;
std : : vector < std : : optional < OutputState > > outputDatas ;
for ( const auto & output : outputs ) {
const auto data = output . toObject ( ) ;
OutputState state ;
bool hasIdentifier = false ;
if ( const auto it = data . find ( " edidIdentifier " ) ; it ! = data . end ( ) ) {
if ( const auto str = it - > toString ( ) ; ! str . isEmpty ( ) ) {
state . edidIdentifier = str ;
hasIdentifier = true ;
}
}
if ( const auto it = data . find ( " connectorName " ) ; it ! = data . end ( ) ) {
if ( const auto str = it - > toString ( ) ; ! str . isEmpty ( ) ) {
state . connectorName = str ;
hasIdentifier = true ;
}
}
if ( ! hasIdentifier ) {
// without an identifier the settings are useless
// we still have to push something into the list so that the indices stay correct
outputDatas . push_back ( std : : nullopt ) ;
continue ;
}
if ( const auto it = data . find ( " mode " ) ; it ! = data . end ( ) ) {
const auto obj = it - > toObject ( ) ;
const int width = obj [ " width " ] . toInt ( 0 ) ;
const int height = obj [ " height " ] . toInt ( 0 ) ;
const int refreshRate = obj [ " refreshRate " ] . toInt ( 0 ) ;
if ( width > 0 & & height > 0 & & refreshRate > 0 ) {
state . mode = ModeData {
. size = QSize ( width , height ) ,
. refreshRate = uint32_t ( refreshRate ) ,
} ;
}
}
if ( const auto it = data . find ( " scale " ) ; it ! = data . end ( ) ) {
const double scale = it - > toDouble ( 0 ) ;
if ( scale > 0 & & scale < = 3 ) {
state . scale = scale ;
}
}
if ( const auto it = data . find ( " transform " ) ; it ! = data . end ( ) ) {
const auto str = it - > toString ( ) ;
if ( str = = " Normal " ) {
state . transform = OutputTransform : : Kind : : Normal ;
} else if ( str = = " Rotated90 " ) {
state . transform = OutputTransform : : Kind : : Rotated90 ;
} else if ( str = = " Rotated180 " ) {
state . transform = OutputTransform : : Kind : : Rotated180 ;
} else if ( str = = " Rotated270 " ) {
state . transform = OutputTransform : : Kind : : Rotated270 ;
}
}
if ( const auto it = data . find ( " overscan " ) ; it ! = data . end ( ) ) {
const int overscan = it - > toInt ( - 1 ) ;
if ( overscan > = 0 & & overscan < = 100 ) {
state . overscan = overscan ;
}
}
if ( const auto it = data . find ( " rgbRange " ) ; it ! = data . end ( ) ) {
const auto str = it - > toString ( ) ;
if ( str = = " Automatic " ) {
state . rgbRange = Output : : RgbRange : : Automatic ;
} else if ( str = = " Limited " ) {
state . rgbRange = Output : : RgbRange : : Limited ;
} else if ( str = = " Full " ) {
state . rgbRange = Output : : RgbRange : : Full ;
}
}
if ( const auto it = data . find ( " vrrPolicy " ) ; it ! = data . end ( ) ) {
const auto str = it - > toString ( ) ;
if ( str = = " Never " ) {
state . vrrPolicy = RenderLoop : : VrrPolicy : : Never ;
} else if ( str = = " Automatic " ) {
state . vrrPolicy = RenderLoop : : VrrPolicy : : Automatic ;
} else if ( str = = " Always " ) {
state . vrrPolicy = RenderLoop : : VrrPolicy : : Always ;
}
}
if ( const auto it = data . find ( " highDynamicRange " ) ; it ! = data . end ( ) & & it - > isBool ( ) ) {
state . highDynamicRange = it - > toBool ( ) ;
}
if ( const auto it = data . find ( " sdrBrightness " ) ; it ! = data . end ( ) & & it - > isDouble ( ) ) {
state . sdrBrightness = it - > toInt ( 200 ) ;
}
if ( const auto it = data . find ( " wideColorGamut " ) ; it ! = data . end ( ) & & it - > isBool ( ) ) {
state . wideColorGamut = it - > toBool ( ) ;
}
if ( const auto it = data . find ( " autoRotation " ) ; it ! = data . end ( ) ) {
const auto str = it - > toString ( ) ;
if ( str = = " Never " ) {
state . autoRotation = Output : : AutoRotationPolicy : : Never ;
} else if ( str = = " InTabletMode " ) {
state . autoRotation = Output : : AutoRotationPolicy : : InTabletMode ;
} else if ( str = = " Always " ) {
state . autoRotation = Output : : AutoRotationPolicy : : Always ;
}
}
outputDatas . push_back ( state ) ;
}
const auto setups = ( * setupsIt ) [ " data " ] . toArray ( ) ;
for ( const auto & s : setups ) {
const auto data = s . toObject ( ) ;
const auto outputs = data [ " outputs " ] . toArray ( ) ;
Setup setup ;
bool fail = false ;
for ( const auto & output : outputs ) {
const auto outputData = output . toObject ( ) ;
SetupState state ;
if ( const auto it = outputData . find ( " enabled " ) ; it ! = outputData . end ( ) & & it - > isBool ( ) ) {
state . enabled = it - > toBool ( ) ;
} else {
fail = true ;
break ;
}
if ( const auto it = outputData . find ( " outputIndex " ) ; it ! = outputData . end ( ) ) {
const int index = it - > toInt ( - 1 ) ;
if ( index < = - 1 | | size_t ( index ) > = outputDatas . size ( ) ) {
fail = true ;
break ;
}
// the outputs must be unique
const bool unique = std : : none_of ( setup . outputs . begin ( ) , setup . outputs . end ( ) , [ & index ] ( const auto & output ) {
return output . outputIndex = = size_t ( index ) ;
} ) ;
if ( ! unique ) {
fail = true ;
break ;
}
state . outputIndex = index ;
}
if ( const auto it = outputData . find ( " position " ) ; it ! = outputData . end ( ) ) {
const auto obj = it - > toObject ( ) ;
const auto x = obj . find ( " x " ) ;
const auto y = obj . find ( " y " ) ;
if ( x = = obj . end ( ) | | ! x - > isDouble ( ) | | y = = obj . end ( ) | | ! y - > isDouble ( ) ) {
fail = true ;
break ;
}
state . position = QPoint ( x - > toInt ( 0 ) , y - > toInt ( 0 ) ) ;
} else {
fail = true ;
break ;
}
if ( const auto it = outputData . find ( " priority " ) ; it ! = outputData . end ( ) ) {
state . priority = it - > toInt ( - 1 ) ;
if ( state . priority < 0 & & state . enabled ) {
fail = true ;
break ;
}
}
setup . outputs . push_back ( state ) ;
}
if ( fail | | setup . outputs . empty ( ) ) {
continue ;
}
setup . lidClosed = data [ " lidClosed " ] . toBool ( false ) ;
// there must be only one setup that refers to a given set of outputs
const bool alreadyExists = std : : any_of ( m_setups . begin ( ) , m_setups . end ( ) , [ & setup ] ( const auto & other ) {
if ( setup . lidClosed ! = other . lidClosed | | setup . outputs . size ( ) ! = other . outputs . size ( ) ) {
return false ;
}
return std : : all_of ( setup . outputs . begin ( ) , setup . outputs . end ( ) , [ & other ] ( const auto & output ) {
return std : : any_of ( other . outputs . begin ( ) , other . outputs . end ( ) , [ & output ] ( const auto & otherOutput ) {
return output . outputIndex = = otherOutput . outputIndex ;
} ) ;
} ) ;
} ) ;
if ( alreadyExists ) {
continue ;
}
m_setups . push_back ( setup ) ;
}
// repair the outputs list in case it's broken
for ( size_t i = 0 ; i < outputDatas . size ( ) ; i + + ) {
if ( ! outputDatas [ i ] ) {
for ( auto setupIt = m_setups . begin ( ) ; setupIt ! = m_setups . end ( ) ; ) {
const bool broken = std : : any_of ( setupIt - > outputs . begin ( ) , setupIt - > outputs . end ( ) , [ i ] ( const auto & output ) {
return output . outputIndex = = i ;
} ) ;
if ( broken ) {
setupIt = m_setups . erase ( setupIt ) ;
continue ;
}
for ( auto & output : setupIt - > outputs ) {
if ( output . outputIndex > i ) {
output . outputIndex - - ;
}
}
setupIt + + ;
}
outputDatas . erase ( outputDatas . begin ( ) + i ) ;
}
}
for ( const auto & o : outputDatas ) {
Q_ASSERT ( o ) ;
m_outputs . push_back ( * o ) ;
}
}
void OutputConfigurationStore : : save ( )
{
QJsonDocument document ;
QJsonArray array ;
QJsonObject outputs ;
outputs [ " name " ] = " outputs " ;
QJsonArray outputsData ;
for ( const auto & output : m_outputs ) {
QJsonObject o ;
if ( output . edidIdentifier ) {
o [ " edidIdentifier " ] = * output . edidIdentifier ;
}
if ( output . connectorName ) {
o [ " connectorName " ] = * output . connectorName ;
}
if ( output . mode ) {
QJsonObject mode ;
mode [ " width " ] = output . mode - > size . width ( ) ;
mode [ " height " ] = output . mode - > size . height ( ) ;
mode [ " refreshRate " ] = int ( output . mode - > refreshRate ) ;
o [ " mode " ] = mode ;
}
if ( output . scale ) {
o [ " scale " ] = * output . scale ;
}
if ( output . transform = = OutputTransform : : Kind : : Normal ) {
o [ " transform " ] = " Normal " ;
} else if ( output . transform = = OutputTransform : : Kind : : Rotated90 ) {
o [ " transform " ] = " Rotated90 " ;
} else if ( output . transform = = OutputTransform : : Kind : : Rotated180 ) {
o [ " transform " ] = " Rotated180 " ;
} else if ( output . transform = = OutputTransform : : Kind : : Rotated270 ) {
o [ " transform " ] = " Rotated270 " ;
}
if ( output . overscan ) {
o [ " overscan " ] = int ( * output . overscan ) ;
}
if ( output . rgbRange = = Output : : RgbRange : : Automatic ) {
o [ " rgbRange " ] = " Automatic " ;
} else if ( output . rgbRange = = Output : : RgbRange : : Limited ) {
o [ " rgbRange " ] = " Limited " ;
} else if ( output . rgbRange = = Output : : RgbRange : : Full ) {
o [ " rgbRange " ] = " Full " ;
}
if ( output . vrrPolicy = = RenderLoop : : VrrPolicy : : Never ) {
o [ " vrrPolicy " ] = " Never " ;
} else if ( output . vrrPolicy = = RenderLoop : : VrrPolicy : : Automatic ) {
o [ " vrrPolicy " ] = " Automatic " ;
} else if ( output . vrrPolicy = = RenderLoop : : VrrPolicy : : Always ) {
o [ " vrrPolicy " ] = " Always " ;
}
if ( output . highDynamicRange ) {
o [ " highDynamicRange " ] = * output . highDynamicRange ;
}
if ( output . sdrBrightness ) {
o [ " sdrBrightness " ] = int ( * output . sdrBrightness ) ;
}
if ( output . wideColorGamut ) {
o [ " wideColorGamut " ] = * output . wideColorGamut ;
}
if ( output . autoRotation ) {
switch ( * output . autoRotation ) {
case Output : : AutoRotationPolicy : : Never :
o [ " autoRotation " ] = " Never " ;
break ;
case Output : : AutoRotationPolicy : : InTabletMode :
o [ " autoRotation " ] = " InTabletMode " ;
break ;
case Output : : AutoRotationPolicy : : Always :
o [ " autoRotation " ] = " Always " ;
break ;
}
}
outputsData . append ( o ) ;
}
outputs [ " data " ] = outputsData ;
array . append ( outputs ) ;
QJsonObject setups ;
setups [ " name " ] = " setups " ;
QJsonArray setupData ;
for ( const auto & setup : m_setups ) {
QJsonObject o ;
o [ " lidClosed " ] = setup . lidClosed ;
QJsonArray outputs ;
for ( ssize_t i = 0 ; i < setup . outputs . size ( ) ; i + + ) {
const auto & output = setup . outputs [ i ] ;
QJsonObject o ;
o [ " enabled " ] = output . enabled ;
o [ " outputIndex " ] = int ( output . outputIndex ) ;
o [ " priority " ] = output . priority ;
QJsonObject pos ;
pos [ " x " ] = output . position . x ( ) ;
pos [ " y " ] = output . position . y ( ) ;
o [ " position " ] = pos ;
outputs . append ( o ) ;
}
o [ " outputs " ] = outputs ;
setupData . append ( o ) ;
}
setups [ " data " ] = setupData ;
array . append ( setups ) ;
const QString path = QStandardPaths : : writableLocation ( QStandardPaths : : ConfigLocation ) + " /kwinoutputconfig.json " ;
QFile f ( path ) ;
if ( ! f . open ( QIODevice : : WriteOnly ) ) {
qCWarning ( KWIN_CORE , " Couldn't open output config file %s " , qPrintable ( path ) ) ;
return ;
}
document . setArray ( array ) ;
f . write ( document . toJson ( ) ) ;
f . flush ( ) ;
}
bool OutputConfigurationStore : : isAutoRotateActive ( const QVector < Output * > & outputs , bool isTabletMode ) const
{
const auto internalIt = std : : find_if ( outputs . begin ( ) , outputs . end ( ) , [ ] ( Output * output ) {
return output - > isInternal ( ) & & output - > isEnabled ( ) ;
} ) ;
if ( internalIt = = outputs . end ( ) ) {
return false ;
}
Output * internal = * internalIt ;
switch ( internal - > autoRotationPolicy ( ) ) {
case Output : : AutoRotationPolicy : : Never :
return false ;
case Output : : AutoRotationPolicy : : InTabletMode :
return isTabletMode ;
case Output : : AutoRotationPolicy : : Always :
return true ;
}
Q_UNREACHABLE ( ) ;
}
}