1 /*************************************************************************************
2 * Copyright (C) 2012 by Alejandro Fiestas Olivares <afiestas@kde.org> *
3 * Copyright (C) 2014 by Daniel Vrátil <dvratil@redhat.com> *
4 * *
5 * This library is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU Lesser General Public *
7 * License as published by the Free Software Foundation; either *
8 * version 2.1 of the License, or (at your option) any later version. *
9 * *
10 * This library is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
13 * Lesser General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU Lesser General Public *
16 * License along with this library; if not, write to the Free Software *
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
18 *************************************************************************************/
19 #include "config.h"
20 #include "backend.h"
21 #include "backendmanager_p.h"
22 #include "disman_debug.h"
23 #include "output.h"
24
25 #include <QCryptographicHash>
26 #include <QDebug>
27 #include <QRect>
28 #include <QStringList>
29
30 #include <sstream>
31
32 using namespace Disman;
33
34 class Q_DECL_HIDDEN Config::Private : public QObject
35 {
36 Q_OBJECT
37 public:
Private(Config * parent,Cause cause)38 Private(Config* parent, Cause cause)
39 : QObject(parent)
40 , valid(true)
41 , supported_features(Config::Feature::None)
42 , tablet_mode_available(false)
43 , tablet_mode_engaged(false)
44 , cause{cause}
45 , q(parent)
46 {
47 }
48
remove_output(OutputMap::iterator iter)49 auto remove_output(OutputMap::iterator iter)
50 {
51 if (iter == outputs.end()) {
52 return iter;
53 }
54
55 OutputPtr output = iter->second;
56 if (!output) {
57 return outputs.erase(iter);
58 }
59
60 auto const outputId = iter->first;
61 iter = outputs.erase(iter);
62
63 if (primary_output == output) {
64 q->set_primary_output(OutputPtr());
65 }
66 output->disconnect(q);
67
68 Q_EMIT q->output_removed(outputId);
69
70 return iter;
71 }
72
73 bool valid;
74 ScreenPtr screen;
75 OutputPtr primary_output;
76 OutputMap outputs;
77 Features supported_features;
78 bool tablet_mode_available;
79 bool tablet_mode_engaged;
80 Cause cause;
81
82 private:
83 Config* q;
84 };
85
can_be_applied(const ConfigPtr & config)86 bool Config::can_be_applied(const ConfigPtr& config)
87 {
88 return can_be_applied(config, ValidityFlag::None);
89 }
90
can_be_applied(const ConfigPtr & config,ValidityFlags flags)91 bool Config::can_be_applied(const ConfigPtr& config, ValidityFlags flags)
92 {
93 if (!config) {
94 qCDebug(DISMAN) << "can_be_applied: Config not available, returning false";
95 return false;
96 }
97 ConfigPtr currentConfig = BackendManager::instance()->config();
98 if (!currentConfig) {
99 qCDebug(DISMAN) << "can_be_applied: Current config not available, returning false";
100 return false;
101 }
102
103 QRect rect;
104 OutputPtr currentOutput;
105 int enabledOutputsCount = 0;
106
107 for (auto const& [key, output] : config->outputs()) {
108 if (!output->enabled()) {
109 continue;
110 }
111
112 ++enabledOutputsCount;
113
114 currentOutput = currentConfig->output(output->id());
115 // If there is no such output
116 if (!currentOutput) {
117 qCDebug(DISMAN) << "can_be_applied: The output:" << output->id() << "does not exists";
118 return false;
119 }
120
121 // if there is no currentMode
122 if (!output->auto_mode()) {
123 qCDebug(DISMAN) << "can_be_applied: The output:" << output->id()
124 << "has no currentModeId";
125 return false;
126 }
127 // If the mode is not found in the current output
128 if (!currentOutput->mode(output->auto_mode()->id())) {
129 qCDebug(DISMAN) << "can_be_applied: The output:" << output->id()
130 << "has no mode:" << output->auto_mode()->id().c_str();
131 return false;
132 }
133
134 auto const outputSize = output->auto_mode()->size();
135
136 if (output->position().x() < rect.x()) {
137 rect.setX(output->position().x());
138 }
139
140 if (output->position().y() < rect.y()) {
141 rect.setY(output->position().y());
142 }
143
144 QPoint bottomRight;
145 if (output->horizontal()) {
146 bottomRight = QPoint(output->position().x() + outputSize.width(),
147 output->position().y() + outputSize.height());
148 } else {
149 bottomRight = QPoint(output->position().x() + outputSize.height(),
150 output->position().y() + outputSize.width());
151 }
152
153 if (bottomRight.x() > rect.width()) {
154 rect.setWidth(bottomRight.x());
155 }
156
157 if (bottomRight.y() > rect.height()) {
158 rect.setHeight(bottomRight.y());
159 }
160 }
161
162 if (flags & ValidityFlag::RequireAtLeastOneEnabledScreen && enabledOutputsCount == 0) {
163 qCDebug(DISMAN) << "canBeAppled: There are no enabled screens, at least one required";
164 return false;
165 }
166
167 const int maxEnabledOutputsCount = config->screen()->max_outputs_count();
168 if (enabledOutputsCount > maxEnabledOutputsCount) {
169 qCDebug(DISMAN) << "can_be_applied: Too many active screens. Requested: "
170 << enabledOutputsCount << ", Max: " << maxEnabledOutputsCount;
171 return false;
172 }
173
174 // TODO: Why we do this again? Isn't the screen size determined by the outer rect of all
175 // outputs? At least it's that way in the Wayland case.
176 if (rect.width() > config->screen()->max_size().width()) {
177 qCDebug(DISMAN) << "can_be_applied: The configuration is too wide:" << rect.width();
178 return false;
179 }
180 if (rect.height() > config->screen()->max_size().height()) {
181 qCDebug(DISMAN) << "can_be_applied: The configuration is too high:" << rect.height();
182 return false;
183 }
184
185 return true;
186 }
187
Config()188 Config::Config()
189 : Config(Cause::unknown)
190 {
191 }
192
Config(Cause cause)193 Config::Config(Cause cause)
194 : QObject(nullptr)
195 , d(new Private(this, cause))
196 {
197 }
198
~Config()199 Config::~Config()
200 {
201 delete d;
202 }
203
clone() const204 ConfigPtr Config::clone() const
205 {
206 ConfigPtr newConfig(new Config(cause()));
207 newConfig->d->screen = d->screen->clone();
208
209 for (auto const& [key, ourOutput] : d->outputs) {
210 auto cloned_output = ourOutput->clone();
211 newConfig->add_output(cloned_output);
212
213 if (d->primary_output == ourOutput) {
214 newConfig->set_primary_output(cloned_output);
215 }
216 }
217
218 newConfig->set_supported_features(supported_features());
219 newConfig->set_tablet_mode_available(tablet_mode_available());
220 newConfig->set_tablet_mode_engaged(tablet_mode_engaged());
221
222 return newConfig;
223 }
224
compare(ConfigPtr config) const225 bool Config::compare(ConfigPtr config) const
226 {
227 if (!config) {
228 return false;
229 }
230
231 auto const simple_data_compare = d->valid == config->d->valid && hash() == config->hash()
232 && d->supported_features == config->d->supported_features
233 && d->tablet_mode_available == config->d->tablet_mode_available
234 && d->tablet_mode_engaged == config->d->tablet_mode_engaged && d->cause == config->d->cause;
235
236 if (!simple_data_compare) {
237 return false;
238 }
239
240 auto other_screen = config->screen();
241 if (d->screen) {
242 if (!other_screen || !d->screen->compare(other_screen)) {
243 return false;
244 }
245 } else if (other_screen) {
246 return false;
247 }
248
249 auto other_primary = config->primary_output();
250 if (d->primary_output) {
251 if (!other_primary || d->primary_output->id() != other_primary->id()) {
252 return false;
253 }
254 } else if (other_primary) {
255 return false;
256 }
257
258 // Check that we have all outputs of the other config.
259 for (auto const& [key, output] : config->d->outputs) {
260 if (!this->output(output->id())) {
261 return false;
262 }
263 }
264 // Check that the other config has also all our outputs and that these have the same data.
265 for (auto const& [key, output] : d->outputs) {
266 auto other_output = config->output(output->id());
267 if (!other_output || !output->compare(other_output)) {
268 return false;
269 }
270 }
271 return true;
272 }
273
hash() const274 QString Config::hash() const
275 {
276 QStringList hashedOutputs;
277 for (auto const& [key, output] : d->outputs) {
278 hashedOutputs << QString::fromStdString(output->hash());
279 }
280
281 std::sort(hashedOutputs.begin(), hashedOutputs.end());
282 const auto hash = QCryptographicHash::hash(hashedOutputs.join(QString()).toLatin1(),
283 QCryptographicHash::Md5);
284 return QString::fromLatin1(hash.toHex());
285 }
286
cause() const287 Config::Cause Config::cause() const
288 {
289 return d->cause;
290 }
291
set_cause(Cause cause)292 void Config::set_cause(Cause cause)
293 {
294 d->cause = cause;
295 }
296
screen() const297 ScreenPtr Config::screen() const
298 {
299 return d->screen;
300 }
301
setScreen(const ScreenPtr & screen)302 void Config::setScreen(const ScreenPtr& screen)
303 {
304 d->screen = screen;
305 }
306
output(int outputId) const307 OutputPtr Config::output(int outputId) const
308 {
309 if (auto out = d->outputs.find(outputId); out != d->outputs.end()) {
310 return out->second;
311 }
312 return nullptr;
313 }
314
supported_features() const315 Config::Features Config::supported_features() const
316 {
317 return d->supported_features;
318 }
319
set_supported_features(const Config::Features & features)320 void Config::set_supported_features(const Config::Features& features)
321 {
322 d->supported_features = features;
323 }
324
tablet_mode_available() const325 bool Config::tablet_mode_available() const
326 {
327 return d->tablet_mode_available;
328 }
329
set_tablet_mode_available(bool available)330 void Config::set_tablet_mode_available(bool available)
331 {
332 d->tablet_mode_available = available;
333 }
334
tablet_mode_engaged() const335 bool Config::tablet_mode_engaged() const
336 {
337 return d->tablet_mode_engaged;
338 }
339
set_tablet_mode_engaged(bool engaged)340 void Config::set_tablet_mode_engaged(bool engaged)
341 {
342 d->tablet_mode_engaged = engaged;
343 }
344
outputs() const345 OutputMap Config::outputs() const
346 {
347 return d->outputs;
348 }
349
primary_output() const350 OutputPtr Config::primary_output() const
351 {
352 return d->primary_output;
353 }
354
set_primary_output(const OutputPtr & newPrimary)355 void Config::set_primary_output(const OutputPtr& newPrimary)
356 {
357 if (d->primary_output == newPrimary) {
358 return;
359 }
360
361 d->primary_output = newPrimary;
362 Q_EMIT primary_output_changed(newPrimary);
363 }
364
replication_source(OutputPtr const & output)365 OutputPtr Config::replication_source(OutputPtr const& output)
366 {
367 if (auto source_id = output->replication_source()) {
368 for (auto const& [key, output] : d->outputs) {
369 if (output->id() == source_id) {
370 return output;
371 }
372 }
373 }
374 return nullptr;
375 }
376
add_output(const OutputPtr & output)377 void Config::add_output(const OutputPtr& output)
378 {
379 d->outputs.insert({output->id(), output});
380
381 Q_EMIT output_added(output);
382 }
383
remove_output(int outputId)384 void Config::remove_output(int outputId)
385 {
386 d->remove_output(d->outputs.find(outputId));
387 }
388
set_outputs(OutputMap const & outputs)389 void Config::set_outputs(OutputMap const& outputs)
390 {
391 auto primary = primary_output();
392 for (auto iter = d->outputs.begin(), end = d->outputs.end(); iter != end;) {
393 iter = d->remove_output(iter);
394 end = d->outputs.end();
395 }
396
397 for (auto const& [key, output] : outputs) {
398 add_output(output);
399 if (primary && primary->id() == output->id()) {
400 set_primary_output(output);
401 primary = nullptr;
402 }
403 }
404 }
405
valid() const406 bool Config::valid() const
407 {
408 return d->valid;
409 }
410
set_valid(bool valid)411 void Config::set_valid(bool valid)
412 {
413 d->valid = valid;
414 }
415
apply(const ConfigPtr & other)416 void Config::apply(const ConfigPtr& other)
417 {
418 d->screen->apply(other->screen());
419
420 // Remove removed outputs
421 for (auto iter = d->outputs.begin(), end = d->outputs.end(); iter != end;) {
422 if (other->d->outputs.find(iter->second->id()) == other->d->outputs.end()) {
423 iter = d->remove_output(iter);
424 end = d->outputs.end();
425 } else {
426 iter++;
427 }
428 }
429
430 for (auto const& [key, otherOutput] : other->d->outputs) {
431 // Add new outputs
432 OutputPtr output;
433 auto primary
434 = other->primary_output() && other->primary_output()->id() == otherOutput->id();
435
436 if (d->outputs.find(otherOutput->id()) == d->outputs.end()) {
437 output = otherOutput->clone();
438 add_output(output);
439 } else {
440 // Update existing outputs
441 output = d->outputs[otherOutput->id()];
442 output->apply(otherOutput);
443 }
444 if (primary) {
445 set_primary_output(output);
446 }
447 }
448
449 // Update validity
450 set_valid(other->valid());
451 set_cause(other->cause());
452 }
453
log() const454 std::string Config::log() const
455 {
456 auto print_cause = [](Config::Cause cause) -> std::string {
457 switch (cause) {
458 case Config::Cause::unknown:
459 return "unknown";
460 case Config::Cause::file:
461 return "file";
462 case Config::Cause::generated:
463 return "generated";
464 case Config::Cause::interactive:
465 return "interactive";
466 default:
467 return "ill-defined";
468 }
469 };
470
471 std::stringstream ss;
472
473 ss << " Disman::Config {";
474 ss << " cause: " << print_cause(cause());
475 if (auto primary = primary_output()) {
476 ss << ", primary: " << primary->id();
477 }
478 if (tablet_mode_available()) {
479 ss << " tablet-mode: " << (tablet_mode_engaged() ? "engaged" : "disengaged");
480 }
481 for (auto const& [key, output] : outputs()) {
482 auto log = std::istringstream(output->log());
483 std::string line;
484 while (std::getline(log, line)) {
485 ss << std::endl << " " << line;
486 }
487 }
488 ss << std::endl << " }";
489 return ss.str();
490 }
491
operator <<(QDebug dbg,const Disman::ConfigPtr & config)492 QDebug operator<<(QDebug dbg, const Disman::ConfigPtr& config)
493 {
494 if (config) {
495 dbg << Qt::endl << config->log().c_str();
496 } else {
497 dbg << "Disman::Config {null}";
498 }
499 return dbg;
500 }
501
502 #include "config.moc"
503