1 /*
2  *  SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
3  *  SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
4  *
5  *  SPDX-License-Identifier: LGPL-2.1-or-later
6  */
7 
8 #include "output.h"
9 #include "abstractbackend.h"
10 #include "backendmanager_p.h"
11 #include "edid.h"
12 #include "kscreen_debug.h"
13 #include "mode.h"
14 
15 #include <QCryptographicHash>
16 #include <QRect>
17 #include <QScopedPointer>
18 #include <QStringList>
19 
20 using namespace KScreen;
21 
22 class Q_DECL_HIDDEN Output::Private
23 {
24 public:
Private()25     Private()
26         : id(0)
27         , type(Unknown)
28         , replicationSource(0)
29         , rotation(None)
30         , scale(1.0)
31         , logicalSize(QSizeF())
32         , connected(false)
33         , enabled(false)
34         , primary(false)
35         , edid(nullptr)
36     {
37     }
38 
Private(const Private & other)39     Private(const Private &other)
40         : id(other.id)
41         , name(other.name)
42         , type(other.type)
43         , icon(other.icon)
44         , clones(other.clones)
45         , replicationSource(other.replicationSource)
46         , currentMode(other.currentMode)
47         , preferredMode(other.preferredMode)
48         , preferredModes(other.preferredModes)
49         , sizeMm(other.sizeMm)
50         , pos(other.pos)
51         , size(other.size)
52         , rotation(other.rotation)
53         , scale(other.scale)
54         , connected(other.connected)
55         , enabled(other.enabled)
56         , primary(other.primary)
57         , followPreferredMode(other.followPreferredMode)
58         , capabilities(other.capabilities)
59         , overscan(other.overscan)
60         , vrrPolicy(other.vrrPolicy)
61         , rgbRange(other.rgbRange)
62     {
63         const auto otherModeList = other.modeList;
64         for (const ModePtr &otherMode : otherModeList) {
65             modeList.insert(otherMode->id(), otherMode->clone());
66         }
67         if (other.edid) {
68             edid.reset(other.edid->clone());
69         }
70     }
71 
72     QString biggestMode(const ModeList &modes) const;
73     bool compareModeList(const ModeList &before, const ModeList &after);
74 
75     int id;
76     QString name;
77     Type type;
78     QString icon;
79     ModeList modeList;
80     QList<int> clones;
81     int replicationSource;
82     QString currentMode;
83     QString preferredMode;
84     QStringList preferredModes;
85     QSize sizeMm;
86     QPoint pos;
87     QSize size;
88     Rotation rotation;
89     qreal scale;
90     QSizeF logicalSize;
91     bool connected;
92     bool enabled;
93     bool primary;
94     bool followPreferredMode = false;
95     Capabilities capabilities;
96     uint32_t overscan = 0;
97     VrrPolicy vrrPolicy = VrrPolicy::Automatic;
98     RgbRange rgbRange = RgbRange::Automatic;
99 
100     QScopedPointer<Edid> edid;
101 };
102 
compareModeList(const ModeList & before,const ModeList & after)103 bool Output::Private::compareModeList(const ModeList &before, const ModeList &after)
104 {
105     if (before.count() != after.count()) {
106         return false;
107     }
108 
109     for (auto itb = before.constBegin(); itb != before.constEnd(); ++itb) {
110         auto ita = after.constFind(itb.key());
111         if (ita == after.constEnd()) {
112             return false;
113         }
114         const auto &mb = itb.value();
115         const auto &ma = ita.value();
116         if (mb->id() != ma->id()) {
117             return false;
118         }
119         if (mb->size() != ma->size()) {
120             return false;
121         }
122         if (!qFuzzyCompare(mb->refreshRate(), ma->refreshRate())) {
123             return false;
124         }
125         if (mb->name() != ma->name()) {
126             return false;
127         }
128     }
129     // They're the same
130     return true;
131 }
132 
biggestMode(const ModeList & modes) const133 QString Output::Private::biggestMode(const ModeList &modes) const
134 {
135     int area, total = 0;
136     KScreen::ModePtr biggest;
137     for (const KScreen::ModePtr &mode : modes) {
138         area = mode->size().width() * mode->size().height();
139         if (area < total) {
140             continue;
141         }
142         if (area == total && mode->refreshRate() < biggest->refreshRate()) {
143             continue;
144         }
145         if (area == total && mode->refreshRate() > biggest->refreshRate()) {
146             biggest = mode;
147             continue;
148         }
149 
150         total = area;
151         biggest = mode;
152     }
153 
154     if (!biggest) {
155         return QString();
156     }
157 
158     return biggest->id();
159 }
160 
Output()161 Output::Output()
162     : QObject(nullptr)
163     , d(new Private())
164 {
165 }
166 
Output(Output::Private * dd)167 Output::Output(Output::Private *dd)
168     : QObject()
169     , d(dd)
170 {
171 }
172 
~Output()173 Output::~Output()
174 {
175     delete d;
176 }
177 
clone() const178 OutputPtr Output::clone() const
179 {
180     return OutputPtr(new Output(new Private(*d)));
181 }
182 
id() const183 int Output::id() const
184 {
185     return d->id;
186 }
187 
setId(int id)188 void Output::setId(int id)
189 {
190     if (d->id == id) {
191         return;
192     }
193 
194     d->id = id;
195 
196     Q_EMIT outputChanged();
197 }
198 
name() const199 QString Output::name() const
200 {
201     return d->name;
202 }
203 
setName(const QString & name)204 void Output::setName(const QString &name)
205 {
206     if (d->name == name) {
207         return;
208     }
209 
210     d->name = name;
211 
212     Q_EMIT outputChanged();
213 }
214 
215 // TODO KF6: remove this deprecated method
hash() const216 QString Output::hash() const
217 {
218     if (edid() && edid()->isValid()) {
219         return edid()->hash();
220     }
221     return name();
222 }
223 
hashMd5() const224 QString Output::hashMd5() const
225 {
226     if (edid() && edid()->isValid()) {
227         return edid()->hash();
228     }
229     const auto hash = QCryptographicHash::hash(name().toLatin1(), QCryptographicHash::Md5);
230     return QString::fromLatin1(hash.toHex());
231 }
232 
type() const233 Output::Type Output::type() const
234 {
235     return d->type;
236 }
237 
setType(Type type)238 void Output::setType(Type type)
239 {
240     if (d->type == type) {
241         return;
242     }
243 
244     d->type = type;
245 
246     Q_EMIT outputChanged();
247 }
248 
icon() const249 QString Output::icon() const
250 {
251     return d->icon;
252 }
253 
setIcon(const QString & icon)254 void Output::setIcon(const QString &icon)
255 {
256     if (d->icon == icon) {
257         return;
258     }
259 
260     d->icon = icon;
261 
262     Q_EMIT outputChanged();
263 }
264 
mode(const QString & id) const265 ModePtr Output::mode(const QString &id) const
266 {
267     if (!d->modeList.contains(id)) {
268         return ModePtr();
269     }
270 
271     return d->modeList[id];
272 }
273 
modes() const274 ModeList Output::modes() const
275 {
276     return d->modeList;
277 }
278 
setModes(const ModeList & modes)279 void Output::setModes(const ModeList &modes)
280 {
281     bool changed = !d->compareModeList(d->modeList, modes);
282     d->modeList = modes;
283     if (changed) {
284         Q_EMIT modesChanged();
285         Q_EMIT outputChanged();
286     }
287 }
288 
currentModeId() const289 QString Output::currentModeId() const
290 {
291     return d->currentMode;
292 }
293 
setCurrentModeId(const QString & mode)294 void Output::setCurrentModeId(const QString &mode)
295 {
296     if (d->currentMode == mode) {
297         return;
298     }
299 
300     d->currentMode = mode;
301 
302     Q_EMIT currentModeIdChanged();
303 }
304 
currentMode() const305 ModePtr Output::currentMode() const
306 {
307     return d->modeList.value(d->currentMode);
308 }
309 
setPreferredModes(const QStringList & modes)310 void Output::setPreferredModes(const QStringList &modes)
311 {
312     d->preferredMode = QString();
313     d->preferredModes = modes;
314 }
315 
preferredModes() const316 QStringList Output::preferredModes() const
317 {
318     return d->preferredModes;
319 }
320 
preferredModeId() const321 QString Output::preferredModeId() const
322 {
323     if (!d->preferredMode.isEmpty()) {
324         return d->preferredMode;
325     }
326     if (d->preferredModes.isEmpty()) {
327         return d->biggestMode(modes());
328     }
329 
330     int total = 0;
331     KScreen::ModePtr biggest;
332     KScreen::ModePtr candidateMode;
333     for (const QString &modeId : qAsConst(d->preferredModes)) {
334         candidateMode = mode(modeId);
335         const int area = candidateMode->size().width() * candidateMode->size().height();
336         if (area < total) {
337             continue;
338         }
339         if (area == total && biggest && candidateMode->refreshRate() < biggest->refreshRate()) {
340             continue;
341         }
342         if (area == total && biggest && candidateMode->refreshRate() > biggest->refreshRate()) {
343             biggest = candidateMode;
344             continue;
345         }
346 
347         total = area;
348         biggest = candidateMode;
349     }
350 
351     Q_ASSERT_X(biggest, "preferredModeId", "biggest mode must exist");
352 
353     d->preferredMode = biggest->id();
354     return d->preferredMode;
355 }
356 
preferredMode() const357 ModePtr Output::preferredMode() const
358 {
359     return d->modeList.value(preferredModeId());
360 }
361 
pos() const362 QPoint Output::pos() const
363 {
364     return d->pos;
365 }
366 
setPos(const QPoint & pos)367 void Output::setPos(const QPoint &pos)
368 {
369     if (d->pos == pos) {
370         return;
371     }
372 
373     d->pos = pos;
374 
375     Q_EMIT posChanged();
376 }
377 
size() const378 QSize Output::size() const
379 {
380     return d->size;
381 }
382 
setSize(const QSize & size)383 void Output::setSize(const QSize &size)
384 {
385     if (d->size == size) {
386         return;
387     }
388 
389     d->size = size;
390 
391     Q_EMIT sizeChanged();
392 }
393 
394 // TODO KF6: make the Rotation enum an enum class and align values with Wayland transformation property
rotation() const395 Output::Rotation Output::rotation() const
396 {
397     return d->rotation;
398 }
399 
setRotation(Output::Rotation rotation)400 void Output::setRotation(Output::Rotation rotation)
401 {
402     if (d->rotation == rotation) {
403         return;
404     }
405 
406     d->rotation = rotation;
407 
408     Q_EMIT rotationChanged();
409 }
410 
scale() const411 qreal Output::scale() const
412 {
413     return d->scale;
414 }
415 
setScale(qreal factor)416 void Output::setScale(qreal factor)
417 {
418     if (qFuzzyCompare(d->scale, factor)) {
419         return;
420     }
421     d->scale = factor;
422     Q_EMIT scaleChanged();
423 }
424 
logicalSize() const425 QSizeF Output::logicalSize() const
426 {
427     if (d->logicalSize.isValid()) {
428         return d->logicalSize;
429     }
430 
431     QSizeF size = enforcedModeSize();
432     if (!size.isValid()) {
433         return QSizeF();
434     }
435     size = size / d->scale;
436 
437     // We can't use d->size, because d->size does not reflect the actual rotation() set by caller.
438     // It is only updated when we get update from KScreen, but not when user changes mode or
439     // rotation manually.
440 
441     if (!isHorizontal()) {
442         size = size.transposed();
443     }
444     return size;
445 }
446 
explicitLogicalSize() const447 QSizeF Output::explicitLogicalSize() const
448 {
449     return d->logicalSize;
450 }
451 
setLogicalSize(const QSizeF & size)452 void Output::setLogicalSize(const QSizeF &size)
453 {
454     if (qFuzzyCompare(d->logicalSize.width(), size.width()) && qFuzzyCompare(d->logicalSize.height(), size.height())) {
455         return;
456     }
457     d->logicalSize = size;
458     Q_EMIT logicalSizeChanged();
459 }
460 
isConnected() const461 bool Output::isConnected() const
462 {
463     return d->connected;
464 }
465 
setConnected(bool connected)466 void Output::setConnected(bool connected)
467 {
468     if (d->connected == connected) {
469         return;
470     }
471 
472     d->connected = connected;
473 
474     Q_EMIT isConnectedChanged();
475 }
476 
isEnabled() const477 bool Output::isEnabled() const
478 {
479     return d->enabled;
480 }
481 
setEnabled(bool enabled)482 void Output::setEnabled(bool enabled)
483 {
484     if (d->enabled == enabled) {
485         return;
486     }
487 
488     d->enabled = enabled;
489 
490     Q_EMIT isEnabledChanged();
491 }
492 
isPrimary() const493 bool Output::isPrimary() const
494 {
495     return d->primary;
496 }
497 
setPrimary(bool primary)498 void Output::setPrimary(bool primary)
499 {
500     if (d->primary == primary) {
501         return;
502     }
503 
504     d->primary = primary;
505 
506     Q_EMIT isPrimaryChanged();
507 }
508 
clones() const509 QList<int> Output::clones() const
510 {
511     return d->clones;
512 }
513 
setClones(const QList<int> & outputlist)514 void Output::setClones(const QList<int> &outputlist)
515 {
516     if (d->clones == outputlist) {
517         return;
518     }
519 
520     d->clones = outputlist;
521 
522     Q_EMIT clonesChanged();
523 }
524 
replicationSource() const525 int Output::replicationSource() const
526 {
527     return d->replicationSource;
528 }
529 
setReplicationSource(int source)530 void Output::setReplicationSource(int source)
531 {
532     if (d->replicationSource == source) {
533         return;
534     }
535 
536     d->replicationSource = source;
537 
538     Q_EMIT replicationSourceChanged();
539 }
540 
setEdid(const QByteArray & rawData)541 void Output::setEdid(const QByteArray &rawData)
542 {
543     Q_ASSERT(d->edid.isNull());
544     d->edid.reset(new Edid(rawData));
545 }
546 
edid() const547 Edid *Output::edid() const
548 {
549     return d->edid.data();
550 }
551 
sizeMm() const552 QSize Output::sizeMm() const
553 {
554     return d->sizeMm;
555 }
556 
setSizeMm(const QSize & size)557 void Output::setSizeMm(const QSize &size)
558 {
559     d->sizeMm = size;
560 }
561 
followPreferredMode() const562 bool KScreen::Output::followPreferredMode() const
563 {
564     return d->followPreferredMode;
565 }
566 
setFollowPreferredMode(bool follow)567 void KScreen::Output::setFollowPreferredMode(bool follow)
568 {
569     if (follow != d->followPreferredMode) {
570         d->followPreferredMode = follow;
571         Q_EMIT followPreferredModeChanged(follow);
572     }
573 }
574 
isPositionable() const575 bool Output::isPositionable() const
576 {
577     return isConnected() && isEnabled() && !replicationSource();
578 }
579 
enforcedModeSize() const580 QSize Output::enforcedModeSize() const
581 {
582     if (const auto mode = currentMode()) {
583         return mode->size();
584     } else if (const auto mode = preferredMode()) {
585         return mode->size();
586     } else if (d->modeList.count() > 0) {
587         return d->modeList.first()->size();
588     }
589     return QSize();
590 }
591 
geometry() const592 QRect Output::geometry() const
593 {
594     QSize size = logicalSize().toSize();
595     if (!size.isValid()) {
596         return QRect();
597     }
598 
599     return QRect(d->pos, size);
600 }
601 
capabilities() const602 Output::Capabilities Output::capabilities() const
603 {
604     return d->capabilities;
605 }
606 
setCapabilities(Capabilities capabilities)607 void Output::setCapabilities(Capabilities capabilities)
608 {
609     if (d->capabilities != capabilities) {
610         d->capabilities = capabilities;
611         Q_EMIT capabilitiesChanged();
612     }
613 }
614 
overscan() const615 uint32_t Output::overscan() const
616 {
617     return d->overscan;
618 }
619 
setOverscan(uint32_t overscan)620 void Output::setOverscan(uint32_t overscan)
621 {
622     if (d->overscan != overscan) {
623         d->overscan = overscan;
624         Q_EMIT overscanChanged();
625     }
626 }
627 
vrrPolicy() const628 Output::VrrPolicy Output::vrrPolicy() const
629 {
630     return d->vrrPolicy;
631 }
632 
setVrrPolicy(VrrPolicy policy)633 void Output::setVrrPolicy(VrrPolicy policy)
634 {
635     if (d->vrrPolicy != policy) {
636         d->vrrPolicy = policy;
637         Q_EMIT vrrPolicyChanged();
638     }
639 }
640 
rgbRange() const641 Output::RgbRange Output::rgbRange() const
642 {
643     return d->rgbRange;
644 }
645 
setRgbRange(Output::RgbRange rgbRange)646 void Output::setRgbRange(Output::RgbRange rgbRange)
647 {
648     if (d->rgbRange != rgbRange) {
649         d->rgbRange = rgbRange;
650         Q_EMIT rgbRangeChanged();
651     }
652 }
653 
apply(const OutputPtr & other)654 void Output::apply(const OutputPtr &other)
655 {
656     typedef void (KScreen::Output::*ChangeSignal)();
657     QList<ChangeSignal> changes;
658 
659     // We block all signals, and emit them only after we have set up everything
660     // This is necessary in order to prevent clients from accessing inconsistent
661     // outputs from intermediate change signals
662     const bool keepBlocked = signalsBlocked();
663     blockSignals(true);
664     if (d->name != other->d->name) {
665         changes << &Output::outputChanged;
666         setName(other->d->name);
667     }
668     if (d->type != other->d->type) {
669         changes << &Output::outputChanged;
670         setType(other->d->type);
671     }
672     if (d->icon != other->d->icon) {
673         changes << &Output::outputChanged;
674         setIcon(other->d->icon);
675     }
676     if (d->pos != other->d->pos) {
677         changes << &Output::posChanged;
678         setPos(other->pos());
679     }
680     if (d->rotation != other->d->rotation) {
681         changes << &Output::rotationChanged;
682         setRotation(other->d->rotation);
683     }
684     if (!qFuzzyCompare(d->scale, other->d->scale)) {
685         changes << &Output::scaleChanged;
686         setScale(other->d->scale);
687     }
688     if (d->currentMode != other->d->currentMode) {
689         changes << &Output::currentModeIdChanged;
690         setCurrentModeId(other->d->currentMode);
691     }
692     if (d->connected != other->d->connected) {
693         changes << &Output::isConnectedChanged;
694         setConnected(other->d->connected);
695     }
696     if (d->enabled != other->d->enabled) {
697         changes << &Output::isEnabledChanged;
698         setEnabled(other->d->enabled);
699     }
700     if (d->primary != other->d->primary) {
701         changes << &Output::isPrimaryChanged;
702         setPrimary(other->d->primary);
703     }
704     if (d->clones != other->d->clones) {
705         changes << &Output::clonesChanged;
706         setClones(other->d->clones);
707     }
708     if (d->replicationSource != other->d->replicationSource) {
709         changes << &Output::replicationSourceChanged;
710         setReplicationSource(other->d->replicationSource);
711     }
712     if (!d->compareModeList(d->modeList, other->d->modeList)) {
713         changes << &Output::outputChanged;
714         changes << &Output::modesChanged;
715     }
716 
717     setPreferredModes(other->d->preferredModes);
718     ModeList modes;
719     for (const ModePtr &otherMode : other->modes()) {
720         modes.insert(otherMode->id(), otherMode->clone());
721     }
722     setModes(modes);
723 
724     if (d->capabilities != other->d->capabilities) {
725         changes << &Output::capabilitiesChanged;
726         setCapabilities(other->d->capabilities);
727     }
728     if (d->vrrPolicy != other->d->vrrPolicy) {
729         changes << &Output::vrrPolicyChanged;
730         setVrrPolicy(other->d->vrrPolicy);
731     }
732     if (d->overscan != other->d->overscan) {
733         changes << &Output::overscanChanged;
734         setOverscan(other->d->overscan);
735     }
736     if (d->rgbRange != other->d->rgbRange) {
737         changes << &Output::rgbRangeChanged;
738         setRgbRange(other->d->rgbRange);
739     }
740 
741     // Non-notifyable changes
742     if (other->d->edid) {
743         d->edid.reset(other->d->edid->clone());
744     }
745 
746     blockSignals(keepBlocked);
747 
748     while (!changes.isEmpty()) {
749         const ChangeSignal &sig = changes.first();
750         Q_EMIT(this->*sig)();
751         changes.removeAll(sig);
752     }
753 }
754 
operator <<(QDebug dbg,const KScreen::OutputPtr & output)755 QDebug operator<<(QDebug dbg, const KScreen::OutputPtr &output)
756 {
757     QDebugStateSaver saver(dbg);
758     if (!output) {
759         dbg << "KScreen::Output(NULL)";
760         return dbg;
761     }
762 
763     dbg.nospace() << "KScreen::Output(" << output->id() << ", " << output->name() << ", " << (output->isConnected() ? "connected " : "disconnected ")
764                   << (output->isEnabled() ? "enabled" : "disabled") << (output->isPrimary() ? " primary" : "") << ", pos: " << output->pos()
765                   << ", res: " << output->size() << ", modeId: " << output->currentModeId() << ", scale: " << output->scale()
766                   << ", clone: " << (output->clones().isEmpty() ? "no" : "yes") << ", rotation: " << output->rotation()
767                   << ", followPreferredMode: " << output->followPreferredMode() << ")";
768     return dbg;
769 }
770