Changeset View
Changeset View
Standalone View
Standalone View
kded/output.cpp
- This file was added.
1 | /******************************************************************** | ||||
---|---|---|---|---|---|
2 | Copyright 2019 Roman Gilg <subdiff@gmail.com> | ||||
3 | | ||||
4 | This program is free software; you can redistribute it and/or modify | ||||
5 | it under the terms of the GNU General Public License as published by | ||||
6 | the Free Software Foundation; either version 2 of the License, or | ||||
7 | (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, see <http://www.gnu.org/licenses/>. | ||||
16 | *********************************************************************/ | ||||
17 | #include "output.h" | ||||
18 | #include "config.h" | ||||
19 | | ||||
20 | #include "kscreen_daemon_debug.h" | ||||
21 | #include "generator.h" | ||||
22 | | ||||
23 | #include <QStringList> | ||||
24 | #include <QFile> | ||||
25 | #include <QStringBuilder> | ||||
26 | #include <QJsonDocument> | ||||
27 | #include <QDir> | ||||
28 | #include <QLoggingCategory> | ||||
29 | | ||||
30 | #include <kscreen/output.h> | ||||
31 | #include <kscreen/edid.h> | ||||
32 | | ||||
33 | QString Output::s_dirName = QStringLiteral("outputs/"); | ||||
34 | | ||||
35 | QString Output::dirPath() | ||||
36 | { | ||||
37 | return Config::dirPath() % s_dirName; | ||||
38 | } | ||||
39 | | ||||
40 | QString Output::globalFileName(const QString &hash) | ||||
41 | { | ||||
42 | const auto dir = dirPath(); | ||||
43 | if (!QDir().mkpath(dir)) { | ||||
44 | return QString(); | ||||
45 | } | ||||
46 | return dir % hash; | ||||
47 | } | ||||
48 | | ||||
49 | void Output::readInGlobalPartFromInfo(KScreen::OutputPtr output, const QVariantMap &info) | ||||
50 | { | ||||
51 | output->setRotation(static_cast<KScreen::Output::Rotation>(info.value(QStringLiteral("rotation"), 1).toInt())); | ||||
52 | output->setScale(info.value(QStringLiteral("scale"), 1).toInt()); | ||||
53 | | ||||
54 | const QVariantMap modeInfo = info[QStringLiteral("mode")].toMap(); | ||||
55 | const QVariantMap modeSize = modeInfo[QStringLiteral("size")].toMap(); | ||||
56 | const QSize size = QSize(modeSize[QStringLiteral("width")].toInt(), modeSize[QStringLiteral("height")].toInt()); | ||||
57 | | ||||
58 | qCDebug(KSCREEN_KDED) << "Finding a mode for" << size << "@" << modeInfo[QStringLiteral("refresh")].toFloat(); | ||||
59 | | ||||
60 | KScreen::ModeList modes = output->modes(); | ||||
61 | KScreen::ModePtr matchingMode; | ||||
62 | for(const KScreen::ModePtr &mode : modes) { | ||||
63 | if (mode->size() != size) { | ||||
64 | continue; | ||||
65 | } | ||||
66 | if (!qFuzzyCompare(mode->refreshRate(), modeInfo[QStringLiteral("refresh")].toFloat())) { | ||||
67 | continue; | ||||
68 | } | ||||
69 | | ||||
70 | qCDebug(KSCREEN_KDED) << "\tFound: " << mode->id() << " " << mode->size() << "@" << mode->refreshRate(); | ||||
71 | matchingMode = mode; | ||||
72 | break; | ||||
73 | } | ||||
74 | | ||||
75 | if (!matchingMode) { | ||||
76 | qCWarning(KSCREEN_KDED) << "\tFailed to find a matching mode - this means that our config is corrupted" | ||||
77 | "or a different device with the same serial number has been connected (very unlikely)." | ||||
78 | "Falling back to preferred modes."; | ||||
79 | matchingMode = output->preferredMode(); | ||||
80 | } | ||||
81 | if (!matchingMode) { | ||||
82 | qCWarning(KSCREEN_KDED) << "\tFailed to get a preferred mode, falling back to biggest mode."; | ||||
83 | matchingMode = Generator::biggestMode(modes); | ||||
84 | } | ||||
85 | if (!matchingMode) { | ||||
86 | qCWarning(KSCREEN_KDED) << "\tFailed to get biggest mode. Which means there are no modes. Turning off the screen."; | ||||
87 | output->setEnabled(false); | ||||
88 | return; | ||||
89 | } | ||||
90 | | ||||
91 | output->setCurrentModeId(matchingMode->id()); | ||||
92 | } | ||||
93 | | ||||
94 | QVariantMap Output::getGlobalData(KScreen::OutputPtr output) | ||||
95 | { | ||||
96 | QFile file(globalFileName(output->hashMd5())); | ||||
97 | if (!file.open(QIODevice::ReadOnly)) { | ||||
98 | qCDebug(KSCREEN_KDED) << "Failed to open file" << file.fileName(); | ||||
99 | return QVariantMap(); | ||||
100 | } | ||||
101 | QJsonDocument parser; | ||||
102 | return parser.fromJson(file.readAll()).toVariant().toMap(); | ||||
103 | } | ||||
104 | | ||||
105 | bool Output::readInGlobal(KScreen::OutputPtr output) | ||||
106 | { | ||||
107 | const QVariantMap info = getGlobalData(output); | ||||
108 | if (info.empty()) { | ||||
109 | // if info is empty, the global file does not exists, or is in an unreadable state | ||||
110 | return false; | ||||
111 | } | ||||
112 | readInGlobalPartFromInfo(output, info); | ||||
113 | return true; | ||||
114 | } | ||||
115 | | ||||
116 | void Output::readIn(KScreen::OutputPtr output, const QVariantMap &info) | ||||
117 | { | ||||
118 | const QVariantMap posInfo = info[QStringLiteral("pos")].toMap(); | ||||
119 | QPoint point(posInfo[QStringLiteral("x")].toInt(), posInfo[QStringLiteral("y")].toInt()); | ||||
120 | output->setPos(point); | ||||
121 | output->setPrimary(info[QStringLiteral("primary")].toBool()); | ||||
122 | output->setEnabled(info[QStringLiteral("enabled")].toBool()); | ||||
123 | | ||||
124 | if (!readInGlobal(output)) { | ||||
125 | // read in global part from config info instead | ||||
126 | readInGlobalPartFromInfo(output, info); | ||||
127 | } | ||||
128 | } | ||||
129 | | ||||
130 | void Output::readInOutputs(KScreen::OutputList outputs, const QVariantList &outputsInfo) | ||||
131 | { | ||||
132 | // As global outputs are indexed by a hash of their edid, which is not unique, | ||||
133 | // to be able to tell apart multiple identical outputs, these need special treatment | ||||
134 | QStringList duplicateIds; | ||||
135 | { | ||||
136 | QStringList allIds; | ||||
137 | allIds.reserve(outputs.count()); | ||||
138 | for (const KScreen::OutputPtr &output : outputs) { | ||||
139 | const auto outputId = output->hash(); | ||||
140 | if (allIds.contains(outputId) && !duplicateIds.contains(outputId)) { | ||||
141 | duplicateIds << outputId; | ||||
142 | } | ||||
143 | allIds << outputId; | ||||
144 | } | ||||
145 | allIds.clear(); | ||||
146 | } | ||||
147 | | ||||
148 | for (KScreen::OutputPtr output : outputs) { | ||||
149 | if (!output->isConnected()) { | ||||
150 | output->setEnabled(false); | ||||
151 | continue; | ||||
152 | } | ||||
153 | const auto outputId = output->hash(); | ||||
154 | bool infoFound = false; | ||||
155 | for (const auto &variantInfo : outputsInfo) { | ||||
156 | const QVariantMap info = variantInfo.toMap(); | ||||
157 | if (outputId == info[QStringLiteral("id")].toString()) { | ||||
158 | | ||||
159 | // We may have identical outputs connected, these will have the same id in the config | ||||
160 | // in order to find the right one, also check the output's name (usually the connector) | ||||
161 | if (!output->name().isEmpty() && duplicateIds.contains(outputId)) { | ||||
162 | const auto metadata = info[QStringLiteral("metadata")].toMap(); | ||||
163 | const auto outputName = metadata[QStringLiteral("name")].toString(); | ||||
164 | if (output->name() != outputName) { | ||||
165 | infoFound = true; | ||||
166 | readIn(output, info); | ||||
167 | } | ||||
168 | // was a duplicate id, but info not for this output | ||||
169 | continue; | ||||
170 | } | ||||
171 | infoFound = true; | ||||
172 | readIn(output, info); | ||||
173 | } | ||||
174 | } | ||||
175 | if (!infoFound) { | ||||
176 | // no info in info for this output, try reading in global output info atleast or set some default values | ||||
177 | | ||||
178 | qCWarning(KSCREEN_KDED) << "\tFailed to find a matching output in the current info data - this means that our info is corrupted" | ||||
179 | "or a different device with the same serial number has been connected (very unlikely)."; | ||||
180 | if (!readInGlobal(output)) { | ||||
181 | // set some default values instead | ||||
182 | readInGlobalPartFromInfo(output, QVariantMap()); | ||||
183 | } | ||||
184 | } | ||||
185 | } | ||||
186 | } | ||||
187 | | ||||
188 | static QVariantMap metadata(const KScreen::OutputPtr &output) | ||||
189 | { | ||||
190 | QVariantMap metadata; | ||||
191 | metadata[QStringLiteral("name")] = output->name(); | ||||
192 | if (!output->edid() || !output->edid()->isValid()) { | ||||
193 | return metadata; | ||||
194 | } | ||||
195 | | ||||
196 | metadata[QStringLiteral("fullname")] = output->edid()->deviceId(); | ||||
197 | return metadata; | ||||
198 | } | ||||
199 | | ||||
200 | bool Output::writeGlobalPart(const KScreen::OutputPtr &output, QVariantMap &info) | ||||
201 | { | ||||
202 | if (!output->isEnabled()) { | ||||
203 | return false; | ||||
204 | } | ||||
205 | const KScreen::ModePtr mode = output->currentMode(); | ||||
206 | if (!mode) { | ||||
207 | qWarning() << "CurrentMode is null" << output->name(); | ||||
208 | return false; | ||||
209 | } | ||||
210 | | ||||
211 | info[QStringLiteral("id")] = output->hash(); | ||||
212 | info[QStringLiteral("rotation")] = output->rotation(); | ||||
213 | info[QStringLiteral("scale")] = output->scale(); | ||||
214 | info[QStringLiteral("metadata")] = metadata(output); | ||||
215 | | ||||
216 | QVariantMap modeInfo; | ||||
217 | modeInfo[QStringLiteral("refresh")] = mode->refreshRate(); | ||||
218 | | ||||
219 | QVariantMap modeSize; | ||||
220 | modeSize[QStringLiteral("width")] = mode->size().width(); | ||||
221 | modeSize[QStringLiteral("height")] = mode->size().height(); | ||||
222 | modeInfo[QStringLiteral("size")] = modeSize; | ||||
223 | | ||||
224 | info[QStringLiteral("mode")] = modeInfo; | ||||
225 | | ||||
226 | return true; | ||||
227 | } | ||||
228 | | ||||
229 | void Output::writeGlobal(const KScreen::OutputPtr &output) | ||||
230 | { | ||||
231 | // get old values and subsequently override | ||||
232 | QVariantMap info = getGlobalData(output); | ||||
233 | if (!writeGlobalPart(output, info)) { | ||||
234 | return; | ||||
235 | } | ||||
236 | | ||||
237 | QFile file(globalFileName(output->hashMd5())); | ||||
238 | if (!file.open(QIODevice::WriteOnly)) { | ||||
239 | qCWarning(KSCREEN_KDED) << "Failed to open global output file for writing! " << file.errorString(); | ||||
240 | return; | ||||
241 | } | ||||
242 | | ||||
243 | file.write(QJsonDocument::fromVariant(info).toJson()); | ||||
244 | return; | ||||
245 | } |