File indexing completed on 2024-04-28 09:26:09

0001 /*
0002  *  SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
0003  *  SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
0004  *
0005  *  SPDX-License-Identifier: LGPL-2.1-or-later
0006  */
0007 
0008 #include "output.h"
0009 #include "edid.h"
0010 #include "kscreen_debug.h"
0011 #include "mode.h"
0012 
0013 #include <QCryptographicHash>
0014 #include <QGuiApplication>
0015 #include <QRect>
0016 #include <QScopedPointer>
0017 
0018 #include <cstdint>
0019 #include <qobjectdefs.h>
0020 #include <utility>
0021 
0022 using namespace KScreen;
0023 
0024 class Q_DECL_HIDDEN Output::Private
0025 {
0026 public:
0027     Private()
0028         : id(0)
0029         , type(Unknown)
0030         , rotation(None)
0031         , connected(false)
0032         , enabled(false)
0033         , priority(0)
0034         , replicationSource(0)
0035         , edid(nullptr)
0036         , scale(1.0)
0037         , explicitLogicalSize(QSizeF())
0038     {
0039     }
0040 
0041     Private(const Private &other)
0042         : id(other.id)
0043         , name(other.name)
0044         , type(other.type)
0045         , icon(other.icon)
0046         , pos(other.pos)
0047         , size(other.size)
0048         , rotation(other.rotation)
0049         , currentMode(other.currentMode)
0050         , preferredMode(other.preferredMode)
0051         , preferredModes(other.preferredModes)
0052         , connected(other.connected)
0053         , enabled(other.enabled)
0054         , priority(other.priority)
0055         , clones(other.clones)
0056         , replicationSource(other.replicationSource)
0057         , sizeMm(other.sizeMm)
0058         , scale(other.scale)
0059         , followPreferredMode(other.followPreferredMode)
0060         , explicitLogicalSize(other.explicitLogicalSize)
0061         , capabilities(other.capabilities)
0062         , overscan(other.overscan)
0063         , vrrPolicy(other.vrrPolicy)
0064         , rgbRange(other.rgbRange)
0065         , highDynamicRange(other.highDynamicRange)
0066         , sdrBrightness(other.sdrBrightness)
0067         , wideColorGamut(other.wideColorGamut)
0068         , autoRotatePolicy(other.autoRotatePolicy)
0069         , iccProfilePath(other.iccProfilePath)
0070         , sdrGamutWideness(other.sdrGamutWideness)
0071         , maxPeakBrightness(other.maxPeakBrightness)
0072         , maxAverageBrightness(other.maxAverageBrightness)
0073         , minBrightness(other.minBrightness)
0074         , maxPeakBrightnessOverride(other.maxPeakBrightnessOverride)
0075         , maxAverageBrightnessOverride(other.maxAverageBrightnessOverride)
0076         , minBrightnessOverride(other.minBrightnessOverride)
0077     {
0078         const auto otherModeList = other.modeList;
0079         for (const ModePtr &otherMode : otherModeList) {
0080             modeList.insert(otherMode->id(), otherMode->clone());
0081         }
0082         if (other.edid) {
0083             edid.reset(other.edid->clone());
0084         }
0085     }
0086 
0087     QString biggestMode(const ModeList &modes) const;
0088     bool compareModeList(const ModeList &before, const ModeList &after);
0089 
0090     // please keep them consistent with order of Q_PROPERTY declarations
0091     int id;
0092     QString name;
0093     Type type;
0094     QString icon;
0095     ModeList modeList;
0096     QPoint pos;
0097     QSize size;
0098     Rotation rotation;
0099     // next three don't exactly match properties by name, but keep them close to each other anyway
0100     QString currentMode;
0101     QString preferredMode;
0102     QStringList preferredModes;
0103     //
0104     bool connected;
0105     bool enabled;
0106     uint32_t priority;
0107     QList<int> clones;
0108     int replicationSource;
0109     QScopedPointer<Edid> edid;
0110     QSize sizeMm;
0111     qreal scale;
0112     bool followPreferredMode = false;
0113     QSizeF explicitLogicalSize;
0114     Capabilities capabilities;
0115     uint32_t overscan = 0;
0116     VrrPolicy vrrPolicy = VrrPolicy::Automatic;
0117     RgbRange rgbRange = RgbRange::Automatic;
0118     bool highDynamicRange = false;
0119     uint32_t sdrBrightness = 200;
0120     bool wideColorGamut = false;
0121     AutoRotatePolicy autoRotatePolicy = AutoRotatePolicy::InTabletMode;
0122     QString iccProfilePath;
0123     double sdrGamutWideness = 0;
0124     double maxPeakBrightness = 0;
0125     double maxAverageBrightness = 0;
0126     double minBrightness = 0;
0127     std::optional<double> maxPeakBrightnessOverride;
0128     std::optional<double> maxAverageBrightnessOverride;
0129     std::optional<double> minBrightnessOverride;
0130 };
0131 
0132 bool Output::Private::compareModeList(const ModeList &before, const ModeList &after)
0133 {
0134     if (before.count() != after.count()) {
0135         return false;
0136     }
0137 
0138     for (auto itb = before.constBegin(); itb != before.constEnd(); ++itb) {
0139         auto ita = after.constFind(itb.key());
0140         if (ita == after.constEnd()) {
0141             return false;
0142         }
0143         const auto &mb = itb.value();
0144         const auto &ma = ita.value();
0145         if (mb->id() != ma->id()) {
0146             return false;
0147         }
0148         if (mb->size() != ma->size()) {
0149             return false;
0150         }
0151         if (!qFuzzyCompare(mb->refreshRate(), ma->refreshRate())) {
0152             return false;
0153         }
0154         if (mb->name() != ma->name()) {
0155             return false;
0156         }
0157     }
0158     // They're the same
0159     return true;
0160 }
0161 
0162 QString Output::Private::biggestMode(const ModeList &modes) const
0163 {
0164     int area, total = 0;
0165     KScreen::ModePtr biggest;
0166     for (const KScreen::ModePtr &mode : modes) {
0167         area = mode->size().width() * mode->size().height();
0168         if (area < total) {
0169             continue;
0170         }
0171         if (area == total && mode->refreshRate() < biggest->refreshRate()) {
0172             continue;
0173         }
0174         if (area == total && mode->refreshRate() > biggest->refreshRate()) {
0175             biggest = mode;
0176             continue;
0177         }
0178 
0179         total = area;
0180         biggest = mode;
0181     }
0182 
0183     if (!biggest) {
0184         return QString();
0185     }
0186 
0187     return biggest->id();
0188 }
0189 
0190 Output::Output()
0191     : QObject(nullptr)
0192     , d(new Private())
0193 {
0194 }
0195 
0196 Output::Output(Output::Private *dd)
0197     : QObject()
0198     , d(dd)
0199 {
0200 }
0201 
0202 Output::~Output()
0203 {
0204     delete d;
0205 }
0206 
0207 OutputPtr Output::clone() const
0208 {
0209     return OutputPtr(new Output(new Private(*d)));
0210 }
0211 
0212 int Output::id() const
0213 {
0214     return d->id;
0215 }
0216 
0217 void Output::setId(int id)
0218 {
0219     if (d->id == id) {
0220         return;
0221     }
0222     d->id = id;
0223     Q_EMIT outputChanged();
0224 }
0225 
0226 QString Output::name() const
0227 {
0228     return d->name;
0229 }
0230 
0231 void Output::setName(const QString &name)
0232 {
0233     if (d->name == name) {
0234         return;
0235     }
0236     d->name = name;
0237     Q_EMIT outputChanged();
0238 }
0239 
0240 // TODO KF6: remove this deprecated method
0241 QString Output::hash() const
0242 {
0243     if (edid() && edid()->isValid()) {
0244         return edid()->hash();
0245     }
0246     return name();
0247 }
0248 
0249 QString Output::hashMd5() const
0250 {
0251     if (edid() && edid()->isValid()) {
0252         return edid()->hash();
0253     }
0254     const auto hash = QCryptographicHash::hash(name().toLatin1(), QCryptographicHash::Md5);
0255     return QString::fromLatin1(hash.toHex());
0256 }
0257 
0258 Output::Type Output::type() const
0259 {
0260     return d->type;
0261 }
0262 
0263 void Output::setType(Type type)
0264 {
0265     if (d->type == type) {
0266         return;
0267     }
0268     d->type = type;
0269     Q_EMIT outputChanged();
0270 }
0271 
0272 QString Output::typeName() const
0273 {
0274     switch (d->type) {
0275     case Output::Unknown:
0276         return QStringLiteral("Unknown");
0277     case Output::Panel:
0278         return QStringLiteral("Panel (Laptop)");
0279     case Output::VGA:
0280         return QStringLiteral("VGA");
0281     case Output::DVI:
0282         return QStringLiteral("DVI");
0283     case Output::DVII:
0284         return QStringLiteral("DVI-I");
0285     case Output::DVIA:
0286         return QStringLiteral("DVI-A");
0287     case Output::DVID:
0288         return QStringLiteral("DVI-D");
0289     case Output::HDMI:
0290         return QStringLiteral("HDMI");
0291     case Output::TV:
0292         return QStringLiteral("TV");
0293     case Output::TVComposite:
0294         return QStringLiteral("TV-Composite");
0295     case Output::TVSVideo:
0296         return QStringLiteral("TV-SVideo");
0297     case Output::TVComponent:
0298         return QStringLiteral("TV-Component");
0299     case Output::TVSCART:
0300         return QStringLiteral("TV-SCART");
0301     case Output::TVC4:
0302         return QStringLiteral("TV-C4");
0303     case Output::DisplayPort:
0304         return QStringLiteral("DisplayPort");
0305     };
0306     return QStringLiteral("Invalid Type") + QString::number(d->type);
0307 }
0308 
0309 QString Output::icon() const
0310 {
0311     return d->icon;
0312 }
0313 
0314 void Output::setIcon(const QString &icon)
0315 {
0316     if (d->icon == icon) {
0317         return;
0318     }
0319     d->icon = icon;
0320     Q_EMIT outputChanged();
0321 }
0322 
0323 ModePtr Output::mode(const QString &id) const
0324 {
0325     if (!d->modeList.contains(id)) {
0326         return ModePtr();
0327     }
0328 
0329     return d->modeList[id];
0330 }
0331 
0332 ModeList Output::modes() const
0333 {
0334     return d->modeList;
0335 }
0336 
0337 void Output::setModes(const ModeList &modes)
0338 {
0339     bool changed = !d->compareModeList(d->modeList, modes);
0340     d->modeList = modes;
0341     if (changed) {
0342         Q_EMIT modesChanged();
0343         Q_EMIT outputChanged();
0344     }
0345 }
0346 
0347 QString Output::currentModeId() const
0348 {
0349     return d->currentMode;
0350 }
0351 
0352 void Output::setCurrentModeId(const QString &mode)
0353 {
0354     if (d->currentMode == mode) {
0355         return;
0356     }
0357     d->currentMode = mode;
0358     Q_EMIT currentModeIdChanged();
0359 }
0360 
0361 ModePtr Output::currentMode() const
0362 {
0363     return d->modeList.value(d->currentMode);
0364 }
0365 
0366 void Output::setPreferredModes(const QStringList &modes)
0367 {
0368     d->preferredMode = QString();
0369     d->preferredModes = modes;
0370 }
0371 
0372 QStringList Output::preferredModes() const
0373 {
0374     return d->preferredModes;
0375 }
0376 
0377 QString Output::preferredModeId() const
0378 {
0379     if (!d->preferredMode.isEmpty()) {
0380         return d->preferredMode;
0381     }
0382     if (d->preferredModes.isEmpty()) {
0383         return d->biggestMode(modes());
0384     }
0385 
0386     int total = 0;
0387     KScreen::ModePtr biggest;
0388     KScreen::ModePtr candidateMode;
0389     for (const QString &modeId : std::as_const(d->preferredModes)) {
0390         candidateMode = mode(modeId);
0391         const int area = candidateMode->size().width() * candidateMode->size().height();
0392         if (area < total) {
0393             continue;
0394         }
0395         if (area == total && biggest && candidateMode->refreshRate() < biggest->refreshRate()) {
0396             continue;
0397         }
0398         if (area == total && biggest && candidateMode->refreshRate() > biggest->refreshRate()) {
0399             biggest = candidateMode;
0400             continue;
0401         }
0402 
0403         total = area;
0404         biggest = candidateMode;
0405     }
0406 
0407     Q_ASSERT_X(biggest, "preferredModeId", "biggest mode must exist");
0408 
0409     d->preferredMode = biggest->id();
0410     return d->preferredMode;
0411 }
0412 
0413 ModePtr Output::preferredMode() const
0414 {
0415     return d->modeList.value(preferredModeId());
0416 }
0417 
0418 QPoint Output::pos() const
0419 {
0420     return d->pos;
0421 }
0422 
0423 void Output::setPos(const QPoint &pos)
0424 {
0425     if (d->pos == pos) {
0426         return;
0427     }
0428     d->pos = pos;
0429     Q_EMIT posChanged();
0430 }
0431 
0432 QSize Output::size() const
0433 {
0434     return d->size;
0435 }
0436 
0437 void Output::setSize(const QSize &size)
0438 {
0439     if (d->size == size) {
0440         return;
0441     }
0442     d->size = size;
0443     Q_EMIT sizeChanged();
0444 }
0445 
0446 // TODO KF6: make the Rotation enum an enum class and align values with Wayland transformation property
0447 Output::Rotation Output::rotation() const
0448 {
0449     return d->rotation;
0450 }
0451 
0452 void Output::setRotation(Output::Rotation rotation)
0453 {
0454     if (d->rotation == rotation) {
0455         return;
0456     }
0457     d->rotation = rotation;
0458     Q_EMIT rotationChanged();
0459 }
0460 
0461 qreal Output::scale() const
0462 {
0463     return d->scale;
0464 }
0465 
0466 void Output::setScale(qreal factor)
0467 {
0468     if (qFuzzyCompare(d->scale, factor)) {
0469         return;
0470     }
0471     d->scale = factor;
0472     Q_EMIT scaleChanged();
0473 }
0474 
0475 QSizeF Output::explicitLogicalSize() const
0476 {
0477     return d->explicitLogicalSize;
0478 }
0479 
0480 QSize Output::explicitLogicalSizeInt() const
0481 {
0482     const QSizeF sizeF = explicitLogicalSize();
0483     return QSize(std::ceil(sizeF.width()), std::ceil(sizeF.height()));
0484 }
0485 
0486 void Output::setExplicitLogicalSize(const QSizeF &size)
0487 {
0488     if (qFuzzyCompare(d->explicitLogicalSize.width(), size.width()) && qFuzzyCompare(d->explicitLogicalSize.height(), size.height())) {
0489         return;
0490     }
0491     d->explicitLogicalSize = size;
0492     Q_EMIT explicitLogicalSizeChanged();
0493 }
0494 
0495 bool Output::isConnected() const
0496 {
0497     return d->connected;
0498 }
0499 
0500 void Output::setConnected(bool connected)
0501 {
0502     if (d->connected == connected) {
0503         return;
0504     }
0505     d->connected = connected;
0506     Q_EMIT isConnectedChanged();
0507 }
0508 
0509 bool Output::isEnabled() const
0510 {
0511     return d->enabled;
0512 }
0513 
0514 void Output::setEnabled(bool enabled)
0515 {
0516     if (d->enabled == enabled) {
0517         return;
0518     }
0519     d->enabled = enabled;
0520     Q_EMIT isEnabledChanged();
0521 }
0522 
0523 bool Output::isPrimary() const
0524 {
0525     return d->enabled && (d->priority == 1);
0526 }
0527 
0528 void Output::setPrimary(bool primary)
0529 {
0530     if (primary) {
0531         setPriority(1);
0532     } else {
0533         qCWarning(KSCREEN) << "Calling Output::setPrimary(false) is not supported. Port your code to Config::setPrimaryOutput";
0534     }
0535 }
0536 
0537 uint32_t Output::priority() const
0538 {
0539     return d->priority;
0540 }
0541 
0542 void Output::setPriority(uint32_t priority)
0543 {
0544     if (d->priority == priority) {
0545         return;
0546     }
0547     d->priority = priority;
0548     Q_EMIT priorityChanged();
0549 }
0550 
0551 QList<int> Output::clones() const
0552 {
0553     return d->clones;
0554 }
0555 
0556 void Output::setClones(const QList<int> &outputlist)
0557 {
0558     if (d->clones == outputlist) {
0559         return;
0560     }
0561     d->clones = outputlist;
0562     Q_EMIT clonesChanged();
0563 }
0564 
0565 int Output::replicationSource() const
0566 {
0567     return d->replicationSource;
0568 }
0569 
0570 void Output::setReplicationSource(int source)
0571 {
0572     if (d->replicationSource == source) {
0573         return;
0574     }
0575     d->replicationSource = source;
0576     Q_EMIT replicationSourceChanged();
0577 }
0578 
0579 void Output::setEdid(const QByteArray &rawData)
0580 {
0581     Q_ASSERT(d->edid.isNull());
0582     d->edid.reset(new Edid(rawData));
0583 }
0584 
0585 Edid *Output::edid() const
0586 {
0587     return d->edid.data();
0588 }
0589 
0590 QSize Output::sizeMm() const
0591 {
0592     return d->sizeMm;
0593 }
0594 
0595 void Output::setSizeMm(const QSize &size)
0596 {
0597     d->sizeMm = size;
0598 }
0599 
0600 bool KScreen::Output::followPreferredMode() const
0601 {
0602     return d->followPreferredMode;
0603 }
0604 
0605 void KScreen::Output::setFollowPreferredMode(bool follow)
0606 {
0607     if (follow == d->followPreferredMode) {
0608         return;
0609     }
0610     d->followPreferredMode = follow;
0611     Q_EMIT followPreferredModeChanged(follow);
0612 }
0613 
0614 bool Output::isPositionable() const
0615 {
0616     return isConnected() && isEnabled() && !replicationSource();
0617 }
0618 
0619 QSize Output::enforcedModeSize() const
0620 {
0621     if (const auto mode = currentMode()) {
0622         return mode->size();
0623     } else if (const auto mode = preferredMode()) {
0624         return mode->size();
0625     } else if (d->modeList.count() > 0) {
0626         return d->modeList.first()->size();
0627     }
0628     return QSize();
0629 }
0630 
0631 QRect Output::geometry() const
0632 {
0633     QSize size = explicitLogicalSizeInt();
0634     if (!size.isValid()) {
0635         return QRect();
0636     }
0637 
0638     return QRect(d->pos, size);
0639 }
0640 
0641 Output::Capabilities Output::capabilities() const
0642 {
0643     return d->capabilities;
0644 }
0645 
0646 void Output::setCapabilities(Capabilities capabilities)
0647 {
0648     if (d->capabilities == capabilities) {
0649         return;
0650     }
0651     d->capabilities = capabilities;
0652     Q_EMIT capabilitiesChanged();
0653 }
0654 
0655 uint32_t Output::overscan() const
0656 {
0657     return d->overscan;
0658 }
0659 
0660 void Output::setOverscan(uint32_t overscan)
0661 {
0662     if (d->overscan == overscan) {
0663         return;
0664     }
0665     d->overscan = overscan;
0666     Q_EMIT overscanChanged();
0667 }
0668 
0669 Output::VrrPolicy Output::vrrPolicy() const
0670 {
0671     return d->vrrPolicy;
0672 }
0673 
0674 void Output::setVrrPolicy(VrrPolicy policy)
0675 {
0676     if (d->vrrPolicy == policy) {
0677         return;
0678     }
0679     d->vrrPolicy = policy;
0680     Q_EMIT vrrPolicyChanged();
0681 }
0682 
0683 Output::RgbRange Output::rgbRange() const
0684 {
0685     return d->rgbRange;
0686 }
0687 
0688 void Output::setRgbRange(Output::RgbRange rgbRange)
0689 {
0690     if (d->rgbRange == rgbRange) {
0691         return;
0692     }
0693     d->rgbRange = rgbRange;
0694     Q_EMIT rgbRangeChanged();
0695 }
0696 
0697 bool Output::isHdrEnabled() const
0698 {
0699     return d->highDynamicRange;
0700 }
0701 
0702 void Output::setHdrEnabled(bool enable)
0703 {
0704     if (d->highDynamicRange != enable) {
0705         d->highDynamicRange = enable;
0706         Q_EMIT hdrEnabledChanged();
0707     }
0708 }
0709 
0710 uint32_t Output::sdrBrightness() const
0711 {
0712     return d->sdrBrightness;
0713 }
0714 
0715 void Output::setSdrBrightness(uint32_t brightness)
0716 {
0717     if (d->sdrBrightness != brightness) {
0718         d->sdrBrightness = brightness;
0719         Q_EMIT sdrBrightnessChanged();
0720     }
0721 }
0722 
0723 bool Output::isWcgEnabled() const
0724 {
0725     return d->wideColorGamut;
0726 }
0727 
0728 void Output::setWcgEnabled(bool enable)
0729 {
0730     if (d->wideColorGamut != enable) {
0731         d->wideColorGamut = enable;
0732         Q_EMIT wcgEnabledChanged();
0733     }
0734 }
0735 
0736 Output::AutoRotatePolicy Output::autoRotatePolicy() const
0737 {
0738     return d->autoRotatePolicy;
0739 }
0740 
0741 void Output::setAutoRotatePolicy(AutoRotatePolicy policy)
0742 {
0743     if (d->autoRotatePolicy != policy) {
0744         d->autoRotatePolicy = policy;
0745         Q_EMIT autoRotatePolicyChanged();
0746     }
0747 }
0748 
0749 QString Output::iccProfilePath() const
0750 {
0751     return d->iccProfilePath;
0752 }
0753 
0754 void Output::setIccProfilePath(const QString &path)
0755 {
0756     if (d->iccProfilePath != path) {
0757         d->iccProfilePath = path;
0758         Q_EMIT iccProfilePathChanged();
0759     }
0760 }
0761 
0762 double Output::sdrGamutWideness() const
0763 {
0764     return d->sdrGamutWideness;
0765 }
0766 
0767 void Output::setSdrGamutWideness(double value)
0768 {
0769     if (d->sdrGamutWideness != value) {
0770         d->sdrGamutWideness = value;
0771         Q_EMIT sdrGamutWidenessChanged();
0772     }
0773 }
0774 
0775 double Output::maxPeakBrightness() const
0776 {
0777     return d->maxPeakBrightness;
0778 }
0779 
0780 void Output::setMaxPeakBrightness(double value)
0781 {
0782     if (d->maxPeakBrightness != value) {
0783         d->maxPeakBrightness = value;
0784         Q_EMIT maxPeakBrightnessChanged();
0785     }
0786 }
0787 
0788 double Output::maxAverageBrightness() const
0789 {
0790     return d->maxAverageBrightness;
0791 }
0792 
0793 void Output::setMaxAverageBrightness(double value)
0794 {
0795     if (d->maxAverageBrightness != value) {
0796         d->maxAverageBrightness = value;
0797         Q_EMIT maxAverageBrightnessChanged();
0798     }
0799 }
0800 
0801 double Output::minBrightness() const
0802 {
0803     return d->minBrightness;
0804 }
0805 
0806 void Output::setMinBrightness(double value)
0807 {
0808     if (d->minBrightness != value) {
0809         d->minBrightness = value;
0810         Q_EMIT minBrightnessChanged();
0811     }
0812 }
0813 
0814 std::optional<double> Output::maxPeakBrightnessOverride() const
0815 {
0816     return d->maxPeakBrightnessOverride;
0817 }
0818 
0819 void Output::setMaxPeakBrightnessOverride(std::optional<double> value)
0820 {
0821     if (d->maxPeakBrightnessOverride != value) {
0822         d->maxPeakBrightnessOverride = value;
0823         Q_EMIT maxPeakBrightnessOverrideChanged();
0824     }
0825 }
0826 
0827 std::optional<double> Output::maxAverageBrightnessOverride() const
0828 {
0829     return d->maxAverageBrightnessOverride;
0830 }
0831 
0832 void Output::setMaxAverageBrightnessOverride(std::optional<double> value)
0833 {
0834     if (d->maxAverageBrightnessOverride != value) {
0835         d->maxAverageBrightnessOverride = value;
0836         Q_EMIT maxAverageBrightnessOverrideChanged();
0837     }
0838 }
0839 
0840 std::optional<double> Output::minBrightnessOverride() const
0841 {
0842     return d->minBrightnessOverride;
0843 }
0844 
0845 void Output::setMinBrightnessOverride(std::optional<double> value)
0846 {
0847     if (d->minBrightnessOverride != value) {
0848         d->minBrightnessOverride = value;
0849         Q_EMIT minBrightnessOverrideChanged();
0850     }
0851 }
0852 
0853 void Output::apply(const OutputPtr &other)
0854 {
0855     typedef void (KScreen::Output::*ChangeSignal)();
0856     QList<ChangeSignal> changes;
0857 
0858     // We block all signals, and emit them only after we have set up everything
0859     // This is necessary in order to prevent clients from accessing inconsistent
0860     // outputs from intermediate change signals
0861     const bool keepBlocked = blockSignals(true);
0862     if (d->name != other->d->name) {
0863         changes << &Output::outputChanged;
0864         setName(other->d->name);
0865     }
0866     if (d->type != other->d->type) {
0867         changes << &Output::outputChanged;
0868         setType(other->d->type);
0869     }
0870     if (d->icon != other->d->icon) {
0871         changes << &Output::outputChanged;
0872         setIcon(other->d->icon);
0873     }
0874     if (d->pos != other->d->pos) {
0875         changes << &Output::posChanged;
0876         setPos(other->pos());
0877     }
0878     if (d->rotation != other->d->rotation) {
0879         changes << &Output::rotationChanged;
0880         setRotation(other->d->rotation);
0881     }
0882     if (!qFuzzyCompare(d->scale, other->d->scale)) {
0883         changes << &Output::scaleChanged;
0884         setScale(other->d->scale);
0885     }
0886     if (d->currentMode != other->d->currentMode) {
0887         changes << &Output::currentModeIdChanged;
0888         setCurrentModeId(other->d->currentMode);
0889     }
0890     if (d->connected != other->d->connected) {
0891         changes << &Output::isConnectedChanged;
0892         setConnected(other->d->connected);
0893     }
0894     if (d->enabled != other->d->enabled) {
0895         changes << &Output::isEnabledChanged;
0896         setEnabled(other->d->enabled);
0897     }
0898     if (d->priority != other->d->priority) {
0899         changes << &Output::priorityChanged;
0900         setPriority(other->d->priority);
0901     }
0902     if (d->clones != other->d->clones) {
0903         changes << &Output::clonesChanged;
0904         setClones(other->d->clones);
0905     }
0906     if (d->replicationSource != other->d->replicationSource) {
0907         changes << &Output::replicationSourceChanged;
0908         setReplicationSource(other->d->replicationSource);
0909     }
0910     if (!d->compareModeList(d->modeList, other->d->modeList)) {
0911         changes << &Output::outputChanged;
0912         changes << &Output::modesChanged;
0913     }
0914 
0915     setPreferredModes(other->d->preferredModes);
0916     ModeList modes;
0917     for (const ModePtr &otherMode : other->modes()) {
0918         modes.insert(otherMode->id(), otherMode->clone());
0919     }
0920     setModes(modes);
0921 
0922     if (d->capabilities != other->d->capabilities) {
0923         changes << &Output::capabilitiesChanged;
0924         setCapabilities(other->d->capabilities);
0925     }
0926     if (d->vrrPolicy != other->d->vrrPolicy) {
0927         changes << &Output::vrrPolicyChanged;
0928         setVrrPolicy(other->d->vrrPolicy);
0929     }
0930     if (d->overscan != other->d->overscan) {
0931         changes << &Output::overscanChanged;
0932         setOverscan(other->d->overscan);
0933     }
0934     if (d->rgbRange != other->d->rgbRange) {
0935         changes << &Output::rgbRangeChanged;
0936         setRgbRange(other->d->rgbRange);
0937     }
0938     if (d->highDynamicRange != other->d->highDynamicRange) {
0939         changes << &Output::hdrEnabledChanged;
0940         setHdrEnabled(other->d->highDynamicRange);
0941     }
0942     if (d->sdrBrightness != other->d->sdrBrightness) {
0943         changes << &Output::sdrBrightnessChanged;
0944         setSdrBrightness(other->d->sdrBrightness);
0945     }
0946     if (d->wideColorGamut != other->d->wideColorGamut) {
0947         changes << &Output::wcgEnabledChanged;
0948         setWcgEnabled(other->d->wideColorGamut);
0949     }
0950     if (d->autoRotatePolicy != other->d->autoRotatePolicy) {
0951         changes << &Output::autoRotatePolicyChanged;
0952         setAutoRotatePolicy(other->d->autoRotatePolicy);
0953     }
0954     if (d->iccProfilePath != other->d->iccProfilePath) {
0955         changes << &Output::iccProfilePathChanged;
0956         setIccProfilePath(other->d->iccProfilePath);
0957     }
0958     if (d->sdrGamutWideness != other->d->sdrGamutWideness) {
0959         changes << &Output::sdrGamutWidenessChanged;
0960         setSdrGamutWideness(other->d->sdrGamutWideness);
0961     }
0962     if (d->maxPeakBrightness != other->d->maxPeakBrightness) {
0963         changes << &Output::maxPeakBrightnessChanged;
0964         setMaxPeakBrightness(other->d->maxPeakBrightness);
0965     }
0966     if (d->maxAverageBrightness != other->d->maxAverageBrightness) {
0967         changes << &Output::maxAverageBrightnessChanged;
0968         setMaxAverageBrightness(other->d->maxAverageBrightness);
0969     }
0970     if (d->minBrightness != other->d->minBrightness) {
0971         changes << &Output::minBrightnessChanged;
0972         setMinBrightness(other->d->minBrightness);
0973     }
0974 
0975     if (d->maxPeakBrightnessOverride != other->d->maxPeakBrightnessOverride) {
0976         changes << &Output::maxPeakBrightnessOverrideChanged;
0977         setMaxPeakBrightnessOverride(other->d->maxPeakBrightnessOverride);
0978     }
0979     if (d->maxAverageBrightnessOverride != other->d->maxAverageBrightnessOverride) {
0980         changes << &Output::maxAverageBrightnessOverrideChanged;
0981         setMaxAverageBrightnessOverride(other->d->maxAverageBrightnessOverride);
0982     }
0983     if (d->minBrightnessOverride != other->d->minBrightnessOverride) {
0984         changes << &Output::minBrightnessOverrideChanged;
0985         setMinBrightnessOverride(other->d->minBrightnessOverride);
0986     }
0987 
0988     // Non-notifyable changes
0989     if (other->d->edid) {
0990         d->edid.reset(other->d->edid->clone());
0991     }
0992 
0993     blockSignals(keepBlocked);
0994 
0995     while (!changes.isEmpty()) {
0996         const ChangeSignal &sig = changes.first();
0997         Q_EMIT(this->*sig)();
0998         changes.removeAll(sig);
0999     }
1000 }
1001 
1002 QDebug operator<<(QDebug dbg, const KScreen::OutputPtr &output)
1003 {
1004     QDebugStateSaver saver(dbg);
1005     if (!output) {
1006         dbg << "KScreen::Output(NULL)";
1007         return dbg;
1008     }
1009 
1010     // clang-format off
1011     dbg.nospace()
1012         << "KScreen::Output("
1013             << output->id() << ", "
1014             << output->name() << ", "
1015             << (output->isConnected() ? "connected " : "disconnected ")
1016             << (output->isEnabled() ? "enabled" : "disabled")
1017             << " priority " << output->priority()
1018             << ", pos: " << output->pos()
1019             << ", res: " << output->size()
1020             << ", modeId: " << output->currentModeId()
1021             << ", scale: " << output->scale()
1022             << ", clone: " << (output->clones().isEmpty() ? "no" : "yes")
1023             << ", rotation: " << output->rotation()
1024             << ", followPreferredMode: " << output->followPreferredMode()
1025         << ")";
1026     // clang-format on
1027     return dbg;
1028 }
1029 
1030 #include "moc_output.cpp"