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