1
/*************************************************************************************
2
* Copyright (C) 2012 by Alejandro Fiestas Olivares <afiestas@kde.org> *
4
* This program is free software; you can redistribute it and/or *
5
* modify it under the terms of the GNU General Public License *
6
* as published by the Free Software Foundation; either version 2 *
7
* of the License, or (at your option) any later version. *
9
* This program is distributed in the hope that it will be useful, *
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12
* GNU General Public License for more details. *
14
* You should have received a copy of the GNU General Public License *
15
* along with this program; if not, write to the Free Software *
16
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA *
17
*************************************************************************************/
19
#include "serializer.h"
20
#include "kscreen_daemon_debug.h"
21
#include "generator.h"
23
#include <QStringList>
24
#include <QCryptographicHash>
26
#include <QStandardPaths>
28
#include <QStringBuilder>
29
#include <QJsonDocument>
31
#include <QLoggingCategory>
33
#include <kscreen/config.h>
34
#include <kscreen/output.h>
35
#include <kscreen/edid.h>
37
QString Serializer::sConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QStringLiteral("/kscreen/");
38
QString Serializer::sFixedConfig = QStringLiteral("fixed-config");
39
void Serializer::setConfigPath(const QString &path)
42
if (!sConfigPath.endsWith(QLatin1Char('/'))) {
43
sConfigPath += QLatin1Char('/');
47
QString Serializer::configFileName(const QString &configId)
49
if (!QDir().mkpath(sConfigPath)) {
52
return sConfigPath % configId;
55
QString Serializer::configId(const KScreen::ConfigPtr &config)
60
return config->connectedOutputsHash();
63
bool Serializer::configExists(const KScreen::ConfigPtr &config)
65
return Serializer::configExists(Serializer::configId(config));
68
bool Serializer::configExists(const QString &id)
70
return (QFile::exists(sConfigPath % id) || QFile::exists(sConfigPath % sFixedConfig));
73
KScreen::ConfigPtr Serializer::loadConfig(const KScreen::ConfigPtr ¤tConfig, const QString &id)
75
KScreen::ConfigPtr config = currentConfig->clone();
78
if (QFile::exists(sConfigPath % sFixedConfig)) {
79
file.setFileName(sConfigPath % sFixedConfig);
80
qCDebug(KSCREEN_KDED) << "found a fixed config, will use " << file.fileName();
82
file.setFileName(configFileName(id));
84
if (!file.open(QIODevice::ReadOnly)) {
85
qCDebug(KSCREEN_KDED) << "failed to open file" << file.fileName();
86
return KScreen::ConfigPtr();
89
KScreen::OutputList outputList = config->outputs();
91
QVariantList outputs = parser.fromJson(file.readAll()).toVariant().toList();
92
Q_FOREACH(KScreen::OutputPtr output, outputList) {
93
if (!output->isConnected() && output->isEnabled()) {
94
output->setEnabled(false);
99
Q_FOREACH(const QVariant &info, outputs) {
100
KScreen::OutputPtr output = Serializer::findOutput(config, info.toMap());
105
if (output->isEnabled()) {
106
const QRect geom = output->geometry();
107
if (geom.x() + geom.width() > screenSize.width()) {
108
screenSize.setWidth(geom.x() + geom.width());
110
if (geom.y() + geom.height() > screenSize.height()) {
111
screenSize.setHeight(geom.y() + geom.height());
115
outputList.remove(output->id());
116
outputList.insert(output->id(), output);
118
config->setOutputs(outputList);
119
config->screen()->setCurrentSize(screenSize);
125
bool Serializer::saveConfig(const KScreen::ConfigPtr &config, const QString &configId)
127
if (!config || configId.isEmpty()) {
130
const KScreen::OutputList outputs = config->outputs();
132
QVariantList outputList;
133
Q_FOREACH(const KScreen::OutputPtr &output, outputs) {
134
if (!output->isConnected()) {
140
info[QStringLiteral("id")] = output->hash();
141
info[QStringLiteral("primary")] = output->isPrimary();
142
info[QStringLiteral("enabled")] = output->isEnabled();
143
info[QStringLiteral("rotation")] = output->rotation();
144
info[QStringLiteral("scale")] = output->scale();
147
pos[QStringLiteral("x")] = output->pos().x();
148
pos[QStringLiteral("y")] = output->pos().y();
149
info[QStringLiteral("pos")] = pos;
151
if (output->isEnabled()) {
152
const KScreen::ModePtr mode = output->currentMode();
154
qWarning() << "CurrentMode is null" << output->name();
158
QVariantMap modeInfo;
159
modeInfo[QStringLiteral("refresh")] = mode->refreshRate();
161
QVariantMap modeSize;
162
modeSize[QStringLiteral("width")] = mode->size().width();
163
modeSize[QStringLiteral("height")] = mode->size().height();
164
modeInfo[QStringLiteral("size")] = modeSize;
166
info[QStringLiteral("mode")] = modeInfo;
169
info[QStringLiteral("metadata")] = Serializer::metadata(output);
171
outputList.append(info);
174
QFile file(configFileName(configId));
175
if (!file.open(QIODevice::WriteOnly)) {
176
qCWarning(KSCREEN_KDED) << "Failed to open config file for writing! " << file.errorString();
180
file.write(QJsonDocument::fromVariant(outputList).toJson());
181
qCDebug(KSCREEN_KDED) << "Config saved on: " << file.fileName();
186
void Serializer::removeConfig(const QString &id)
188
QFile::remove(configFileName(id));
191
bool Serializer::moveConfig(const QString &srcId, const QString &destId)
193
const QFile srcFile(configFileName(srcId));
194
if (srcFile.exists()) {
195
removeConfig(destId);
196
if (QFile::copy(configFileName(srcId), configFileName(destId))) {
198
qCDebug(KSCREEN_KDED) << "Restored config" << srcId << "to" << destId;
205
KScreen::OutputPtr Serializer::findOutput(const KScreen::ConfigPtr &config, const QVariantMap& info)
207
const KScreen::OutputList outputs = config->outputs(); // As individual outputs are indexed by a hash of their edid, which is not unique,
208
// to be able to tell apart multiple identical outputs, these need special treatment
209
QStringList duplicateIds;
211
allIds.reserve(outputs.count());
212
Q_FOREACH (const KScreen::OutputPtr &output, outputs) {
213
const auto outputId = output->hash();
214
if (allIds.contains(outputId) && !duplicateIds.contains(outputId)) {
215
duplicateIds << outputId;
221
Q_FOREACH(KScreen::OutputPtr output, outputs) {
222
if (!output->isConnected()) {
225
const auto outputId = output->hash();
226
if (outputId != info[QStringLiteral("id")].toString()) {
230
// We may have identical outputs connected, these will have the same id in the config
231
// in order to find the right one, also check the output's name (usually the connector)
232
if (!output->name().isEmpty() && duplicateIds.contains(outputId)) {
233
const auto metadata = info[QStringLiteral("metadata")].toMap();
234
const auto outputName = metadata[QStringLiteral("name")].toString();
235
if (output->name() != outputName) {
240
const QVariantMap posInfo = info[QStringLiteral("pos")].toMap();
241
QPoint point(posInfo[QStringLiteral("x")].toInt(), posInfo[QStringLiteral("y")].toInt());
242
output->setPos(point);
243
output->setPrimary(info[QStringLiteral("primary")].toBool());
244
output->setEnabled(info[QStringLiteral("enabled")].toBool());
245
output->setRotation(static_cast<KScreen::Output::Rotation>(info[QStringLiteral("rotation")].toInt()));
246
output->setScale(info.value(QStringLiteral("scale"), 1).toInt());
248
const QVariantMap modeInfo = info[QStringLiteral("mode")].toMap();
249
const QVariantMap modeSize = modeInfo[QStringLiteral("size")].toMap();
250
const QSize size = QSize(modeSize[QStringLiteral("width")].toInt(), modeSize[QStringLiteral("height")].toInt());
252
qCDebug(KSCREEN_KDED) << "Finding a mode for" << size << "@" << modeInfo[QStringLiteral("refresh")].toFloat();
254
KScreen::ModeList modes = output->modes();
255
KScreen::ModePtr matchingMode;
256
Q_FOREACH(const KScreen::ModePtr &mode, modes) {
257
if (mode->size() != size) {
260
if (!qFuzzyCompare(mode->refreshRate(), modeInfo[QStringLiteral("refresh")].toFloat())) {
264
qCDebug(KSCREEN_KDED) << "\tFound: " << mode->id() << " " << mode->size() << "@" << mode->refreshRate();
270
qCWarning(KSCREEN_KDED) << "\tFailed to find a matching mode - this means that our config is corrupted"
271
"or a different device with the same serial number has been connected (very unlikely)."
272
"Falling back to preferred modes.";
273
matchingMode = output->preferredMode();
276
qCWarning(KSCREEN_KDED) << "\tFailed to get a preferred mode, falling back to biggest mode.";
277
matchingMode = Generator::biggestMode(modes);
280
qCWarning(KSCREEN_KDED) << "\tFailed to get biggest mode. Which means there are no modes. Turning off the screen.";
281
output->setEnabled(false);
287
output->setCurrentModeId(matchingMode->id());
291
qCWarning(KSCREEN_KDED) << "\tFailed to find a matching output in the current config - this means that our config is corrupted"
292
"or a different device with the same serial number has been connected (very unlikely).";
293
return KScreen::OutputPtr();
296
QVariantMap Serializer::metadata(const KScreen::OutputPtr &output)
298
QVariantMap metadata;
299
metadata[QStringLiteral("name")] = output->name();
300
if (!output->edid() || !output->edid()->isValid()) {
304
metadata[QStringLiteral("fullname")] = output->edid()->deviceId();