outputconfigurationstore: add new config system

Instead of an external service (like KScreen) storing and restoring output configurations,
with this commit KWin takes over that responsibility. This allows it to, among other things,
generate appropriate configs for new sets of outputs immediately, and take KWin-internal information
about outputs into account when generating them.

CCBUG: 474021
CCBUG: 469653
CCBUG: 466342
CCBUG: 470863
CCBUG: 466556
BUG: 466208
BUG: 455082
BUG: 457430
master
Xaver Hugl 1 year ago
parent 47edb14425
commit ae84480fbf

@ -56,6 +56,7 @@ find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
Quick
UiTools
Widgets
Sensors
)
find_package(Qt6Test ${QT_MIN_VERSION} CONFIG QUIET)

@ -144,6 +144,7 @@ void WaylandTestApplication::performStartup()
// try creating the Wayland Backend
createInput();
createVirtualInputDevices();
createTabletModeManager();
WaylandCompositor::create();
createWorkspace();

@ -204,6 +204,7 @@ target_link_libraries(kwin
Qt::DBus
Qt::Quick
Qt::Widgets
Qt::Sensors
Wayland::Server
KF6::I18n

@ -63,6 +63,10 @@ DrmOutput::DrmOutput(const std::shared_ptr<DrmConnector> &conn)
&& m_connector->edid() && m_connector->edid()->hdrMetadata() && m_connector->edid()->hdrMetadata()->supportsBT2020) {
capabilities |= Capability::WideColorGamut;
}
if (conn->isInternal()) {
// TODO only set this if an orientation sensor is available?
capabilities |= Capability::AutoRotation;
}
const Edid *edid = conn->edid();
@ -335,6 +339,7 @@ void DrmOutput::applyQueuedChanges(const std::shared_ptr<OutputChangeSet> &props
next.highDynamicRange = props->highDynamicRange.value_or(m_state.highDynamicRange);
next.sdrBrightness = props->sdrBrightness.value_or(m_state.sdrBrightness);
next.wideColorGamut = props->wideColorGamut.value_or(m_state.wideColorGamut);
next.autoRotatePolicy = props->autoRotationPolicy.value_or(m_state.autoRotatePolicy);
if (m_state.highDynamicRange != next.highDynamicRange || m_state.sdrBrightness != next.sdrBrightness || m_state.wideColorGamut != next.wideColorGamut) {
m_renderLoop->scheduleRepaint();
}

@ -328,6 +328,7 @@ void Output::applyChanges(const OutputConfiguration &config)
next.position = props->pos.value_or(m_state.position);
next.scale = props->scale.value_or(m_state.scale);
next.rgbRange = props->rgbRange.value_or(m_state.rgbRange);
next.autoRotatePolicy = props->autoRotationPolicy.value_or(m_state.autoRotatePolicy);
setState(next);
setVrrPolicy(props->vrrPolicy.value_or(vrrPolicy()));
@ -401,6 +402,9 @@ void Output::setState(const State &state)
if (oldState.wideColorGamut != state.wideColorGamut) {
Q_EMIT wideColorGamutChanged();
}
if (oldState.autoRotatePolicy != state.autoRotatePolicy) {
Q_EMIT autoRotationPolicyChanged();
}
if (oldState.enabled != state.enabled) {
Q_EMIT enabledChanged();
}
@ -544,6 +548,11 @@ uint32_t Output::sdrBrightness() const
return m_state.sdrBrightness;
}
Output::AutoRotationPolicy Output::autoRotationPolicy() const
{
return m_state.autoRotatePolicy;
}
bool Output::updateCursorLayer()
{
return false;

@ -133,6 +133,7 @@ public:
RgbRange = 1 << 3,
HighDynamicRange = 1 << 4,
WideColorGamut = 1 << 5,
AutoRotation = 1 << 6,
};
Q_DECLARE_FLAGS(Capabilities, Capability)
@ -153,6 +154,13 @@ public:
};
Q_ENUM(RgbRange)
enum class AutoRotationPolicy {
Never = 0,
InTabletMode,
Always
};
Q_ENUM(AutoRotationPolicy);
explicit Output(QObject *parent = nullptr);
~Output() override;
@ -299,6 +307,7 @@ public:
bool wideColorGamut() const;
bool highDynamicRange() const;
uint32_t sdrBrightness() const;
AutoRotationPolicy autoRotationPolicy() const;
virtual bool setGammaRamp(const std::shared_ptr<ColorTransformation> &transformation);
virtual bool setChannelFactors(const QVector3D &rgb);
@ -361,6 +370,7 @@ Q_SIGNALS:
void wideColorGamutChanged();
void sdrBrightnessChanged();
void highDynamicRangeChanged();
void autoRotationPolicyChanged();
protected:
struct Information
@ -395,6 +405,7 @@ protected:
bool wideColorGamut = false;
bool highDynamicRange = false;
uint32_t sdrBrightness = 200;
AutoRotationPolicy autoRotatePolicy = AutoRotationPolicy::InTabletMode;
};
void setInformation(const Information &information);

@ -32,6 +32,7 @@ public:
std::optional<bool> highDynamicRange;
std::optional<uint32_t> sdrBrightness;
std::optional<bool> wideColorGamut;
std::optional<Output::AutoRotationPolicy> autoRotationPolicy;
};
class KWIN_EXPORT OutputConfiguration

@ -290,6 +290,11 @@ void Application::createTabletModeManager()
m_tabletModeManager = std::make_unique<TabletModeManager>();
}
TabletModeManager *Application::tabletModeManager() const
{
return m_tabletModeManager.get();
}
void Application::installNativeX11EventFilter()
{
installNativeEventFilter(m_eventFilter.get());
@ -378,6 +383,8 @@ void Application::setXwaylandScale(qreal scale)
{
if (scale != m_xwaylandScale) {
m_xwaylandScale = scale;
// rerun the fonts kcm init that does the appropriate xrdb call with the new settings
QProcess::startDetached("kcminit", {"kcm_fonts", "kcm_style"});
Q_EMIT xwaylandScaleChanged();
}
}

@ -285,6 +285,7 @@ public:
#if KWIN_BUILD_SCREENLOCKER
ScreenLockerWatcher *screenLockerWatcher() const;
#endif
TabletModeManager *tabletModeManager() const;
/**
* Starts an interactive window selection process.

@ -177,10 +177,6 @@ void ApplicationWayland::refreshSettings(const KConfigGroup &group, const QByteA
KDesktopFile file(group.readPathEntry("InputMethod", QString()));
kwinApp()->inputMethod()->setInputMethodCommand(file.desktopGroup().readEntry("Exec", QString()));
}
if (m_startXWayland && group.name() == "Xwayland" && names.contains("Scale")) {
setXwaylandScale(group.readEntry("Scale", 1.0));
}
}
void ApplicationWayland::startSession()

@ -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();
}
}

@ -8,25 +8,89 @@
*/
#pragma once
#include "core/output.h"
#include <QPoint>
#include <QSize>
#include <QVector>
#include <memory>
#include <optional>
#include <tuple>
#include <unordered_map>
class QOrientationReading;
namespace KWin
{
class OutputConfiguration;
class Output;
class OutputMode;
class OutputConfigurationStore
{
public:
std::pair<OutputConfiguration, QVector<Output *>> queryConfig(const QVector<Output *> &outputs, bool isLidClosed);
OutputConfigurationStore();
~OutputConfigurationStore();
enum class ConfigType {
Preexisting,
Generated,
};
std::optional<std::tuple<OutputConfiguration, QVector<Output *>, ConfigType>> queryConfig(const QVector<Output *> &outputs, bool isLidClosed, QOrientationReading *orientation, bool isTabletMode);
void storeConfig(const QVector<Output *> &allOutputs, bool isLidClosed, const OutputConfiguration &config, const QVector<Output *> &outputOrder);
bool isAutoRotateActive(const QVector<Output *> &outputs, bool isTabletMode) const;
private:
std::pair<OutputConfiguration, QVector<Output *>> generateConfig(const QVector<Output *> &outputs, bool isLidClosed) const;
void applyOrientationReading(OutputConfiguration &config, const QVector<Output *> &outputs, QOrientationReading *orientation, bool isTabletMode);
std::optional<std::pair<OutputConfiguration, QVector<Output *>>> generateLidClosedConfig(const QVector<Output *> &outputs);
std::pair<OutputConfiguration, QVector<Output *>> generateConfig(const QVector<Output *> &outputs, bool isLidClosed);
std::shared_ptr<OutputMode> chooseMode(Output *output) const;
double chooseScale(Output *output, OutputMode *mode) const;
double targetDpi(Output *output) const;
void load();
void save();
struct ModeData
{
QSize size;
uint32_t refreshRate;
};
struct OutputState
{
// identification data
std::optional<QString> edidIdentifier;
std::optional<QString> connectorName;
// actual state
std::optional<ModeData> mode;
std::optional<double> scale;
std::optional<OutputTransform> transform;
std::optional<uint32_t> overscan;
std::optional<Output::RgbRange> rgbRange;
std::optional<RenderLoop::VrrPolicy> vrrPolicy;
std::optional<bool> highDynamicRange;
std::optional<uint32_t> sdrBrightness;
std::optional<bool> wideColorGamut;
std::optional<Output::AutoRotationPolicy> autoRotation;
};
struct SetupState
{
size_t outputIndex;
QPoint position;
bool enabled;
int priority;
};
struct Setup
{
bool lidClosed = false;
QVector<SetupState> outputs;
};
std::pair<OutputConfiguration, QVector<Output *>> setupToConfig(Setup *setup, const std::unordered_map<Output *, size_t> &outputMap) const;
std::optional<std::pair<Setup *, std::unordered_map<Output *, size_t>>> findSetup(const QVector<Output *> &outputs, bool lidClosed);
std::optional<size_t> findOutput(Output *output, const QVector<Output *> &allOutputs) const;
QVector<OutputState> m_outputs;
QVector<Setup> m_setups;
};
}

@ -4,6 +4,7 @@ target_sources(kwin PRIVATE
edid.cpp
egl_context_attribute_builder.cpp
filedescriptor.cpp
orientationsensor.cpp
ramfile.cpp
realtime.cpp
softwarevsyncmonitor.cpp

@ -120,6 +120,9 @@ Edid::Edid(const void *data, uint32_t size)
hash.addData(m_raw);
m_hash = QString::fromLatin1(hash.result().toHex());
m_identifier = QByteArray(productInfo->manufacturer, 3) + " " + QByteArray::number(productInfo->product) + " " + QByteArray::number(productInfo->serial) + " "
+ QByteArray::number(productInfo->manufacture_week) + " " + QByteArray::number(productInfo->manufacture_year) + " " + QByteArray::number(productInfo->model_year);
// colorimetry and HDR metadata
const auto chromaticity = di_edid_get_chromaticity_coords(edid);
if (chromaticity) {
@ -252,4 +255,9 @@ std::optional<Edid::HDRMetadata> Edid::hdrMetadata() const
return m_hdrMetadata;
}
QByteArray Edid::identifier() const
{
return m_identifier;
}
} // namespace KWin

@ -92,6 +92,12 @@ public:
};
std::optional<HDRMetadata> hdrMetadata() const;
/**
* @returns a string that is intended to identify the monitor uniquely.
* Note that multiple monitors can have the same EDID, so this is not always actually unique
*/
QByteArray identifier() const;
private:
QSize m_physicalSize;
QByteArray m_vendor;
@ -102,6 +108,8 @@ private:
Colorimetry m_colorimetry;
std::optional<HDRMetadata> m_hdrMetadata;
QByteArray m_identifier;
QByteArray m_raw;
bool m_isValid = false;
};

@ -0,0 +1,54 @@
/*
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "orientationsensor.h"
#include <QOrientationSensor>
namespace KWin
{
OrientationSensor::OrientationSensor()
: m_sensor(std::make_unique<QOrientationSensor>())
, m_reading(std::make_unique<QOrientationReading>())
{
m_reading->setOrientation(QOrientationReading::Orientation::Undefined);
}
OrientationSensor::~OrientationSensor() = default;
void OrientationSensor::setEnabled(bool enable)
{
if (enable) {
connect(m_sensor.get(), &QOrientationSensor::readingChanged, this, &OrientationSensor::update, Qt::UniqueConnection);
m_sensor->start();
} else {
disconnect(m_sensor.get(), &QOrientationSensor::readingChanged, this, &OrientationSensor::update);
m_reading->setOrientation(QOrientationReading::Undefined);
}
}
QOrientationReading *OrientationSensor::reading() const
{
return m_reading.get();
}
void OrientationSensor::update()
{
if (auto reading = m_sensor->reading()) {
if (m_reading->orientation() != reading->orientation()) {
m_reading->setOrientation(reading->orientation());
Q_EMIT orientationChanged();
}
} else if (m_reading->orientation() != QOrientationReading::Orientation::Undefined) {
m_reading->setOrientation(QOrientationReading::Orientation::Undefined);
Q_EMIT orientationChanged();
}
}
}
#include "moc_orientationsensor.cpp"

@ -0,0 +1,38 @@
/*
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QObject>
#include <memory>
class QOrientationSensor;
class QOrientationReading;
namespace KWin
{
class OrientationSensor : public QObject
{
Q_OBJECT
public:
explicit OrientationSensor();
~OrientationSensor();
void setEnabled(bool enable);
QOrientationReading *reading() const;
Q_SIGNALS:
void orientationChanged();
private:
void update();
const std::unique_ptr<QOrientationSensor> m_sensor;
const std::unique_ptr<QOrientationReading> m_reading;
};
}

@ -22,7 +22,7 @@
namespace KWin
{
static const quint32 s_version = 3;
static const quint32 s_version = 4;
static QtWaylandServer::kde_output_device_v2::transform kwinTransformToOutputDeviceTransform(OutputTransform transform)
{
@ -52,6 +52,9 @@ static uint32_t kwinCapabilitiesToOutputDeviceCapabilities(Output::Capabilities
if (caps & Output::Capability::WideColorGamut) {
ret |= QtWaylandServer::kde_output_device_v2::capability_wide_color_gamut;
}
if (caps & Output::Capability::AutoRotation) {
ret |= QtWaylandServer::kde_output_device_v2::capability_auto_rotate;
}
return ret;
}
@ -65,6 +68,11 @@ static QtWaylandServer::kde_output_device_v2::rgb_range kwinRgbRangeToOutputDevi
return static_cast<QtWaylandServer::kde_output_device_v2::rgb_range>(range);
}
static QtWaylandServer::kde_output_device_v2::auto_rotate_policy kwinAutoRotationToOutputDeviceAutoRotation(Output::AutoRotationPolicy policy)
{
return static_cast<QtWaylandServer::kde_output_device_v2::auto_rotate_policy>(policy);
}
class OutputDeviceV2InterfacePrivate : public QtWaylandServer::kde_output_device_v2
{
public:
@ -89,6 +97,7 @@ public:
void sendHighDynamicRange(Resource *resource);
void sendSdrBrightness(Resource *resource);
void sendWideColorGamut(Resource *resource);
void sendAutoRotationPolicy(Resource *resource);
OutputDeviceV2Interface *q;
QPointer<Display> m_display;
@ -115,6 +124,7 @@ public:
bool m_highDynamicRange = false;
uint32_t m_sdrBrightness = 200;
bool m_wideColorGamut = false;
auto_rotate_policy m_autoRotation = auto_rotate_policy::auto_rotate_policy_in_tablet_mode;
protected:
void kde_output_device_v2_bind_resource(Resource *resource) override;
@ -195,6 +205,7 @@ OutputDeviceV2Interface::OutputDeviceV2Interface(Display *display, Output *handl
updateHighDynamicRange();
updateSdrBrightness();
updateWideColorGamut();
updateAutoRotate();
connect(handle, &Output::geometryChanged,
this, &OutputDeviceV2Interface::updateGlobalPosition);
@ -219,6 +230,7 @@ OutputDeviceV2Interface::OutputDeviceV2Interface(Display *display, Output *handl
connect(handle, &Output::highDynamicRangeChanged, this, &OutputDeviceV2Interface::updateHighDynamicRange);
connect(handle, &Output::sdrBrightnessChanged, this, &OutputDeviceV2Interface::updateSdrBrightness);
connect(handle, &Output::wideColorGamutChanged, this, &OutputDeviceV2Interface::updateWideColorGamut);
connect(handle, &Output::autoRotationPolicyChanged, this, &OutputDeviceV2Interface::updateAutoRotate);
}
OutputDeviceV2Interface::~OutputDeviceV2Interface()
@ -272,6 +284,7 @@ void OutputDeviceV2InterfacePrivate::kde_output_device_v2_bind_resource(Resource
sendHighDynamicRange(resource);
sendSdrBrightness(resource);
sendWideColorGamut(resource);
sendAutoRotationPolicy(resource);
sendDone(resource);
}
@ -390,6 +403,13 @@ void OutputDeviceV2InterfacePrivate::sendWideColorGamut(Resource *resource)
}
}
void OutputDeviceV2InterfacePrivate::sendAutoRotationPolicy(Resource *resource)
{
if (resource->version() >= KDE_OUTPUT_DEVICE_V2_AUTO_ROTATE_POLICY_SINCE_VERSION) {
send_auto_rotate_policy(resource->handle, m_autoRotation);
}
}
void OutputDeviceV2Interface::updateGeometry()
{
const auto clientResources = d->resourceMap();
@ -646,6 +666,19 @@ void OutputDeviceV2Interface::updateWideColorGamut()
}
}
void OutputDeviceV2Interface::updateAutoRotate()
{
const auto policy = kwinAutoRotationToOutputDeviceAutoRotation(d->m_handle->autoRotationPolicy());
if (d->m_autoRotation != policy) {
d->m_autoRotation = policy;
const auto clientResources = d->resourceMap();
for (const auto &resource : clientResources) {
d->sendAutoRotationPolicy(resource);
d->sendDone(resource);
}
}
}
OutputDeviceV2Interface *OutputDeviceV2Interface::get(wl_resource *native)
{
if (auto devicePrivate = resource_cast<OutputDeviceV2InterfacePrivate *>(native); devicePrivate && !devicePrivate->isGlobalRemoved()) {

@ -72,6 +72,7 @@ private:
void updateHighDynamicRange();
void updateSdrBrightness();
void updateWideColorGamut();
void updateAutoRotate();
std::unique_ptr<OutputDeviceV2InterfacePrivate> d;
};

@ -23,7 +23,7 @@
namespace KWin
{
static const quint32 s_version = 4;
static const quint32 s_version = 5;
class OutputManagementV2InterfacePrivate : public QtWaylandServer::kde_output_management_v2
{
@ -62,6 +62,7 @@ protected:
void kde_output_configuration_v2_set_high_dynamic_range(Resource *resource, wl_resource *outputdevice, uint32_t enable_hdr) override;
void kde_output_configuration_v2_set_sdr_brightness(Resource *resource, wl_resource *outputdevice, uint32_t sdr_brightness) override;
void kde_output_configuration_v2_set_wide_color_gamut(Resource *resource, wl_resource *outputdevice, uint32_t enable_wcg) override;
void kde_output_configuration_v2_set_auto_rotate_policy(Resource *resource, wl_resource *outputdevice, uint32_t auto_rotation_policy) override;
};
OutputManagementV2InterfacePrivate::OutputManagementV2InterfacePrivate(Display *display)
@ -272,6 +273,16 @@ void OutputConfigurationV2Interface::kde_output_configuration_v2_set_wide_color_
}
}
void OutputConfigurationV2Interface::kde_output_configuration_v2_set_auto_rotate_policy(Resource *resource, wl_resource *outputdevice, uint32_t auto_rotation_policy)
{
if (invalid) {
return;
}
if (OutputDeviceV2Interface *output = OutputDeviceV2Interface::get(outputdevice)) {
config.changeSet(output->handle())->autoRotationPolicy = static_cast<Output::AutoRotationPolicy>(auto_rotation_policy);
}
}
void OutputConfigurationV2Interface::kde_output_configuration_v2_destroy(Resource *resource)
{
wl_resource_destroy(resource->handle);

@ -52,9 +52,11 @@
#include "placeholderinputeventfilter.h"
#include "placeholderoutput.h"
#include "placementtracker.h"
#include "tabletmodemanager.h"
#include "tiles/tilemanager.h"
#include "useractions.h"
#include "utils/kernel.h"
#include "utils/orientationsensor.h"
#include "utils/xcbutils.h"
#include "virtualdesktops.h"
#include "was_user_interaction_x11_filter.h"
@ -104,6 +106,7 @@ Workspace::Workspace()
, m_placementTracker(std::make_unique<PlacementTracker>(this))
, m_outputConfigStore(std::make_unique<OutputConfigurationStore>())
, m_lidSwitchTracker(std::make_unique<LidSwitchTracker>())
, m_orientationSensor(std::make_unique<OrientationSensor>())
{
_self = this;
@ -231,10 +234,18 @@ void Workspace::init()
connect(this, &Workspace::windowRemoved, m_placementTracker.get(), &PlacementTracker::remove);
m_placementTracker->init(getPlacementTrackerHash());
connect(m_lidSwitchTracker.get(), &LidSwitchTracker::lidStateChanged, this, [this]() {
const auto [config, order] = m_outputConfigStore->queryConfig(kwinApp()->outputBackend()->outputs(), m_lidSwitchTracker->isLidClosed());
applyOutputConfiguration(config, order);
});
const auto applySensorChanges = [this]() {
m_orientationSensor->setEnabled(m_outputConfigStore->isAutoRotateActive(kwinApp()->outputBackend()->outputs(), kwinApp()->tabletModeManager()->effectiveTabletMode()));
const auto opt = m_outputConfigStore->queryConfig(kwinApp()->outputBackend()->outputs(), m_lidSwitchTracker->isLidClosed(), m_orientationSensor->reading(), kwinApp()->tabletModeManager()->effectiveTabletMode());
if (opt) {
const auto &[config, order, type] = *opt;
applyOutputConfiguration(config, order);
}
};
connect(m_lidSwitchTracker.get(), &LidSwitchTracker::lidStateChanged, this, applySensorChanges);
connect(m_orientationSensor.get(), &OrientationSensor::orientationChanged, this, applySensorChanges);
connect(kwinApp()->tabletModeManager(), &TabletModeManager::tabletModeChanged, this, applySensorChanges);
m_orientationSensor->setEnabled(m_outputConfigStore->isAutoRotateActive(kwinApp()->outputBackend()->outputs(), kwinApp()->tabletModeManager()->effectiveTabletMode()));
}
QString Workspace::getPlacementTrackerHash()
@ -462,6 +473,21 @@ bool Workspace::applyOutputConfiguration(const OutputConfiguration &config, cons
return false;
}
updateOutputs(outputOrder);
m_outputConfigStore->storeConfig(kwinApp()->outputBackend()->outputs(), m_lidSwitchTracker->isLidClosed(), config, outputOrder);
KConfig cfg(QStringLiteral("kdeglobals"));
KConfigGroup kscreenGroup = cfg.group("KScreen");
const bool xwaylandClientsScale = kscreenGroup.readEntry("XwaylandClientsScale", true);
if (xwaylandClientsScale) {
double maxScale = 0;
for (Output *output : outputOrder) {
const auto changeset = config.constChangeSet(output);
maxScale = std::max(maxScale, changeset ? changeset->scale.value_or(output->scale()) : output->scale());
}
kwinApp()->setXwaylandScale(maxScale);
} else {
kwinApp()->setXwaylandScale(1);
}
m_orientationSensor->setEnabled(m_outputConfigStore->isAutoRotateActive(kwinApp()->outputBackend()->outputs(), kwinApp()->tabletModeManager()->effectiveTabletMode()));
return true;
}
@ -492,13 +518,30 @@ void Workspace::updateOutputConfiguration()
setOutputOrder(newOrder);
};
const auto &[cfg, order] = m_outputConfigStore->queryConfig(outputs, m_lidSwitchTracker->isLidClosed());
if (!kwinApp()->outputBackend()->applyOutputChanges(cfg)) {
const auto opt = m_outputConfigStore->queryConfig(outputs, m_lidSwitchTracker->isLidClosed(), m_orientationSensor->reading(), kwinApp()->tabletModeManager()->effectiveTabletMode());
if (!opt) {
return;
}
const auto &[cfg, order, type] = *opt;
if (!applyOutputConfiguration(cfg, order)) {
qCWarning(KWIN_CORE) << "Applying output config failed!";
setFallbackOutputOrder();
return;
}
setOutputOrder(order);
if (type == OutputConfigurationStore::ConfigType::Generated) {
const bool hasInternal = std::any_of(outputs.begin(), outputs.end(), [](Output *o) {
return o->isInternal();
});
if (hasInternal && outputs.size() == 2) {
// show the OSD with output configuration presets
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kscreen.osdService"),
QStringLiteral("/org/kde/kscreen/osdService"),
QStringLiteral("org.kde.kscreen.osdService"),
QStringLiteral("showActionSelector"));
QDBusConnection::sessionBus().asyncCall(message);
}
}
}
void Workspace::setupWindowConnections(Window *window)

@ -79,6 +79,7 @@ class TileManager;
class OutputConfigurationStore;
class LidSwitchTracker;
class DpmsInputEventFilter;
class OrientationSensor;
class KWIN_EXPORT Workspace : public QObject
{
@ -725,6 +726,7 @@ private:
std::map<Output *, std::unique_ptr<TileManager>> m_tileManagers;
std::unique_ptr<OutputConfigurationStore> m_outputConfigStore;
std::unique_ptr<LidSwitchTracker> m_lidSwitchTracker;
std::unique_ptr<OrientationSensor> m_orientationSensor;
std::unique_ptr<DpmsInputEventFilter> m_dpmsFilter;
private:

Loading…
Cancel
Save