~neon/project-neon/kscreen

« back to all changes in this revision

Viewing changes to kded/serializer.cpp

  • Committer: Roman Gilg
  • Date: 2019-06-13 20:56:40 UTC
  • Revision ID: git-v1:9e34fd814f9087f3d7ad363a5f8d107aecce4e32
[kded] Introduce Config wrapper class

Summary:
In order to consolidate the code and as a preparatory step for larger changes
introduce a wrapper class for KScreen::Config types. This class combines
information about the type and the previously static Serializer methods, such
that the Serializer class can be removed.

Test Plan: Autotest updated.

Reviewers: #plasma

Subscribers: plasma-devel

Tags: #plasma

Maniphest Tasks: T10028

Differential Revision: https://phabricator.kde.org/D16989

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*************************************************************************************
2
 
 *  Copyright (C) 2012 by Alejandro Fiestas Olivares <afiestas@kde.org>              *
3
 
 *                                                                                   *
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.                           *
8
 
 *                                                                                   *
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.                                     *
13
 
 *                                                                                   *
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
 
 *************************************************************************************/
18
 
 
19
 
#include "serializer.h"
20
 
#include "kscreen_daemon_debug.h"
21
 
#include "generator.h"
22
 
 
23
 
#include <QStringList>
24
 
#include <QCryptographicHash>
25
 
#include <QFile>
26
 
#include <QStandardPaths>
27
 
#include <QRect>
28
 
#include <QStringBuilder>
29
 
#include <QJsonDocument>
30
 
#include <QDir>
31
 
#include <QLoggingCategory>
32
 
 
33
 
#include <kscreen/config.h>
34
 
#include <kscreen/output.h>
35
 
#include <kscreen/edid.h>
36
 
 
37
 
QString Serializer::sConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QStringLiteral("/kscreen/");
38
 
QString Serializer::sFixedConfig = QStringLiteral("fixed-config");
39
 
void Serializer::setConfigPath(const QString &path)
40
 
{
41
 
    sConfigPath = path;
42
 
    if (!sConfigPath.endsWith(QLatin1Char('/'))) {
43
 
        sConfigPath += QLatin1Char('/');
44
 
    }
45
 
}
46
 
 
47
 
QString Serializer::configFileName(const QString &configId)
48
 
{
49
 
    if (!QDir().mkpath(sConfigPath)) {
50
 
        return QString();
51
 
    }
52
 
    return sConfigPath % configId;
53
 
}
54
 
 
55
 
QString Serializer::configId(const KScreen::ConfigPtr &config)
56
 
{
57
 
    if (!config) {
58
 
        return QString();
59
 
    }
60
 
    return config->connectedOutputsHash();
61
 
}
62
 
 
63
 
bool Serializer::configExists(const KScreen::ConfigPtr &config)
64
 
{
65
 
    return Serializer::configExists(Serializer::configId(config));
66
 
}
67
 
 
68
 
bool Serializer::configExists(const QString &id)
69
 
{
70
 
    return (QFile::exists(sConfigPath % id) || QFile::exists(sConfigPath % sFixedConfig));
71
 
}
72
 
 
73
 
KScreen::ConfigPtr Serializer::loadConfig(const KScreen::ConfigPtr &currentConfig, const QString &id)
74
 
{
75
 
    KScreen::ConfigPtr config = currentConfig->clone();
76
 
 
77
 
    QFile file;
78
 
    if (QFile::exists(sConfigPath % sFixedConfig)) {
79
 
        file.setFileName(sConfigPath % sFixedConfig);
80
 
        qCDebug(KSCREEN_KDED) << "found a fixed config, will use " << file.fileName();
81
 
    } else {
82
 
        file.setFileName(configFileName(id));
83
 
    }
84
 
    if (!file.open(QIODevice::ReadOnly)) {
85
 
        qCDebug(KSCREEN_KDED) << "failed to open file" << file.fileName();
86
 
        return KScreen::ConfigPtr();
87
 
    }
88
 
 
89
 
    KScreen::OutputList outputList = config->outputs();
90
 
    QJsonDocument parser;
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);
95
 
        }
96
 
    }
97
 
 
98
 
    QSize screenSize;
99
 
    Q_FOREACH(const QVariant &info, outputs) {
100
 
        KScreen::OutputPtr output = Serializer::findOutput(config, info.toMap());
101
 
        if (!output) {
102
 
            continue;
103
 
        }
104
 
 
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());
109
 
            }
110
 
            if (geom.y() + geom.height() > screenSize.height()) {
111
 
                screenSize.setHeight(geom.y() + geom.height());
112
 
            }
113
 
        }
114
 
 
115
 
        outputList.remove(output->id());
116
 
        outputList.insert(output->id(), output);
117
 
    }
118
 
    config->setOutputs(outputList);
119
 
    config->screen()->setCurrentSize(screenSize);
120
 
 
121
 
 
122
 
    return config;
123
 
}
124
 
 
125
 
bool Serializer::saveConfig(const KScreen::ConfigPtr &config, const QString &configId)
126
 
{
127
 
    if (!config || configId.isEmpty()) {
128
 
        return false;
129
 
    }
130
 
    const KScreen::OutputList outputs = config->outputs();
131
 
 
132
 
    QVariantList outputList;
133
 
    Q_FOREACH(const KScreen::OutputPtr &output, outputs) {
134
 
        if (!output->isConnected()) {
135
 
            continue;
136
 
        }
137
 
 
138
 
        QVariantMap info;
139
 
 
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();
145
 
 
146
 
        QVariantMap pos;
147
 
        pos[QStringLiteral("x")] = output->pos().x();
148
 
        pos[QStringLiteral("y")] = output->pos().y();
149
 
        info[QStringLiteral("pos")] = pos;
150
 
 
151
 
        if (output->isEnabled()) {
152
 
            const KScreen::ModePtr mode = output->currentMode();
153
 
            if (!mode) {
154
 
                qWarning() << "CurrentMode is null" << output->name();
155
 
                return false;
156
 
            }
157
 
 
158
 
            QVariantMap modeInfo;
159
 
            modeInfo[QStringLiteral("refresh")] = mode->refreshRate();
160
 
 
161
 
            QVariantMap modeSize;
162
 
            modeSize[QStringLiteral("width")] = mode->size().width();
163
 
            modeSize[QStringLiteral("height")] = mode->size().height();
164
 
            modeInfo[QStringLiteral("size")] = modeSize;
165
 
 
166
 
            info[QStringLiteral("mode")] = modeInfo;
167
 
        }
168
 
 
169
 
        info[QStringLiteral("metadata")] = Serializer::metadata(output);
170
 
 
171
 
        outputList.append(info);
172
 
    }
173
 
 
174
 
    QFile file(configFileName(configId));
175
 
    if (!file.open(QIODevice::WriteOnly)) {
176
 
        qCWarning(KSCREEN_KDED) << "Failed to open config file for writing! " << file.errorString();
177
 
        return false;
178
 
    }
179
 
 
180
 
    file.write(QJsonDocument::fromVariant(outputList).toJson());
181
 
    qCDebug(KSCREEN_KDED) << "Config saved on: " << file.fileName();
182
 
 
183
 
    return true;
184
 
}
185
 
 
186
 
void Serializer::removeConfig(const QString &id)
187
 
{
188
 
    QFile::remove(configFileName(id));
189
 
}
190
 
 
191
 
bool Serializer::moveConfig(const QString &srcId, const QString &destId)
192
 
{
193
 
    const QFile srcFile(configFileName(srcId));
194
 
    if (srcFile.exists()) {
195
 
        removeConfig(destId);
196
 
        if (QFile::copy(configFileName(srcId), configFileName(destId))) {
197
 
            removeConfig(srcId);
198
 
            qCDebug(KSCREEN_KDED) << "Restored config" << srcId << "to" << destId;
199
 
            return true;
200
 
        }
201
 
    }
202
 
    return false;
203
 
}
204
 
 
205
 
KScreen::OutputPtr Serializer::findOutput(const KScreen::ConfigPtr &config, const QVariantMap& info)
206
 
{
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;
210
 
    QStringList allIds;
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;
216
 
        }
217
 
        allIds << outputId;
218
 
    }
219
 
    allIds.clear();
220
 
 
221
 
    Q_FOREACH(KScreen::OutputPtr output, outputs) {
222
 
        if (!output->isConnected()) {
223
 
            continue;
224
 
        }
225
 
        const auto outputId = output->hash();
226
 
        if (outputId != info[QStringLiteral("id")].toString()) {
227
 
            continue;
228
 
        }
229
 
 
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) {
236
 
                continue;
237
 
            }
238
 
        }
239
 
 
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());
247
 
 
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());
251
 
 
252
 
        qCDebug(KSCREEN_KDED) << "Finding a mode for" << size << "@" << modeInfo[QStringLiteral("refresh")].toFloat();
253
 
 
254
 
        KScreen::ModeList modes = output->modes();
255
 
        KScreen::ModePtr matchingMode;
256
 
        Q_FOREACH(const KScreen::ModePtr &mode, modes) {
257
 
            if (mode->size() != size) {
258
 
                continue;
259
 
            }
260
 
            if (!qFuzzyCompare(mode->refreshRate(), modeInfo[QStringLiteral("refresh")].toFloat())) {
261
 
                continue;
262
 
            }
263
 
 
264
 
            qCDebug(KSCREEN_KDED) << "\tFound: " << mode->id() << " " << mode->size() << "@" << mode->refreshRate();
265
 
            matchingMode = mode;
266
 
            break;
267
 
        }
268
 
 
269
 
        if (!matchingMode) {
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();
274
 
 
275
 
            if (!matchingMode) {
276
 
                qCWarning(KSCREEN_KDED) << "\tFailed to get a preferred mode, falling back to biggest mode.";
277
 
                matchingMode = Generator::biggestMode(modes);
278
 
 
279
 
                if (!matchingMode) {
280
 
                    qCWarning(KSCREEN_KDED) << "\tFailed to get biggest mode. Which means there are no modes. Turning off the screen.";
281
 
                    output->setEnabled(false);
282
 
                    return output;
283
 
                }
284
 
            }
285
 
        }
286
 
 
287
 
        output->setCurrentModeId(matchingMode->id());
288
 
        return output;
289
 
    }
290
 
 
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();
294
 
}
295
 
 
296
 
QVariantMap Serializer::metadata(const KScreen::OutputPtr &output)
297
 
{
298
 
    QVariantMap metadata;
299
 
    metadata[QStringLiteral("name")] = output->name();
300
 
    if (!output->edid() || !output->edid()->isValid()) {
301
 
        return metadata;
302
 
    }
303
 
 
304
 
    metadata[QStringLiteral("fullname")] = output->edid()->deviceId();
305
 
    return metadata;
306
 
}