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