refactor(screen): remove obsolete KScreen backend
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…
Reference in New Issue