refactor(screen): remove obsolete KScreen backend

main
reionwong 3 weeks ago
parent 86fd8a9da2
commit 7fabd0bf2b

@ -1,632 +0,0 @@
/********************************************************************
Copyright 2019 Roman Gilg <subdiff@gmail.com>
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 2 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/>.
*********************************************************************/
#include "control.h"
#include "globals.h"
#include <KDirWatch>
#include <QDir>
#include <QFile>
#include <QJsonDocument>
#include <QStringBuilder>
#include <kscreen/config.h>
#include <kscreen/output.h>
QString Control::s_dirName = QStringLiteral("control/");
Control::Control(QObject *parent)
: QObject(parent)
{
}
void Control::activateWatcher()
{
if (m_watcher) {
return;
}
m_watcher = new KDirWatch(this);
m_watcher->addFile(filePath());
connect(m_watcher, &KDirWatch::dirty, this, [this]() {
readFile();
Q_EMIT changed();
});
}
KDirWatch *Control::watcher() const
{
return m_watcher;
}
bool Control::writeFile()
{
const QString path = filePath();
const auto infoMap = constInfo();
if (infoMap.isEmpty()) {
// Nothing to write. Default control. Remove file if it exists.
QFile::remove(path);
return true;
}
if (!QDir().mkpath(dirPath())) {
return false;
}
// write updated data to file
QFile file(path);
if (!file.open(QIODevice::WriteOnly)) {
return false;
}
file.write(QJsonDocument::fromVariant(infoMap).toJson());
return true;
}
QString Control::dirPath() const
{
return Globals::dirPath() % s_dirName;
}
void Control::readFile()
{
QFile file(filePath());
if (file.open(QIODevice::ReadOnly)) {
// This might not be reached, bus this is ok. The control file will
// eventually be created on first write later on.
QJsonDocument parser;
m_info = parser.fromJson(file.readAll()).toVariant().toMap();
}
}
QString Control::filePathFromHash(const QString &hash) const
{
return dirPath() % hash;
}
QVariantMap &Control::info()
{
return m_info;
}
const QVariantMap &Control::constInfo() const
{
return m_info;
}
Control::OutputRetention Control::convertVariantToOutputRetention(QVariant variant)
{
if (variant.canConvert<int>()) {
const auto retention = variant.toInt();
if (retention == (int)OutputRetention::Global) {
return OutputRetention::Global;
}
if (retention == (int)OutputRetention::Individual) {
return OutputRetention::Individual;
}
}
return OutputRetention::Undefined;
}
ControlConfig::ControlConfig(KScreen::ConfigPtr config, QObject *parent)
: Control(parent)
, m_config(config)
{
// qDebug() << "Looking for control file:" << config->connectedOutputsHash();
readFile();
// TODO: use a file watcher in case of changes to the control file while
// object exists?
// As global outputs are indexed by a hash of their edid, which is not unique,
// to be able to tell apart multiple identical outputs, these need special treatment
QStringList allIds;
const auto outputs = config->outputs();
allIds.reserve(outputs.count());
for (const KScreen::OutputPtr &output : outputs) {
const auto outputId = output->hashMd5();
if (allIds.contains(outputId) && !m_duplicateOutputIds.contains(outputId)) {
m_duplicateOutputIds << outputId;
}
allIds << outputId;
}
for (auto output : outputs) {
m_outputsControls << new ControlOutput(output, this);
}
// TODO: this is same in Output::readInOutputs of the daemon. Combine?
// TODO: connect to outputs added/removed signals and reevaluate duplicate ids
// in case of such a change while object exists?
}
void ControlConfig::activateWatcher()
{
if (watcher()) {
// Watcher was already activated.
return;
}
for (auto *output : m_outputsControls) {
output->activateWatcher();
connect(output, &ControlOutput::changed, this, &ControlConfig::changed);
}
}
QString ControlConfig::dirPath() const
{
return Control::dirPath() % QStringLiteral("configs/");
}
QString ControlConfig::filePath() const
{
if (!m_config) {
return QString();
}
return filePathFromHash(m_config->connectedOutputsHash());
}
bool ControlConfig::writeFile()
{
bool success = true;
for (auto *outputControl : m_outputsControls) {
if (getOutputRetention(outputControl->id(), outputControl->name()) == OutputRetention::Individual) {
continue;
}
success &= outputControl->writeFile();
}
return success && Control::writeFile();
}
bool ControlConfig::infoIsOutput(const QVariantMap &info, const QString &outputId, const QString &outputName) const
{
const QString outputIdInfo = info[QStringLiteral("id")].toString();
if (outputIdInfo.isEmpty()) {
return false;
}
if (outputId != outputIdInfo) {
return false;
}
if (!outputName.isEmpty() && m_duplicateOutputIds.contains(outputId)) {
// We may have identical outputs connected, these will have the same id in the config
// in order to find the right one, also check the output's name (usually the connector)
const auto metadata = info[QStringLiteral("metadata")].toMap();
const auto outputNameInfo = metadata[QStringLiteral("name")].toString();
if (outputName != outputNameInfo) {
// was a duplicate id, but info not for this output
return false;
}
}
return true;
}
Control::OutputRetention ControlConfig::getOutputRetention(const KScreen::OutputPtr &output) const
{
return getOutputRetention(output->hashMd5(), output->name());
}
Control::OutputRetention ControlConfig::getOutputRetention(const QString &outputId, const QString &outputName) const
{
const QVariantList outputsInfo = getOutputs();
for (const auto &variantInfo : outputsInfo) {
const QVariantMap info = variantInfo.toMap();
if (!infoIsOutput(info, outputId, outputName)) {
continue;
}
return convertVariantToOutputRetention(info[QStringLiteral("retention")]);
}
// info for output not found
return OutputRetention::Undefined;
}
static QVariantMap metadata(const QString &outputName)
{
QVariantMap metadata;
metadata[QStringLiteral("name")] = outputName;
return metadata;
}
QVariantMap createOutputInfo(const QString &outputId, const QString &outputName)
{
QVariantMap outputInfo;
outputInfo[QStringLiteral("id")] = outputId;
outputInfo[QStringLiteral("metadata")] = metadata(outputName);
return outputInfo;
}
void ControlConfig::setOutputRetention(const KScreen::OutputPtr &output, OutputRetention value)
{
setOutputRetention(output->hashMd5(), output->name(), value);
}
void ControlConfig::setOutputRetention(const QString &outputId, const QString &outputName, OutputRetention value)
{
QList<QVariant>::iterator it;
QVariantList outputsInfo = getOutputs();
for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) {
QVariantMap outputInfo = (*it).toMap();
if (!infoIsOutput(outputInfo, outputId, outputName)) {
continue;
}
outputInfo[QStringLiteral("retention")] = (int)value;
*it = outputInfo;
setOutputs(outputsInfo);
return;
}
// no entry yet, create one
auto outputInfo = createOutputInfo(outputId, outputName);
outputInfo[QStringLiteral("retention")] = (int)value;
outputsInfo << outputInfo;
setOutputs(outputsInfo);
}
qreal ControlConfig::getScale(const KScreen::OutputPtr &output) const
{
return getScale(output->hashMd5(), output->name());
}
qreal ControlConfig::getScale(const QString &outputId, const QString &outputName) const
{
const auto retention = getOutputRetention(outputId, outputName);
if (retention == OutputRetention::Individual) {
const QVariantList outputsInfo = getOutputs();
for (const auto &variantInfo : outputsInfo) {
const QVariantMap info = variantInfo.toMap();
if (!infoIsOutput(info, outputId, outputName)) {
continue;
}
const auto val = info[QStringLiteral("scale")];
return val.canConvert<qreal>() ? val.toReal() : -1;
}
}
// Retention is global or info for output not in config control file.
if (auto *outputControl = getOutputControl(outputId, outputName)) {
return outputControl->getScale();
}
// Info for output not found.
return -1;
}
void ControlConfig::setScale(const KScreen::OutputPtr &output, qreal value)
{
setScale(output->hashMd5(), output->name(), value);
}
// TODO: combine methods (templated functions)
void ControlConfig::setScale(const QString &outputId, const QString &outputName, qreal value)
{
QList<QVariant>::iterator it;
QVariantList outputsInfo = getOutputs();
auto setOutputScale = [&outputId, &outputName, value, this]() {
if (auto *control = getOutputControl(outputId, outputName)) {
control->setScale(value);
}
};
for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) {
QVariantMap outputInfo = (*it).toMap();
if (!infoIsOutput(outputInfo, outputId, outputName)) {
continue;
}
outputInfo[QStringLiteral("scale")] = value;
*it = outputInfo;
setOutputs(outputsInfo);
setOutputScale();
return;
}
// no entry yet, create one
auto outputInfo = createOutputInfo(outputId, outputName);
outputInfo[QStringLiteral("scale")] = value;
outputsInfo << outputInfo;
setOutputs(outputsInfo);
setOutputScale();
}
bool ControlConfig::getAutoRotate(const KScreen::OutputPtr &output) const
{
return getAutoRotate(output->hashMd5(), output->name());
}
bool ControlConfig::getAutoRotate(const QString &outputId, const QString &outputName) const
{
const auto retention = getOutputRetention(outputId, outputName);
if (retention == OutputRetention::Individual) {
const QVariantList outputsInfo = getOutputs();
for (const auto &variantInfo : outputsInfo) {
const QVariantMap info = variantInfo.toMap();
if (!infoIsOutput(info, outputId, outputName)) {
continue;
}
const auto val = info[QStringLiteral("autorotate")];
return !val.canConvert<bool>() || val.toBool();
}
}
// Retention is global or info for output not in config control file.
if (auto *outputControl = getOutputControl(outputId, outputName)) {
return outputControl->getAutoRotate();
}
// Info for output not found.
return true;
}
void ControlConfig::setAutoRotate(const KScreen::OutputPtr &output, bool value)
{
setAutoRotate(output->hashMd5(), output->name(), value);
}
// TODO: combine methods (templated functions)
void ControlConfig::setAutoRotate(const QString &outputId, const QString &outputName, bool value)
{
QList<QVariant>::iterator it;
QVariantList outputsInfo = getOutputs();
auto setOutputAutoRotate = [&outputId, &outputName, value, this]() {
if (auto *control = getOutputControl(outputId, outputName)) {
control->setAutoRotate(value);
}
};
for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) {
QVariantMap outputInfo = (*it).toMap();
if (!infoIsOutput(outputInfo, outputId, outputName)) {
continue;
}
outputInfo[QStringLiteral("autorotate")] = value;
*it = outputInfo;
setOutputs(outputsInfo);
setOutputAutoRotate();
return;
}
// no entry yet, create one
auto outputInfo = createOutputInfo(outputId, outputName);
outputInfo[QStringLiteral("autorotate")] = value;
outputsInfo << outputInfo;
setOutputs(outputsInfo);
setOutputAutoRotate();
}
bool ControlConfig::getAutoRotateOnlyInTabletMode(const KScreen::OutputPtr &output) const
{
return getAutoRotateOnlyInTabletMode(output->hashMd5(), output->name());
}
bool ControlConfig::getAutoRotateOnlyInTabletMode(const QString &outputId, const QString &outputName) const
{
const auto retention = getOutputRetention(outputId, outputName);
if (retention == OutputRetention::Individual) {
const QVariantList outputsInfo = getOutputs();
for (const auto &variantInfo : outputsInfo) {
const QVariantMap info = variantInfo.toMap();
if (!infoIsOutput(info, outputId, outputName)) {
continue;
}
const auto val = info[QStringLiteral("autorotate-tablet-only")];
return !val.canConvert<bool>() || val.toBool();
}
}
// Retention is global or info for output not in config control file.
if (auto *outputControl = getOutputControl(outputId, outputName)) {
return outputControl->getAutoRotateOnlyInTabletMode();
}
// Info for output not found.
return true;
}
void ControlConfig::setAutoRotateOnlyInTabletMode(const KScreen::OutputPtr &output, bool value)
{
setAutoRotateOnlyInTabletMode(output->hashMd5(), output->name(), value);
}
// TODO: combine methods (templated functions)
void ControlConfig::setAutoRotateOnlyInTabletMode(const QString &outputId, const QString &outputName, bool value)
{
QList<QVariant>::iterator it;
QVariantList outputsInfo = getOutputs();
auto setOutputAutoRotateOnlyInTabletMode = [&outputId, &outputName, value, this]() {
if (auto *control = getOutputControl(outputId, outputName)) {
control->setAutoRotateOnlyInTabletMode(value);
}
};
for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) {
QVariantMap outputInfo = (*it).toMap();
if (!infoIsOutput(outputInfo, outputId, outputName)) {
continue;
}
outputInfo[QStringLiteral("autorotate-tablet-only")] = value;
*it = outputInfo;
setOutputs(outputsInfo);
setOutputAutoRotateOnlyInTabletMode();
return;
}
// no entry yet, create one
auto outputInfo = createOutputInfo(outputId, outputName);
outputInfo[QStringLiteral("autorotate-tablet-only")] = value;
outputsInfo << outputInfo;
setOutputs(outputsInfo);
setOutputAutoRotateOnlyInTabletMode();
}
KScreen::OutputPtr ControlConfig::getReplicationSource(const KScreen::OutputPtr &output) const
{
return getReplicationSource(output->hashMd5(), output->name());
}
KScreen::OutputPtr ControlConfig::getReplicationSource(const QString &outputId, const QString &outputName) const
{
const QVariantList outputsInfo = getOutputs();
for (const auto &variantInfo : outputsInfo) {
const QVariantMap info = variantInfo.toMap();
if (!infoIsOutput(info, outputId, outputName)) {
continue;
}
const QString sourceHash = info[QStringLiteral("replicate-hash")].toString();
const QString sourceName = info[QStringLiteral("replicate-name")].toString();
if (sourceHash.isEmpty() && sourceName.isEmpty()) {
// Common case when the replication source has been unset.
return nullptr;
}
for (const auto &output : m_config->outputs()) {
if (output->hashMd5() == sourceHash && output->name() == sourceName) {
return output;
}
}
// No match.
return nullptr;
}
// Info for output not found.
return nullptr;
}
void ControlConfig::setReplicationSource(const KScreen::OutputPtr &output, const KScreen::OutputPtr &source)
{
setReplicationSource(output->hashMd5(), output->name(), source);
}
void ControlConfig::setReplicationSource(const QString &outputId, const QString &outputName, const KScreen::OutputPtr &source)
{
QList<QVariant>::iterator it;
QVariantList outputsInfo = getOutputs();
const QString sourceHash = source ? source->hashMd5() : QStringLiteral("");
const QString sourceName = source ? source->name() : QStringLiteral("");
for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) {
QVariantMap outputInfo = (*it).toMap();
if (!infoIsOutput(outputInfo, outputId, outputName)) {
continue;
}
outputInfo[QStringLiteral("replicate-hash")] = sourceHash;
outputInfo[QStringLiteral("replicate-name")] = sourceName;
*it = outputInfo;
setOutputs(outputsInfo);
// TODO: shall we set this information also as new global value (like with auto-rotate)?
return;
}
// no entry yet, create one
auto outputInfo = createOutputInfo(outputId, outputName);
outputInfo[QStringLiteral("replicate-hash")] = sourceHash;
outputInfo[QStringLiteral("replicate-name")] = sourceName;
outputsInfo << outputInfo;
setOutputs(outputsInfo);
// TODO: shall we set this information also as new global value (like with auto-rotate)?
}
QVariantList ControlConfig::getOutputs() const
{
return constInfo()[QStringLiteral("outputs")].toList();
}
void ControlConfig::setOutputs(QVariantList outputsInfo)
{
auto &infoMap = info();
infoMap[QStringLiteral("outputs")] = outputsInfo;
}
ControlOutput *ControlConfig::getOutputControl(const QString &outputId, const QString &outputName) const
{
for (auto *control : m_outputsControls) {
if (control->id() == outputId && control->name() == outputName) {
return control;
}
}
return nullptr;
}
ControlOutput::ControlOutput(KScreen::OutputPtr output, QObject *parent)
: Control(parent)
, m_output(output)
{
readFile();
}
QString ControlOutput::id() const
{
return m_output->hashMd5();
}
QString ControlOutput::name() const
{
return m_output->name();
}
QString ControlOutput::dirPath() const
{
return Control::dirPath() % QStringLiteral("outputs/");
}
QString ControlOutput::filePath() const
{
if (!m_output) {
return QString();
}
return filePathFromHash(m_output->hashMd5());
}
qreal ControlOutput::getScale() const
{
const auto val = constInfo()[QStringLiteral("scale")];
return val.canConvert<qreal>() ? val.toReal() : -1;
}
void ControlOutput::setScale(qreal value)
{
auto &infoMap = info();
if (infoMap.isEmpty()) {
infoMap = createOutputInfo(m_output->hashMd5(), m_output->name());
}
infoMap[QStringLiteral("scale")] = value;
}
bool ControlOutput::getAutoRotate() const
{
const auto val = constInfo()[QStringLiteral("autorotate")];
return !val.canConvert<bool>() || val.toBool();
}
void ControlOutput::setAutoRotate(bool value)
{
auto &infoMap = info();
if (infoMap.isEmpty()) {
infoMap = createOutputInfo(m_output->hashMd5(), m_output->name());
}
infoMap[QStringLiteral("autorotate")] = value;
}
bool ControlOutput::getAutoRotateOnlyInTabletMode() const
{
const auto val = constInfo()[QStringLiteral("autorotate-tablet-only")];
return !val.canConvert<bool>() || val.toBool();
}
void ControlOutput::setAutoRotateOnlyInTabletMode(bool value)
{
auto &infoMap = info();
if (infoMap.isEmpty()) {
infoMap = createOutputInfo(m_output->hashMd5(), m_output->name());
}
infoMap[QStringLiteral("autorotate-tablet-only")] = value;
}

@ -1,144 +0,0 @@
/********************************************************************
Copyright 2019 Roman Gilg <subdiff@gmail.com>
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 2 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/>.
*********************************************************************/
#ifndef COMMON_CONTROL_H
#define COMMON_CONTROL_H
#include <kscreen/types.h>
#include <QObject>
#include <QVariantMap>
#include <QVector>
class KDirWatch;
class Control : public QObject
{
Q_OBJECT
public:
enum class OutputRetention {
Undefined = -1,
Global = 0,
Individual = 1,
};
Q_ENUM(OutputRetention)
explicit Control(QObject *parent = nullptr);
~Control() override = default;
virtual bool writeFile();
virtual void activateWatcher();
Q_SIGNALS:
void changed();
protected:
virtual QString dirPath() const;
virtual QString filePath() const = 0;
QString filePathFromHash(const QString &hash) const;
void readFile();
QVariantMap &info();
const QVariantMap &constInfo() const;
KDirWatch *watcher() const;
static OutputRetention convertVariantToOutputRetention(QVariant variant);
private:
static QString s_dirName;
QVariantMap m_info;
KDirWatch *m_watcher = nullptr;
};
class ControlOutput;
class ControlConfig : public Control
{
Q_OBJECT
public:
explicit ControlConfig(KScreen::ConfigPtr config, QObject *parent = nullptr);
OutputRetention getOutputRetention(const KScreen::OutputPtr &output) const;
OutputRetention getOutputRetention(const QString &outputId, const QString &outputName) const;
void setOutputRetention(const KScreen::OutputPtr &output, OutputRetention value);
void setOutputRetention(const QString &outputId, const QString &outputName, OutputRetention value);
qreal getScale(const KScreen::OutputPtr &output) const;
qreal getScale(const QString &outputId, const QString &outputName) const;
void setScale(const KScreen::OutputPtr &output, qreal value);
void setScale(const QString &outputId, const QString &outputName, qreal value);
bool getAutoRotate(const KScreen::OutputPtr &output) const;
bool getAutoRotate(const QString &outputId, const QString &outputName) const;
void setAutoRotate(const KScreen::OutputPtr &output, bool value);
void setAutoRotate(const QString &outputId, const QString &outputName, bool value);
bool getAutoRotateOnlyInTabletMode(const KScreen::OutputPtr &output) const;
bool getAutoRotateOnlyInTabletMode(const QString &outputId, const QString &outputName) const;
void setAutoRotateOnlyInTabletMode(const KScreen::OutputPtr &output, bool value);
void setAutoRotateOnlyInTabletMode(const QString &outputId, const QString &outputName, bool value);
KScreen::OutputPtr getReplicationSource(const KScreen::OutputPtr &output) const;
KScreen::OutputPtr getReplicationSource(const QString &outputId, const QString &outputName) const;
void setReplicationSource(const KScreen::OutputPtr &output, const KScreen::OutputPtr &source);
void setReplicationSource(const QString &outputId, const QString &outputName, const KScreen::OutputPtr &source);
QString dirPath() const override;
QString filePath() const override;
bool writeFile() override;
void activateWatcher() override;
private:
QVariantList getOutputs() const;
void setOutputs(QVariantList outputsInfo);
bool infoIsOutput(const QVariantMap &info, const QString &outputId, const QString &outputName) const;
ControlOutput *getOutputControl(const QString &outputId, const QString &outputName) const;
KScreen::ConfigPtr m_config;
QStringList m_duplicateOutputIds;
QVector<ControlOutput *> m_outputsControls;
};
class ControlOutput : public Control
{
Q_OBJECT
public:
explicit ControlOutput(KScreen::OutputPtr output, QObject *parent = nullptr);
QString id() const;
QString name() const;
// TODO: scale auto value
qreal getScale() const;
void setScale(qreal value);
bool getAutoRotate() const;
void setAutoRotate(bool value);
bool getAutoRotateOnlyInTabletMode() const;
void setAutoRotateOnlyInTabletMode(bool value);
QString dirPath() const override;
QString filePath() const override;
private:
KScreen::OutputPtr m_output;
};
#endif

@ -1,40 +0,0 @@
/********************************************************************
Copyright 2018 Roman Gilg <subdiff@gmail.com>
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 2 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/>.
*********************************************************************/
#include "globals.h"
#include <QStandardPaths>
#include <QStringBuilder>
namespace Globals
{
QString s_dirPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) %
QStringLiteral("/kscreen/");
QString dirPath() {
return s_dirPath;
}
void setDirPath(const QString &path)
{
s_dirPath = path;
if (!s_dirPath.endsWith(QLatin1Char('/'))) {
s_dirPath += QLatin1Char('/');
}
}
}

@ -1,28 +0,0 @@
/********************************************************************
Copyright 2018 Roman Gilg <subdiff@gmail.com>
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 2 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/>.
*********************************************************************/
#ifndef COMMON_GLOBALS_H
#define COMMON_GLOBALS_H
#include <QString>
namespace Globals
{
void setDirPath(const QString &path);
QString dirPath();
}
#endif

@ -1,81 +0,0 @@
/********************************************************************
Copyright © 2019 Roman Gilg <subdiff@gmail.com>
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 2 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/>.
*********************************************************************/
#include "orientation_sensor.h"
#include <QOrientationSensor>
OrientationSensor::OrientationSensor(QObject *parent)
: QObject(parent)
, m_sensor(new QOrientationSensor(this))
{
connect(m_sensor, &QOrientationSensor::activeChanged, this, &OrientationSensor::refresh);
}
OrientationSensor::~OrientationSensor() = default;
void OrientationSensor::updateState()
{
const auto orientation = m_sensor->reading()->orientation();
if (m_value != orientation) {
m_value = orientation;
Q_EMIT valueChanged(orientation);
}
}
void OrientationSensor::refresh()
{
if (m_sensor->isActive()) {
if (m_enabled) {
updateState();
}
Q_EMIT availableChanged(true);
} else {
Q_EMIT availableChanged(false);
}
}
QOrientationReading::Orientation OrientationSensor::value() const
{
return m_value;
}
bool OrientationSensor::available() const
{
return m_sensor->connectToBackend();
}
bool OrientationSensor::enabled() const
{
return m_sensor->isActive();
}
void OrientationSensor::setEnabled(bool enable)
{
if (m_enabled == enable) {
return;
}
m_enabled = enable;
if (enable) {
connect(m_sensor, &QOrientationSensor::readingChanged, this, &OrientationSensor::updateState);
m_sensor->start();
} else {
disconnect(m_sensor, &QOrientationSensor::readingChanged, this, &OrientationSensor::updateState);
m_value = QOrientationReading::Undefined;
}
Q_EMIT enabledChanged(enable);
}

@ -1,47 +0,0 @@
/********************************************************************
Copyright © 2019 Roman Gilg <subdiff@gmail.com>
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 2 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/>.
*********************************************************************/
#pragma once
#include <QObject>
#include <QOrientationReading>
class OrientationSensor final : public QObject
{
Q_OBJECT
public:
explicit OrientationSensor(QObject *parent = nullptr);
~OrientationSensor() override final;
QOrientationReading::Orientation value() const;
bool available() const;
bool enabled() const;
void setEnabled(bool enable);
Q_SIGNALS:
void valueChanged(QOrientationReading::Orientation orientation);
void availableChanged(bool available);
void enabledChanged(bool enabled);
private:
void refresh();
void updateState();
QOrientationSensor *m_sensor;
QOrientationReading::Orientation m_value = QOrientationReading::Undefined;
bool m_enabled = false;
};

@ -1,59 +0,0 @@
/*
* Copyright 2013 Daniel Vrátil <dvratil@redhat.com>
*
* 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 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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/>.
*
*/
#include "utils.h"
#include <kscreen/output.h>
#include <kscreen/edid.h>
QString Utils::outputName(const KScreen::OutputPtr& output)
{
return outputName(output.data());
}
QString Utils::outputName(const KScreen::Output *output)
{
if (output->type() == KScreen::Output::Panel) {
return QObject::tr("Laptop Screen");
}
if (output->edid()) {
// The name will be "VendorName ModelName (ConnectorName)",
// but some components may be empty.
QString name;
if (!(output->edid()->vendor().isEmpty())) {
name = output->edid()->vendor() + QLatin1Char(' ');
}
if (!output->edid()->name().isEmpty()) {
name += output->edid()->name() + QLatin1Char(' ');
}
if (!name.trimmed().isEmpty()) {
return name + QLatin1Char('(') + output->name() + QLatin1Char(')');
}
}
return output->name();
}
QString Utils::sizeToString(const QSize &size)
{
return QStringLiteral("%1x%2").arg(size.width()).arg(size.height());
}

@ -1,40 +0,0 @@
/*
* Copyright 2013 Daniel Vrátil <dvratil@redhat.com>
*
* 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 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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/>.
*
*/
#ifndef KSCREEN_KCM_UTILS_H
#define KSCREEN_KCM_UTILS_H
#include <QString>
#include <QSize>
#include <kscreen/types.h>
#include <kscreen/output.h>
namespace Utils
{
QString outputName(const KScreen::Output *output);
QString outputName(const KScreen::OutputPtr &output);
QString sizeToString(const QSize &size);
}
#endif

@ -1,331 +0,0 @@
/********************************************************************
Copyright © 2019 Roman Gilg <subdiff@gmail.com>
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 2 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/>.
*********************************************************************/
#include "confighandler.h"
#include "outputmodel.h"
#include <kscreen/configmonitor.h>
#include <kscreen/getconfigoperation.h>
#include <kscreen/output.h>
#include <QRect>
#include <QDebug>
using namespace KScreen;
ConfigHandler::ConfigHandler(QObject *parent)
: QObject(parent)
{
}
void ConfigHandler::setConfig(KScreen::ConfigPtr config)
{
m_config = config;
m_initialConfig = m_config->clone();
m_initialControl.reset(new ControlConfig(m_initialConfig));
KScreen::ConfigMonitor::instance()->addConfig(m_config);
m_control.reset(new ControlConfig(config));
m_outputs = new OutputModel(this);
connect(m_outputs, &OutputModel::positionChanged, this, &ConfigHandler::checkScreenNormalization);
connect(m_outputs, &OutputModel::sizeChanged, this, &ConfigHandler::checkScreenNormalization);
for (const KScreen::OutputPtr &output : config->outputs()) {
initOutput(output);
}
m_lastNormalizedScreenSize = screenSize();
// TODO: put this into m_initialControl
m_initialRetention = getRetention();
Q_EMIT retentionChanged();
connect(m_outputs, &OutputModel::changed, this, [this]() {
checkNeedsSave();
Q_EMIT changed();
});
connect(m_config.data(), &KScreen::Config::outputAdded, this, [this]() {
Q_EMIT outputConnect(true);
});
connect(m_config.data(), &KScreen::Config::outputRemoved, this, [this]() {
Q_EMIT outputConnect(false);
});
// connect(m_config.data(), &KScreen::Config::primaryOutputChanged, this, &ConfigHandler::primaryOutputChanged);
Q_EMIT outputModelChanged();
}
void ConfigHandler::resetScale(const KScreen::OutputPtr &output)
{
// Load scale control (either not set, same or windowing system does not transmit scale).
const qreal scale = m_control->getScale(output);
if (scale > 0) {
output->setScale(scale);
for (auto initialOutput : m_initialConfig->outputs()) {
if (initialOutput->id() == output->id()) {
initialOutput->setScale(scale);
break;
}
}
}
}
void ConfigHandler::initOutput(const KScreen::OutputPtr &output)
{
if (output->isConnected()) {
resetScale(output);
m_outputs->add(output);
}
connect(output.data(), &KScreen::Output::isConnectedChanged, this, [this, output]() {
Q_EMIT outputConnect(output->isConnected());
});
}
void ConfigHandler::updateInitialData()
{
m_initialRetention = getRetention();
connect(new GetConfigOperation(), &GetConfigOperation::finished, this, [this](ConfigOperation *op) {
if (op->hasError()) {
return;
}
m_initialConfig = qobject_cast<GetConfigOperation *>(op)->config();
for (auto output : m_config->outputs()) {
resetScale(output);
}
m_initialControl.reset(new ControlConfig(m_initialConfig));
checkNeedsSave();
});
}
void ConfigHandler::checkNeedsSave()
{
if (m_config->supportedFeatures() & KScreen::Config::Feature::PrimaryDisplay) {
if (m_config->primaryOutput() && m_initialConfig->primaryOutput()) {
if (m_config->primaryOutput()->hashMd5() != m_initialConfig->primaryOutput()->hashMd5()) {
Q_EMIT needsSaveChecked(true);
return;
}
} else if ((bool)m_config->primaryOutput() != (bool)m_initialConfig->primaryOutput()) {
Q_EMIT needsSaveChecked(true);
return;
}
}
if (m_initialRetention != getRetention()) {
Q_EMIT needsSaveChecked(true);
return;
}
for (const auto &output : m_config->connectedOutputs()) {
const QString hash = output->hashMd5();
for (const auto &initialOutput : m_initialConfig->outputs()) {
if (hash != initialOutput->hashMd5()) {
continue;
}
bool needsSave = false;
if (output->isEnabled() != initialOutput->isEnabled()) {
needsSave = true;
}
// clang-format off
if (output->isEnabled()) {
needsSave |= output->currentModeId() !=
initialOutput->currentModeId()
|| output->pos() != initialOutput->pos()
|| output->scale() != initialOutput->scale()
|| output->rotation() != initialOutput->rotation()
|| output->replicationSource() != initialOutput->replicationSource()
|| autoRotate(output) != m_initialControl->getAutoRotate(output)
|| autoRotateOnlyInTabletMode(output)
!= m_initialControl->getAutoRotateOnlyInTabletMode(output);
}
// clang-format on
if (needsSave) {
Q_EMIT needsSaveChecked(true);
return;
}
break;
}
}
Q_EMIT needsSaveChecked(false);
}
QSize ConfigHandler::screenSize() const
{
int width = 0, height = 0;
QSize size;
for (const auto &output : m_config->connectedOutputs()) {
if (!output->isPositionable()) {
continue;
}
const int outputRight = output->geometry().right();
const int outputBottom = output->geometry().bottom();
if (outputRight > width) {
width = outputRight;
}
if (outputBottom > height) {
height = outputBottom;
}
}
if (width > 0 && height > 0) {
size = QSize(width, height);
} else {
size = QSize();
}
return size;
}
QSize ConfigHandler::normalizeScreen()
{
if (!m_config) {
return QSize();
}
bool changed = m_outputs->normalizePositions();
const auto currentScreenSize = screenSize();
changed |= m_lastNormalizedScreenSize != currentScreenSize;
m_lastNormalizedScreenSize = currentScreenSize;
Q_EMIT screenNormalizationUpdate(true);
return currentScreenSize;
}
void ConfigHandler::checkScreenNormalization()
{
const bool normalized = !m_config || (m_lastNormalizedScreenSize == screenSize() && m_outputs->positionsNormalized());
Q_EMIT screenNormalizationUpdate(normalized);
}
void ConfigHandler::primaryOutputSelected(int index)
{
Q_UNUSED(index)
// TODO
}
void ConfigHandler::primaryOutputChanged(const KScreen::OutputPtr &output)
{
Q_UNUSED(output)
}
Control::OutputRetention ConfigHandler::getRetention() const
{
using Retention = Control::OutputRetention;
auto ret = Retention::Undefined;
if (!m_control) {
return ret;
}
const auto outputs = m_config->connectedOutputs();
if (outputs.isEmpty()) {
return ret;
}
ret = m_control->getOutputRetention(outputs.first());
for (const auto &output : outputs) {
const auto outputRet = m_control->getOutputRetention(output);
if (ret != outputRet) {
// Control file with different retention values per output.
return Retention::Undefined;
}
}
if (ret == Retention::Undefined) {
// If all outputs have undefined retention,
// this should be displayed as global retention.
return Retention::Global;
}
return ret;
}
int ConfigHandler::retention() const
{
return static_cast<int>(getRetention());
}
void ConfigHandler::setRetention(int retention)
{
using Retention = Control::OutputRetention;
if (!m_control) {
return;
}
if (retention != static_cast<int>(Retention::Global) && retention != static_cast<int>(Retention::Individual)) {
// We only allow setting to global or individual retention.
return;
}
if (retention == ConfigHandler::retention()) {
return;
}
auto ret = static_cast<Retention>(retention);
for (const auto &output : m_config->connectedOutputs()) {
m_control->setOutputRetention(output, ret);
}
checkNeedsSave();
Q_EMIT retentionChanged();
Q_EMIT changed();
}
qreal ConfigHandler::scale(const KScreen::OutputPtr &output) const
{
return m_control->getScale(output);
}
void ConfigHandler::setScale(KScreen::OutputPtr &output, qreal scale)
{
m_control->setScale(output, scale);
}
KScreen::OutputPtr ConfigHandler::replicationSource(const KScreen::OutputPtr &output) const
{
return m_control->getReplicationSource(output);
}
void ConfigHandler::setReplicationSource(KScreen::OutputPtr &output, const KScreen::OutputPtr &source)
{
m_control->setReplicationSource(output, source);
}
bool ConfigHandler::autoRotate(const KScreen::OutputPtr &output) const
{
return m_control->getAutoRotate(output);
}
void ConfigHandler::setAutoRotate(KScreen::OutputPtr &output, bool autoRotate)
{
m_control->setAutoRotate(output, autoRotate);
}
bool ConfigHandler::autoRotateOnlyInTabletMode(const KScreen::OutputPtr &output) const
{
return m_control->getAutoRotateOnlyInTabletMode(output);
}
void ConfigHandler::setAutoRotateOnlyInTabletMode(KScreen::OutputPtr &output, bool value)
{
m_control->setAutoRotateOnlyInTabletMode(output, value);
}
void ConfigHandler::writeControl()
{
if (!m_control) {
return;
}
m_control->writeFile();
}

@ -1,101 +0,0 @@
/********************************************************************
Copyright © 2019 Roman Gilg <subdiff@gmail.com>
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 2 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/>.
*********************************************************************/
#ifndef CONFIGHANDLER_H
#define CONFIGHANDLER_H
#include "./common/control.h"
#include <kscreen/config.h>
#include <memory>
class OutputModel;
class ConfigHandler : public QObject
{
Q_OBJECT
public:
explicit ConfigHandler(QObject *parent = nullptr);
~ConfigHandler() override = default;
void setConfig(KScreen::ConfigPtr config);
void updateInitialData();
OutputModel *outputModel() const
{
return m_outputs;
}
QSize normalizeScreen();
KScreen::ConfigPtr config() const
{
return m_config;
}
KScreen::ConfigPtr initialConfig() const
{
return m_initialConfig;
}
int retention() const;
void setRetention(int retention);
qreal scale(const KScreen::OutputPtr &output) const;
void setScale(KScreen::OutputPtr &output, qreal scale);
KScreen::OutputPtr replicationSource(const KScreen::OutputPtr &output) const;
void setReplicationSource(KScreen::OutputPtr &output, const KScreen::OutputPtr &source);
bool autoRotate(const KScreen::OutputPtr &output) const;
void setAutoRotate(KScreen::OutputPtr &output, bool autoRotate);
bool autoRotateOnlyInTabletMode(const KScreen::OutputPtr &output) const;
void setAutoRotateOnlyInTabletMode(KScreen::OutputPtr &output, bool value);
void writeControl();
void checkNeedsSave();
Q_SIGNALS:
void outputModelChanged();
void changed();
void screenNormalizationUpdate(bool normalized);
void needsSaveChecked(bool need);
void retentionChanged();
void outputConnect(bool connected);
private:
void checkScreenNormalization();
QSize screenSize() const;
Control::OutputRetention getRetention() const;
void primaryOutputSelected(int index);
void primaryOutputChanged(const KScreen::OutputPtr &output);
void initOutput(const KScreen::OutputPtr &output);
void resetScale(const KScreen::OutputPtr &output);
KScreen::ConfigPtr m_config = nullptr;
KScreen::ConfigPtr m_initialConfig;
OutputModel *m_outputs = nullptr;
std::unique_ptr<ControlConfig> m_control;
std::unique_ptr<ControlConfig> m_initialControl;
Control::OutputRetention m_initialRetention = Control::OutputRetention::Undefined;
QSize m_lastNormalizedScreenSize;
};
#endif

@ -1,981 +0,0 @@
/********************************************************************
Copyright © 2019 Roman Gilg <subdiff@gmail.com>
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 2 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/>.
*********************************************************************/
#include "outputmodel.h"
#include "./common/utils.h"
#include "confighandler.h"
#include <QRect>
OutputModel::OutputModel(ConfigHandler *configHandler)
: QAbstractListModel(configHandler)
, m_config(configHandler)
{
connect(this, &OutputModel::dataChanged, this, &OutputModel::changed);
}
int OutputModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_outputs.count();
}
QVariant OutputModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= m_outputs.count()) {
return QVariant();
}
const KScreen::OutputPtr &output = m_outputs[index.row()].ptr;
switch (role) {
case Qt::DisplayRole:
return Utils::outputName(output);
case EnabledRole:
return output->isEnabled();
case InternalRole:
return output->type() == KScreen::Output::Type::Panel;
case PrimaryRole:
return output->isPrimary();
case SizeRole:
return output->geometry().size();
case PositionRole:
return m_outputs[index.row()].pos;
case NormalizedPositionRole:
return output->geometry().topLeft();
case AutoRotateRole:
return m_config->autoRotate(output);
case AutoRotateOnlyInTabletModeRole:
return m_config->autoRotateOnlyInTabletMode(output);
case RotationRole:
return output->rotation();
case ScaleRole:
return output->scale();
case ResolutionIndexRole:
return resolutionIndex(output);
case ResolutionsRole:
return resolutionsStrings(output);
case RefreshRateIndexRole:
return refreshRateIndex(output);
case ReplicationSourceModelRole:
return replicationSourceModel(output);
case ReplicationSourceIndexRole:
return replicationSourceIndex(index.row());
case ReplicasModelRole:
return replicasModel(output);
case RefreshRatesRole:
QVariantList ret;
for (const auto rate : refreshRates(output)) {
ret << QString("%1 Hz").arg(int(rate + 0.5));
}
return ret;
}
return QVariant();
}
bool OutputModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
if (index.row() < 0 || index.row() >= m_outputs.count()) {
return false;
}
Output &output = m_outputs[index.row()];
switch (role) {
case PositionRole:
if (value.canConvert<QPoint>()) {
QPoint val = value.toPoint();
if (output.pos == val) {
return false;
}
snap(output, val);
m_outputs[index.row()].pos = val;
updatePositions();
Q_EMIT positionChanged();
Q_EMIT dataChanged(index, index, {role});
return true;
}
break;
case EnabledRole:
if (value.canConvert<bool>()) {
return setEnabled(index.row(), value.toBool());
}
break;
case PrimaryRole:
if (value.canConvert<bool>()) {
bool primary = value.toBool();
if (output.ptr->isPrimary() == primary) {
return false;
}
m_config->config()->setPrimaryOutput(output.ptr);
Q_EMIT dataChanged(index, index, {role});
return true;
}
break;
case ResolutionIndexRole:
if (value.canConvert<int>()) {
return setResolution(index.row(), value.toInt());
}
break;
case RefreshRateIndexRole:
if (value.canConvert<int>()) {
return setRefreshRate(index.row(), value.toInt());
}
break;
case AutoRotateRole:
if (value.canConvert<bool>()) {
return setAutoRotate(index.row(), value.value<bool>());
}
break;
case AutoRotateOnlyInTabletModeRole:
if (value.canConvert<bool>()) {
return setAutoRotateOnlyInTabletMode(index.row(), value.value<bool>());
}
break;
case RotationRole:
if (value.canConvert<KScreen::Output::Rotation>()) {
return setRotation(index.row(),
value.value<KScreen::Output::Rotation>());
}
break;
case ReplicationSourceIndexRole:
if (value.canConvert<int>()) {
return setReplicationSourceIndex(index.row(), value.toInt() - 1);
}
break;
case ScaleRole:
bool ok;
const qreal scale = value.toReal(&ok);
if (ok && !qFuzzyCompare(output.ptr->scale(), scale)) {
output.ptr->setScale(scale);
m_config->setScale(output.ptr, scale);
Q_EMIT sizeChanged();
Q_EMIT dataChanged(index, index, {role, SizeRole});
return true;
}
break;
}
return false;
}
QHash<int, QByteArray> OutputModel::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
roles[EnabledRole] = "enabled";
roles[InternalRole] = "internal";
roles[PrimaryRole] = "primary";
roles[SizeRole] = "size";
roles[PositionRole] = "position";
roles[NormalizedPositionRole] = "normalizedPosition";
roles[AutoRotateRole] = "autoRotate";
roles[AutoRotateOnlyInTabletModeRole] = "autoRotateOnlyInTabletMode";
roles[RotationRole] = "rotation";
roles[ScaleRole] = "scale";
roles[ResolutionIndexRole] = "resolutionIndex";
roles[ResolutionsRole] = "resolutions";
roles[RefreshRateIndexRole] = "refreshRateIndex";
roles[RefreshRatesRole] = "refreshRates";
roles[ReplicationSourceModelRole] = "replicationSourceModel";
roles[ReplicationSourceIndexRole] = "replicationSourceIndex";
roles[ReplicasModelRole] = "replicasModel";
return roles;
}
void OutputModel::add(const KScreen::OutputPtr &output)
{
const int insertPos = m_outputs.count();
Q_EMIT beginInsertRows(QModelIndex(), insertPos, insertPos);
int i = 0;
while (i < m_outputs.size()) {
const QPoint pos = m_outputs[i].ptr->pos();
if (output->pos().x() < pos.x()) {
break;
}
if (output->pos().x() == pos.x() &&
output->pos().y() < pos.y()) {
break;
}
i++;
}
// Set the initial non-normalized position to be the normalized
// position plus the current delta.
QPoint pos = output->pos();
if (!m_outputs.isEmpty()) {
const QPoint delta = m_outputs[0].pos - m_outputs[0].ptr->pos();
pos = output->pos() + delta;
}
m_outputs.insert(i, Output(output, pos));
connect(output.data(), &KScreen::Output::priorityChanged,
this, [this, output](){
roleChanged(output->id(), PrimaryRole);
});
Q_EMIT endInsertRows();
// Update replications.
for (int j = 0; j < m_outputs.size(); j++) {
if (i == j) {
continue;
}
QModelIndex index = createIndex(j, 0);
// Calling this directly ignores possible optimization when the
// refresh rate hasn't changed in fact. But that's ok.
Q_EMIT dataChanged(index, index, {ReplicationSourceModelRole,
ReplicationSourceIndexRole});
}
}
void OutputModel::remove(int outputId)
{
auto it = std::find_if(m_outputs.begin(), m_outputs.end(),
[outputId](const Output &output) {
return output.ptr->id() == outputId;
});
if (it != m_outputs.end()) {
const int index = it - m_outputs.begin();
Q_EMIT beginRemoveRows(QModelIndex(), index, index);
m_outputs.erase(it);
Q_EMIT endRemoveRows();
}
}
void OutputModel::resetPosition(const Output &output)
{
if (output.posReset.x() < 0) {
// KCM was closed in between.
for (const Output &out : m_outputs) {
if (out.ptr->id() == output.ptr->id()) {
continue;
}
if (out.ptr->geometry().right() > output.ptr->pos().x()) {
output.ptr->setPos(out.ptr->geometry().topRight());
}
}
} else {
output.ptr->setPos(/*output.ptr->pos() - */output.posReset);
}
}
bool OutputModel::setEnabled(int outputIndex, bool enable)
{
Output &output = m_outputs[outputIndex];
if (output.ptr->isEnabled() == enable) {
return false;
}
output.ptr->setEnabled(enable);
if (enable) {
resetPosition(output);
setResolution(outputIndex, resolutionIndex(output.ptr));
reposition();
} else {
output.posReset = output.ptr->pos();
}
QModelIndex index = createIndex(outputIndex, 0);
Q_EMIT dataChanged(index, index, {EnabledRole});
return true;
}
inline bool refreshRateCompare(float rate1, float rate2)
{
return qAbs(rate1 - rate2) < 0.5;
}
bool OutputModel::setResolution(int outputIndex, int resIndex)
{
const Output &output = m_outputs[outputIndex];
const auto resolutionList = resolutions(output.ptr);
if (resIndex < 0 || resIndex >= resolutionList.size()) {
return false;
}
const QSize size = resolutionList[resIndex];
const float oldRate = output.ptr->currentMode() ? output.ptr->currentMode()->refreshRate() :
-1;
const auto modes = output.ptr->modes();
auto modeIt = std::find_if(modes.begin(), modes.end(),
[size, oldRate](const KScreen::ModePtr &mode) {
// TODO: we don't want to compare against old refresh rate if
// refresh rate selection is auto.
return mode->size() == size &&
refreshRateCompare(mode->refreshRate(), oldRate);
});
if (modeIt == modes.end()) {
// New resolution does not support previous refresh rate.
// Get the highest one instead.
float bestRefreshRate = 0;
auto it = modes.begin();
while (it != modes.end()) {
if ((*it)->size() == size && (*it)->refreshRate() > bestRefreshRate) {
modeIt = it;
}
it++;
}
}
Q_ASSERT(modeIt != modes.end());
const auto id = (*modeIt)->id();
if (output.ptr->currentModeId() == id) {
return false;
}
output.ptr->setCurrentModeId(id);
QModelIndex index = createIndex(outputIndex, 0);
// Calling this directly ignores possible optimization when the
// refresh rate hasn't changed in fact. But that's ok.
Q_EMIT dataChanged(index, index, {ResolutionIndexRole,
SizeRole,
RefreshRateIndexRole});
Q_EMIT sizeChanged();
return true;
}
bool OutputModel::setRefreshRate(int outputIndex, int refIndex)
{
const Output &output = m_outputs[outputIndex];
const auto rates = refreshRates(output.ptr);
if (refIndex < 0 || refIndex >= rates.size()) {
return false;
}
const float refreshRate = rates[refIndex];
const auto modes = output.ptr->modes();
const auto oldMode = output.ptr->currentMode();
auto modeIt = std::find_if(modes.begin(), modes.end(),
[oldMode, refreshRate](const KScreen::ModePtr &mode) {
// TODO: we don't want to compare against old refresh rate if
// refresh rate selection is auto.
return mode->size() == oldMode->size() &&
refreshRateCompare(mode->refreshRate(), refreshRate);
});
Q_ASSERT(modeIt != modes.end());
if (refreshRateCompare(oldMode->refreshRate(), (*modeIt)->refreshRate())) {
// no change
return false;
}
output.ptr->setCurrentModeId((*modeIt)->id());
QModelIndex index = createIndex(outputIndex, 0);
Q_EMIT dataChanged(index, index, {RefreshRateIndexRole});
return true;
}
bool OutputModel::setAutoRotate(int outputIndex, bool value)
{
Output &output = m_outputs[outputIndex];
if (m_config->autoRotate(output.ptr) == value) {
return false;
}
m_config->setAutoRotate(output.ptr, value);
QModelIndex index = createIndex(outputIndex, 0);
Q_EMIT dataChanged(index, index, {AutoRotateRole});
return true;
}
bool OutputModel::setAutoRotateOnlyInTabletMode(int outputIndex, bool value)
{
Output &output = m_outputs[outputIndex];
if (m_config->autoRotateOnlyInTabletMode(output.ptr) == value) {
return false;
}
m_config->setAutoRotateOnlyInTabletMode(output.ptr, value);
QModelIndex index = createIndex(outputIndex, 0);
Q_EMIT dataChanged(index, index, {AutoRotateOnlyInTabletModeRole});
return true;
}
bool OutputModel::setRotation(int outputIndex, KScreen::Output::Rotation rotation)
{
const Output &output = m_outputs[outputIndex];
if (rotation != KScreen::Output::None
&& rotation != KScreen::Output::Left
&& rotation != KScreen::Output::Inverted
&& rotation != KScreen::Output::Right) {
return false;
}
if (output.ptr->rotation() == rotation) {
return false;
}
output.ptr->setRotation(rotation);
QModelIndex index = createIndex(outputIndex, 0);
Q_EMIT dataChanged(index, index, {RotationRole, SizeRole});
Q_EMIT sizeChanged();
return true;
}
int OutputModel::resolutionIndex(const KScreen::OutputPtr &output) const
{
const QSize currentResolution = output->enforcedModeSize();
if (!currentResolution.isValid()) {
return 0;
}
const auto sizes = resolutions(output);
const auto it = std::find_if(sizes.begin(),
sizes.end(),
[currentResolution](const QSize &size) {
return size == currentResolution;
});
if (it == sizes.end()) {
return -1;
}
return it - sizes.begin();
}
int OutputModel::refreshRateIndex(const KScreen::OutputPtr &output) const
{
if (!output->currentMode()) {
return 0;
}
const auto rates = refreshRates(output);
const float currentRate = output->currentMode()->refreshRate();
const auto it = std::find_if(rates.begin(),
rates.end(),
[currentRate](float rate) {
return refreshRateCompare(rate, currentRate);
});
if (it == rates.end()) {
return 0;
}
return it - rates.begin();
}
static int greatestCommonDivisor(int a, int b) {
if (b == 0) {
return a;
}
return greatestCommonDivisor(b, a % b);
}
QVariantList OutputModel::resolutionsStrings(const KScreen::OutputPtr &output) const
{
QVariantList ret;
for (const QSize &size : resolutions(output)) {
int divisor = greatestCommonDivisor(size.width(), size.height());
// Prefer "16:10" over "8:5"
if (size.height() / divisor == 5) {
divisor /= 2;
}
const QString text = QString("%1x%2").arg(QString::number(size.width()))
.arg(QString::number(size.height()));
ret << text;
}
return ret;
}
QVector<QSize> OutputModel::resolutions(const KScreen::OutputPtr &output) const
{
QVector<QSize> hits;
for (const auto &mode : output->modes()) {
const QSize size = mode->size();
if (!hits.contains(size)) {
hits << size;
}
}
std::sort(hits.begin(), hits.end(), [](const QSize &a, const QSize &b) {
if (a.width() > b.width()) {
return true;
}
if (a.width() == b.width() && a.height() > b.height()) {
return true;
}
return false;
});
return hits;
}
QVector<float> OutputModel::refreshRates(const KScreen::OutputPtr
&output) const
{
QVector<float> hits;
QSize baseSize;
if (output->currentMode()) {
baseSize = output->currentMode()->size();
} else if (output->preferredMode()) {
baseSize = output->preferredMode()->size();
}
if (!baseSize.isValid()) {
return hits;
}
for (const auto &mode : output->modes()) {
if (mode->size() != baseSize) {
continue;
}
const float rate = mode->refreshRate();
if (std::find_if(hits.begin(), hits.end(),
[rate](float r) {
return refreshRateCompare(r, rate);
}) != hits.end()) {
continue;
}
hits << rate;
}
return hits;
}
int OutputModel::replicationSourceId(const Output &output) const
{
const KScreen::OutputPtr source = m_config->replicationSource(output.ptr);
if (!source) {
return 0;
}
return source->id();
}
QStringList OutputModel::replicationSourceModel(const KScreen::OutputPtr &output) const
{
QStringList ret = { QObject::tr("None") };
for (const auto &out : m_outputs) {
if (out.ptr->id() != output->id()) {
const int outSourceId = replicationSourceId(out);
if (outSourceId == output->id()) {
// 'output' is already source for replication, can't be replica itself
return { QObject::tr("Replicated by other output") };
}
if (outSourceId) {
// This 'out' is a replica. Can't be a replication source.
continue;
}
ret.append(Utils::outputName(out.ptr));
}
}
return ret;
}
bool OutputModel::setReplicationSourceIndex(int outputIndex, int sourceIndex)
{
if (outputIndex <= sourceIndex) {
sourceIndex++;
}
if (sourceIndex >= m_outputs.count()) {
return false;
}
Output &output = m_outputs[outputIndex];
const int oldSourceId = replicationSourceId(output);
if (sourceIndex < 0) {
if (oldSourceId == 0) {
// no change
return false;
}
m_config->setReplicationSource(output.ptr, nullptr);
output.ptr->setExplicitLogicalSize(QSizeF());
resetPosition(output);
} else {
const auto source = m_outputs[sourceIndex].ptr;
if (oldSourceId == source->id()) {
// no change
return false;
}
m_config->setReplicationSource(output.ptr, source);
output.posReset = output.ptr->pos();
output.ptr->setPos(source->pos());
output.ptr->setExplicitLogicalSize(source->explicitLogicalSize());
}
reposition();
QModelIndex index = createIndex(outputIndex, 0);
Q_EMIT dataChanged(index, index, {ReplicationSourceIndexRole});
if (oldSourceId != 0) {
auto it = std::find_if(m_outputs.begin(), m_outputs.end(),
[oldSourceId](const Output &out) {
return out.ptr->id() == oldSourceId;
});
if (it != m_outputs.end()) {
QModelIndex index = createIndex(it - m_outputs.begin(), 0);
Q_EMIT dataChanged(index, index, {ReplicationSourceModelRole, ReplicasModelRole});
}
}
if (sourceIndex >= 0) {
QModelIndex index = createIndex(sourceIndex, 0);
Q_EMIT dataChanged(index, index, {ReplicationSourceModelRole, ReplicasModelRole});
}
return true;
}
int OutputModel::replicationSourceIndex(int outputIndex) const
{
const int sourceId = replicationSourceId(m_outputs[outputIndex]);
if (!sourceId) {
return 0;
}
for (int i = 0; i < m_outputs.size(); i++) {
const Output &output = m_outputs[i];
if (output.ptr->id() == sourceId) {
return i + (outputIndex > i ? 1 : 0);
}
}
return 0;
}
QVariantList OutputModel::replicasModel(const KScreen::OutputPtr &output) const
{
QVariantList ret;
for (int i = 0; i < m_outputs.size(); i++) {
const Output &out = m_outputs[i];
if (out.ptr->id() != output->id()) {
if (replicationSourceId(out) == output->id()) {
ret << i;
}
}
}
return ret;
}
void OutputModel::roleChanged(int outputId, OutputRoles role)
{
for (int i = 0; i < m_outputs.size(); i++) {
Output &output = m_outputs[i];
if (output.ptr->id() == outputId) {
QModelIndex index = createIndex(i, 0);
Q_EMIT dataChanged(index, index, {role});
return;
}
}
}
bool OutputModel::positionable(const Output &output) const
{
return output.ptr->isPositionable();
}
void OutputModel::reposition()
{
int x = 0;
int y = 0;
// Find first valid output.
for (const auto &out : m_outputs) {
if (positionable(out)) {
x = out.ptr->pos().x();
y = out.ptr->pos().y();
break;
}
}
for (int i = 0; i < m_outputs.size(); i++) {
if (!positionable(m_outputs[i])) {
continue;
}
const QPoint &cmp = m_outputs[i].ptr->pos();
if (cmp.x() < x) {
x = cmp.x();
}
if (cmp.y() < y) {
y = cmp.y();
}
}
if (x == 0 && y == 0) {
return;
}
for (int i = 0; i < m_outputs.size(); i++) {
auto &out = m_outputs[i];
out.ptr->setPos(out.ptr->pos() - QPoint(x, y));
QModelIndex index = createIndex(i, 0);
Q_EMIT dataChanged(index, index, {NormalizedPositionRole});
}
m_config->normalizeScreen();
}
QPoint OutputModel::originDelta() const
{
int x = 0;
int y = 0;
// Find first valid output.
for (const auto &out : m_outputs) {
if (positionable(out)) {
x = out.pos.x();
y = out.pos.y();
break;
}
}
for (int i = 1; i < m_outputs.size(); i++) {
if (!positionable(m_outputs[i])) {
continue;
}
const QPoint &cmp = m_outputs[i].pos;
if (cmp.x() < x) {
x = cmp.x();
}
if (cmp.y() < y) {
y = cmp.y();
}
}
return QPoint(x, y);
}
void OutputModel::updatePositions()
{
const QPoint delta = originDelta();
for (int i = 0; i < m_outputs.size(); i++) {
const auto &out = m_outputs[i];
if (!positionable(out)) {
continue;
}
const QPoint set = out.pos - delta;
if (out.ptr->pos() != set) {
out.ptr->setPos(set);
QModelIndex index = createIndex(i, 0);
Q_EMIT dataChanged(index, index, {NormalizedPositionRole});
}
}
updateOrder();
}
void OutputModel::updateOrder()
{
auto order = m_outputs;
std::sort(order.begin(), order.end(), [](const Output &a, const Output &b) {
const int xDiff = b.ptr->pos().x() - a.ptr->pos().x();
const int yDiff = b.ptr->pos().y() - a.ptr->pos().y();
if (xDiff > 0) {
return true;
}
if (xDiff == 0 && yDiff > 0) {
return true;
}
return false;
});
for (int i = 0; i < order.size(); i++) {
for (int j = 0; j < m_outputs.size(); j++) {
if (order[i].ptr->id() != m_outputs[j].ptr->id()) {
continue;
}
if (i != j) {
beginMoveRows(QModelIndex(), j, j, QModelIndex(), i);
m_outputs.remove(j);
m_outputs.insert(i, order[i]);
endMoveRows();
}
break;
}
}
// TODO: Could this be optimized by only outputs updating where replica indices changed?
for (int i = 0; i < m_outputs.size(); i++) {
QModelIndex index = createIndex(i, 0);
Q_EMIT dataChanged(index, index, { ReplicasModelRole });
}
}
bool OutputModel::normalizePositions()
{
bool changed = false;
for (int i = 0; i < m_outputs.size(); i++) {
auto &output = m_outputs[i];
if (output.pos == output.ptr->pos()) {
continue;
}
if (!positionable(output)) {
continue;
}
changed = true;
auto index = createIndex(i, 0);
output.pos = output.ptr->pos();
Q_EMIT dataChanged(index, index, {PositionRole});
}
return changed;
}
bool OutputModel::positionsNormalized() const
{
// There might be slight deviations because of snapping.
return originDelta().manhattanLength() < 5;
}
const int s_snapArea = 80;
bool isVerticalClose(const QRect &rect1, const QRect &rect2)
{
if (rect2.top() - rect1.bottom() > s_snapArea ) {
return false;
}
if (rect1.top() - rect2.bottom() > s_snapArea ) {
return false;
}
return true;
}
bool snapToRight(const QRect &target,
const QSize &size,
QPoint &dest)
{
if (qAbs(target.right() - dest.x()) < s_snapArea) {
// In snap zone for left to right snap.
dest.setX(target.right() + 1);
return true;
}
if (qAbs(target.right() - (dest.x() + size.width())) < s_snapArea) {
// In snap zone for right to right snap.
dest.setX(target.right() - size.width());
return true;
}
return false;
}
bool snapToLeft(const QRect &target,
const QSize &size,
QPoint &dest)
{
if (qAbs(target.left() - dest.x()) < s_snapArea) {
// In snap zone for left to left snap.
dest.setX(target.left());
return true;
}
if (qAbs(target.left() - (dest.x() + size.width())) < s_snapArea) {
// In snap zone for right to left snap.
dest.setX(target.left() - size.width());
return true;
}
return false;
}
bool snapToMiddle(const QRect &target,
const QSize &size,
QPoint &dest)
{
const int outputMid = dest.y() + size.height() / 2;
const int targetMid = target.top() + target.height() / 2;
if (qAbs(targetMid - outputMid) < s_snapArea) {
// In snap zone for middle to middle snap.
dest.setY(targetMid - size.height() / 2);
return true;
}
return false;
}
bool snapToTop(const QRect &target,
const QSize &size,
QPoint &dest)
{
if (qAbs(target.top() - dest.y()) < s_snapArea) {
// In snap zone for bottom to top snap.
dest.setY(target.top());
return true;
}
if (qAbs(target.top() - (dest.y() + size.height())) < s_snapArea) {
// In snap zone for top to top snap.
dest.setY(target.top() - size.height());
return true;
}
return false;
}
bool snapToBottom(const QRect &target,
const QSize &size,
QPoint &dest)
{
if (qAbs(target.bottom() - dest.y()) < s_snapArea) {
// In snap zone for top to bottom snap.
dest.setY(target.bottom() + 1);
return true;
}
if (qAbs(target.bottom() - (dest.y() + size.height())) < s_snapArea) {
// In snap zone for bottom to bottom snap.
dest.setY(target.bottom() - size.height() + 1);
return true;
}
return false;
}
bool snapVertical(const QRect &target,
const QSize &size,
QPoint &dest)
{
if (snapToMiddle(target, size, dest)) {
return true;
}
if (snapToBottom(target, size, dest)) {
return true;
}
if (snapToTop(target, size, dest)) {
return true;
}
return false;
}
void OutputModel::snap(const Output &output, QPoint &dest)
{
const QSize size = output.ptr->geometry().size();
for (const Output &out : m_outputs) {
if (out.ptr->id() == output.ptr->id()) {
// Can not snap to itself.
continue;
}
if (!positionable(out)) {
continue;
}
const QRect target(out.pos, out.ptr->geometry().size());
if (!isVerticalClose(target, QRect(dest, size))) {
continue;
}
// try snap left to right first
if (snapToRight(target, size, dest)) {
snapVertical(target, size, dest);
continue;
}
if (snapToLeft(target, size, dest)) {
snapVertical(target, size, dest);
continue;
}
if (snapVertical(target, size, dest)) {
continue;
}
}
}

@ -1,154 +0,0 @@
/********************************************************************
Copyright © 2019 Roman Gilg <subdiff@gmail.com>
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 2 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/>.
*********************************************************************/
#ifndef OUTPUTMODEL_H
#define OUTPUTMODEL_H
#include <kscreen/config.h>
#include <kscreen/output.h>
#include <QAbstractListModel>
#include <QPoint>
class ConfigHandler;
class OutputModel : public QAbstractListModel
{
Q_OBJECT
public:
enum OutputRoles {
EnabledRole = Qt::UserRole + 1,
InternalRole,
PrimaryRole,
SizeRole,
/** Position in the graphical view relative to some arbitrary but fixed origin. */
PositionRole,
/** Position for backend relative to most northwest display corner. */
NormalizedPositionRole,
AutoRotateRole,
AutoRotateOnlyInTabletModeRole,
RotationRole,
ScaleRole,
ResolutionIndexRole,
ResolutionsRole,
RefreshRateIndexRole,
RefreshRatesRole,
ReplicationSourceModelRole,
ReplicationSourceIndexRole,
ReplicasModelRole
};
explicit OutputModel(ConfigHandler *configHandler);
~OutputModel() override = default;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index,
int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index,
const QVariant &value,
int role = Qt::EditRole) override;
void add(const KScreen::OutputPtr &output);
void remove(int outputId);
/**
* Resets the origin for calculation of positions to the most northwest display corner
* while keeping the normalized positions untouched.
*
* @return true if some (unnormalized) output position changed on this call, otherwise false.
*/
bool normalizePositions();
bool positionsNormalized() const;
Q_SIGNALS:
void positionChanged();
void sizeChanged();
void changed();
protected:
QHash<int, QByteArray> roleNames() const override;
private:
struct Output {
Output() {}
Output(const Output &output)
: ptr(output.ptr)
, pos(output.pos)
{}
Output(Output &&) noexcept = default;
Output(KScreen::OutputPtr _ptr, const QPoint &_pos)
: ptr(_ptr)
, pos(_pos)
{}
Output &operator=(const Output &output) {
ptr = output.ptr;
pos = output.pos;
posReset = QPoint(-1, -1);
return *this;
}
Output &operator=(Output &&) noexcept = default;
KScreen::OutputPtr ptr;
QPoint pos;
QPoint posReset = QPoint(-1, -1);
};
void roleChanged(int outputId, OutputRoles role);
void resetPosition(const Output &output);
void reposition();
void updatePositions();
void updateOrder();
QPoint originDelta() const;
/**
* @brief Snaps moved output to others
* @param output the moved output
* @param dest the desired destination to be adjusted by snapping
*/
void snap(const Output &output, QPoint &dest);
bool setEnabled(int outputIndex, bool enable);
bool setResolution(int outputIndex, int resIndex);
bool setRefreshRate(int outputIndex, int refIndex);
bool setRotation(int outputIndex, KScreen::Output::Rotation rotation);
bool setAutoRotate(int outputIndex, bool value);
bool setAutoRotateOnlyInTabletMode(int outputIndex, bool value);
int resolutionIndex(const KScreen::OutputPtr &output) const;
int refreshRateIndex(const KScreen::OutputPtr &output) const;
QVariantList resolutionsStrings(const KScreen::OutputPtr &output) const;
QVector<QSize> resolutions(const KScreen::OutputPtr &output) const;
QVector<float> refreshRates(const KScreen::OutputPtr &output) const;
bool positionable(const Output &output) const;
QStringList replicationSourceModel(const KScreen::OutputPtr &output) const;
bool setReplicationSourceIndex(int outputIndex, int sourceIndex);
int replicationSourceIndex(int outputIndex) const;
int replicationSourceId(const Output &output) const;
QVariantList replicasModel(const KScreen::OutputPtr &output) const;
QVector<Output> m_outputs;
ConfigHandler *m_config;
};
#endif

@ -1,20 +0,0 @@
#include <QQmlExtensionPlugin>
#include <QQmlEngine>
#include "outputmodel.h"
#include "screen.h"
class QmlPlugins : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
public:
void registerTypes(const char * uri) override {
// qmlRegisterType<OutputModel>(uri, 1, 0, "OutputModel");
qmlRegisterType<Screen>(uri, 1, 0, "Screen");
qmlRegisterType<KScreen::Output>(uri, 1, 0, "Output");
}
};
#include "plugin.moc"

@ -1,73 +0,0 @@
#include "screen.h"
#include "outputmodel.h"
#include <kscreen/setconfigoperation.h>
#include <QQmlExtensionPlugin>
#include <QQmlEngine>
Screen::Screen(QObject *parent)
: QObject(parent)
{
qmlRegisterType<OutputModel>();
load();
}
void Screen::load()
{
// Don't pull away the outputModel under QML's feet
// signal its disappearance first before deleting and replacing it.
// We take the m_config pointer so outputModel() will return null,
// gracefully cleaning up the QML side and only then we will delete it.
auto *oldConfig = m_config.release();
if (oldConfig) {
emit outputModelChanged();
delete oldConfig;
}
m_config.reset(new ConfigHandler(this));
connect(m_config.get(), &ConfigHandler::outputModelChanged, this, &Screen::outputModelChanged);
connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, this, &Screen::configReady);
}
void Screen::save()
{
if (!m_config)
return;
auto config = m_config->config();
bool atLeastOneEnabledOutput = false;
for (const KScreen::OutputPtr &output : config->outputs()) {
KScreen::ModePtr mode = output->currentMode();
atLeastOneEnabledOutput |= output->isEnabled();
}
m_config->writeControl();
auto *op = new KScreen::SetConfigOperation(config);
op->exec();
}
OutputModel *Screen::outputModel() const
{
if (!m_config) {
return nullptr;
}
return m_config->outputModel();
}
void Screen::configReady(KScreen::ConfigOperation *op)
{
if (op->hasError()) {
m_config.reset();
return;
}
KScreen::ConfigPtr config = qobject_cast<KScreen::GetConfigOperation *>(op)->config();
// const bool autoRotationSupported = config->supportedFeatures() & (KScreen::Config::Feature::AutoRotation | KScreen::Config::Feature::TabletMode);
m_config->setConfig(config);
}

@ -1,33 +0,0 @@
#pragma once
#include <QObject>
#include <memory>
#include <kscreen/getconfigoperation.h>
#include "confighandler.h"
#include "outputmodel.h"
class ConfigHandler;
class OutputModel;
class Screen : public QObject
{
Q_OBJECT
Q_PROPERTY(OutputModel *outputModel READ outputModel NOTIFY outputModelChanged)
public:
explicit Screen(QObject *parent = nullptr);
OutputModel *outputModel() const;
void load();
Q_INVOKABLE void save();
private:
void configReady(KScreen::ConfigOperation *op);
Q_SIGNALS:
void outputModelChanged();
private:
std::unique_ptr<ConfigHandler> m_config;
};
Loading…
Cancel
Save