You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libcutefish/screen/screen.cpp

182 lines
6.0 KiB
C++

#include "screen.h"
#include "outputmodel.h"
#include <KScreen/ConfigMonitor>
#include <KScreen/GetConfigOperation>
#include <KScreen/Mode>
#include <KScreen/SetConfigOperation>
#include <QDebug>
#include <QGuiApplication>
#include <QSettings>
#include <QTimer>
static bool isDynamicDisplay(const KScreen::OutputPtr &output)
{
if (!output) {
return false;
}
const QString identity = QStringLiteral("%1 %2 %3")
.arg(output->name(), output->vendor(), output->model());
return identity.contains(QStringLiteral("virtual"), Qt::CaseInsensitive)
|| identity.contains(QStringLiteral("qemu"), Qt::CaseInsensitive)
|| identity.contains(QStringLiteral("spice"), Qt::CaseInsensitive)
|| identity.contains(QStringLiteral("virtio"), Qt::CaseInsensitive);
}
static QString displayOutputKey(const KScreen::OutputPtr &output)
{
if (!output) {
return QString();
}
// Virtual outputs can change their EDID/mode list whenever the host
// resizes the guest window. Their hash is therefore not a stable
// identity; use the compositor's output name instead.
QString key = isDynamicDisplay(output) ? output->name() : output->hashMd5();
if (key.isEmpty()) {
key = output->name();
}
if (key.isEmpty()) {
key = QString::number(output->id());
}
return key.replace(QLatin1Char('/'), QLatin1Char('_'));
}
static void saveDisplayConfiguration(const KScreen::ConfigPtr &config)
{
if (!config) {
return;
}
QSettings settings(QSettings::UserScope, QStringLiteral("cutefishos"), QStringLiteral("display"));
settings.setValue(QStringLiteral("Version"), 1);
settings.beginGroup(QStringLiteral("Outputs"));
const KScreen::OutputList outputs = config->outputs();
for (auto it = outputs.cbegin(); it != outputs.cend(); ++it) {
const KScreen::OutputPtr output = it.value();
if (!output || !output->isConnected()) {
continue;
}
settings.beginGroup(displayOutputKey(output));
settings.setValue(QStringLiteral("Name"), output->name());
settings.setValue(QStringLiteral("Enabled"), output->isEnabled());
settings.setValue(QStringLiteral("Priority"), output->priority());
settings.setValue(QStringLiteral("PositionX"), output->pos().x());
settings.setValue(QStringLiteral("PositionY"), output->pos().y());
settings.setValue(QStringLiteral("Scale"), output->scale());
settings.setValue(QStringLiteral("Rotation"), static_cast<int>(output->rotation()));
if (const KScreen::ModePtr mode = output->currentMode()) {
settings.setValue(QStringLiteral("ModeId"), mode->id());
settings.setValue(QStringLiteral("ModeWidth"), mode->size().width());
settings.setValue(QStringLiteral("ModeHeight"), mode->size().height());
settings.setValue(QStringLiteral("ModeRefreshRate"), mode->refreshRate());
}
settings.endGroup();
}
settings.endGroup();
settings.sync();
KScreen::OutputPtr primary = config->primaryOutput();
if (!primary) {
for (const KScreen::OutputPtr &output : config->connectedOutputs()) {
if (output && output->isEnabled()) {
primary = output;
break;
}
}
}
if (primary) {
QSettings themeSettings(QSettings::UserScope,
QStringLiteral("cutefishos"),
QStringLiteral("theme"));
themeSettings.setValue(QStringLiteral("PixelRatio"), primary->scale());
themeSettings.sync();
}
}
KScreenScreen::KScreenScreen(QObject *parent)
: QObject(parent)
, m_outputModel(new KScreenOutputModel(this))
{
if (QGuiApplication::platformName().contains(QStringLiteral("wayland"), Qt::CaseInsensitive)
&& qEnvironmentVariableIsEmpty("KSCREEN_BACKEND")) {
qputenv("KSCREEN_BACKEND", QByteArrayLiteral("kwayland"));
}
connect(KScreen::ConfigMonitor::instance(), &KScreen::ConfigMonitor::configurationChanged,
this, [this] {
if (!m_applying) {
loadConfig();
}
});
QTimer::singleShot(0, this, &KScreenScreen::loadConfig);
}
KScreenOutputModel *KScreenScreen::outputModel() const
{
return m_outputModel;
}
void KScreenScreen::loadConfig()
{
if (m_applying) {
return;
}
KScreen::GetConfigOperation operation(KScreen::ConfigOperation::NoOptions, this);
if (!operation.exec() || operation.hasError() || !operation.config()) {
qWarning() << "Unable to read the display configuration from KScreen:"
<< operation.errorString();
if (m_config) {
KScreen::ConfigMonitor::instance()->removeConfig(m_config);
}
m_config.clear();
m_outputModel->setConfig({});
emit outputModelChanged();
return;
}
if (m_config) {
KScreen::ConfigMonitor::instance()->removeConfig(m_config);
}
m_config = operation.config();
KScreen::ConfigMonitor::instance()->addConfig(m_config);
m_outputModel->setConfig(m_config);
emit outputModelChanged();
}
void KScreenScreen::save()
{
if (!m_config || m_applying) {
return;
}
if (!KScreen::Config::canBeApplied(m_config,
KScreen::Config::ValidityFlag::RequireAtLeastOneEnabledScreen)) {
qWarning() << "The requested display configuration is not valid";
loadConfig();
return;
}
m_applying = true;
// Cutefish owns the persistent display configuration. KWin is only the
// live display server to which this snapshot is applied.
saveDisplayConfiguration(m_config);
KScreen::SetConfigOperation operation(m_config, this);
const bool applied = operation.exec() && !operation.hasError();
if (!applied) {
qWarning() << "Unable to apply the display configuration through KWin Wayland:"
<< operation.errorString();
}
m_applying = false;
loadConfig();
}