kwin/src/outputconfigurationstore.cpp
2023-06-21 11:17:00 +02:00

149 lines
5.5 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "outputconfigurationstore.h"
#include "core/inputdevice.h"
#include "core/output.h"
#include "core/outputbackend.h"
#include "core/outputconfiguration.h"
#include "input.h"
#include "input_event.h"
#include "kscreenintegration.h"
#include "workspace.h"
namespace KWin
{
std::pair<OutputConfiguration, QVector<Output *>> OutputConfigurationStore::queryConfig(const QVector<Output *> &outputs, bool isLidClosed)
{
// 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();
} else {
// no config file atm -> generate a new one
return generateConfig(outputs, isLidClosed);
}
}
std::pair<OutputConfiguration, QVector<Output *>> OutputConfigurationStore::generateConfig(const QVector<Output *> &outputs, bool isLidClosed) const
{
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 bool enable = !isLidClosed || !output->isInternal();
*ret.changeSet(output) = {
.mode = mode,
.enabled = enable,
.pos = pos,
.scale = scale,
.transform = output->panelOrientation(),
.overscan = 0,
.rgbRange = Output::RgbRange::Automatic,
.vrrPolicy = RenderLoop::VrrPolicy::Automatic,
};
pos.setX(pos.x() + mode->size().width() / scale);
if (enable) {
outputOrder.push_back(output);
}
}
return std::make_pair(ret, outputs);
}
std::shared_ptr<OutputMode> OutputConfigurationStore::chooseMode(Output *output) const
{
const auto modes = output->modes();
// some displays advertise bigger modes than their native resolution
// to avoid that, take the preferred mode into account, which is usually the native one
const auto preferred = std::find_if(modes.begin(), modes.end(), [](const auto &mode) {
return mode->flags() & OutputMode::Flag::Preferred;
});
if (preferred != modes.end()) {
// some high refresh rate displays advertise a 60Hz mode as preferred for compatibility reasons
// ignore that and choose the highest possible refresh rate by default instead
std::shared_ptr<OutputMode> highestRefresh = *preferred;
for (const auto &mode : modes) {
if (mode->size() == highestRefresh->size() && mode->refreshRate() > highestRefresh->refreshRate()) {
highestRefresh = mode;
}
}
// if the preferred mode size has a refresh rate that's too low for PCs,
// allow falling back to a mode with lower resolution and a more usable refresh rate
if (highestRefresh->refreshRate() >= 50000) {
return highestRefresh;
}
}
std::shared_ptr<OutputMode> ret;
for (auto mode : modes) {
if (!ret) {
ret = mode;
continue;
}
const bool retUsableRefreshRate = ret->refreshRate() >= 50000;
const bool usableRefreshRate = mode->refreshRate() >= 50000;
if (retUsableRefreshRate && !usableRefreshRate) {
ret = mode;
continue;
}
if ((usableRefreshRate && !retUsableRefreshRate)
|| mode->size().width() > ret->size().width()
|| mode->size().height() > ret->size().height()
|| (mode->size() == ret->size() && mode->refreshRate() > ret->refreshRate())) {
ret = mode;
}
}
return ret;
}
double OutputConfigurationStore::chooseScale(Output *output, OutputMode *mode) const
{
if (output->physicalSize().height() <= 0) {
// invalid size, can't do anything with this
return 1.0;
}
const double outputDpi = mode->size().height() / (output->physicalSize().height() / 25.4);
const double desiredScale = outputDpi / targetDpi(output);
// round to 25% steps
return std::clamp(std::round(100.0 * desiredScale / 25.0) * 25.0 / 100.0, 1.0, 5.0);
}
double OutputConfigurationStore::targetDpi(Output *output) const
{
// The eye's ability to perceive detail diminishes with distance, so objects
// that are closer can be smaller and their details remain equally
// distinguishable. As a result, each device type has its own ideal physical
// size of items on its screen based on how close the user's eyes are
// expected to be from it on average, and its target DPI value needs to be
// changed accordingly.
const auto devices = input()->devices();
const bool hasLaptopLid = std::any_of(devices.begin(), devices.end(), [](const auto &device) {
return device->isLidSwitch();
});
if (output->isInternal()) {
if (hasLaptopLid) {
// laptop screens: usually closer to the face than desktop monitors
return 125;
} else {
// phone screens: even closer than laptops
return 136;
}
} else {
// "normal" 1x scale desktop monitor dpi
return 96;
}
}
}