File indexing completed on 2024-04-14 15:37:38

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         , replicationSource(0)
0031         , rotation(None)
0032         , scale(1.0)
0033         , explicitLogicalSize(QSizeF())
0034         , connected(false)
0035         , enabled(false)
0036         , priority(0)
0037         , edid(nullptr)
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     {
0066         const auto otherModeList = other.modeList;
0067         for (const ModePtr &otherMode : otherModeList) {
0068             modeList.insert(otherMode->id(), otherMode->clone());
0069         }
0070         if (other.edid) {
0071             edid.reset(other.edid->clone());
0072         }
0073     }
0074 
0075     QString biggestMode(const ModeList &modes) const;
0076     bool compareModeList(const ModeList &before, const ModeList &after);
0077 
0078     // please keep them consistent with order of Q_PROPERTY declarations
0079     int id;
0080     QString name;
0081     Type type;
0082     QString icon;
0083     ModeList modeList;
0084     QPoint pos;
0085     QSize size;
0086     Rotation rotation;
0087     // next three don't exactly match properties by name, but keep them close to each other anyway
0088     QString currentMode;
0089     QString preferredMode;
0090     QStringList preferredModes;
0091     //
0092     bool connected;
0093     bool enabled;
0094     uint32_t priority;
0095     QList<int> clones;
0096     int replicationSource;
0097     QScopedPointer<Edid> edid;
0098     QSize sizeMm;
0099     qreal scale;
0100     bool followPreferredMode = false;
0101     QSizeF explicitLogicalSize;
0102     Capabilities capabilities;
0103     uint32_t overscan = 0;
0104     VrrPolicy vrrPolicy = VrrPolicy::Automatic;
0105     RgbRange rgbRange = RgbRange::Automatic;
0106 };
0107 
0108 bool Output::Private::compareModeList(const ModeList &before, const ModeList &after)
0109 {
0110     if (before.count() != after.count()) {
0111         return false;
0112     }
0113 
0114     for (auto itb = before.constBegin(); itb != before.constEnd(); ++itb) {
0115         auto ita = after.constFind(itb.key());
0116         if (ita == after.constEnd()) {
0117             return false;
0118         }
0119         const auto &mb = itb.value();
0120         const auto &ma = ita.value();
0121         if (mb->id() != ma->id()) {
0122             return false;
0123         }
0124         if (mb->size() != ma->size()) {
0125             return false;
0126         }
0127         if (!qFuzzyCompare(mb->refreshRate(), ma->refreshRate())) {
0128             return false;
0129         }
0130         if (mb->name() != ma->name()) {
0131             return false;
0132         }
0133     }
0134     // They're the same
0135     return true;
0136 }
0137 
0138 QString Output::Private::biggestMode(const ModeList &modes) const
0139 {
0140     int area, total = 0;
0141     KScreen::ModePtr biggest;
0142     for (const KScreen::ModePtr &mode : modes) {
0143         area = mode->size().width() * mode->size().height();
0144         if (area < total) {
0145             continue;
0146         }
0147         if (area == total && mode->refreshRate() < biggest->refreshRate()) {
0148             continue;
0149         }
0150         if (area == total && mode->refreshRate() > biggest->refreshRate()) {
0151             biggest = mode;
0152             continue;
0153         }
0154 
0155         total = area;
0156         biggest = mode;
0157     }
0158 
0159     if (!biggest) {
0160         return QString();
0161     }
0162 
0163     return biggest->id();
0164 }
0165 
0166 Output::Output()
0167     : QObject(nullptr)
0168     , d(new Private())
0169 {
0170 }
0171 
0172 Output::Output(Output::Private *dd)
0173     : QObject()
0174     , d(dd)
0175 {
0176 }
0177 
0178 Output::~Output()
0179 {
0180     delete d;
0181 }
0182 
0183 OutputPtr Output::clone() const
0184 {
0185     return OutputPtr(new Output(new Private(*d)));
0186 }
0187 
0188 int Output::id() const
0189 {
0190     return d->id;
0191 }
0192 
0193 void Output::setId(int id)
0194 {
0195     if (d->id == id) {
0196         return;
0197     }
0198     d->id = id;
0199     Q_EMIT outputChanged();
0200 }
0201 
0202 QString Output::name() const
0203 {
0204     return d->name;
0205 }
0206 
0207 void Output::setName(const QString &name)
0208 {
0209     if (d->name == name) {
0210         return;
0211     }
0212     d->name = name;
0213     Q_EMIT outputChanged();
0214 }
0215 
0216 // TODO KF6: remove this deprecated method
0217 QString Output::hash() const
0218 {
0219     if (edid() && edid()->isValid()) {
0220         return edid()->hash();
0221     }
0222     return name();
0223 }
0224 
0225 QString Output::hashMd5() const
0226 {
0227     if (edid() && edid()->isValid()) {
0228         return edid()->hash();
0229     }
0230     const auto hash = QCryptographicHash::hash(name().toLatin1(), QCryptographicHash::Md5);
0231     return QString::fromLatin1(hash.toHex());
0232 }
0233 
0234 Output::Type Output::type() const
0235 {
0236     return d->type;
0237 }
0238 
0239 void Output::setType(Type type)
0240 {
0241     if (d->type == type) {
0242         return;
0243     }
0244     d->type = type;
0245     Q_EMIT outputChanged();
0246 }
0247 
0248 QString Output::typeName() const
0249 {
0250     switch (d->type) {
0251     case Output::Unknown:
0252         return QStringLiteral("Unknown");
0253     case Output::Panel:
0254         return QStringLiteral("Panel (Laptop)");
0255     case Output::VGA:
0256         return QStringLiteral("VGA");
0257     case Output::DVI:
0258         return QStringLiteral("DVI");
0259     case Output::DVII:
0260         return QStringLiteral("DVI-I");
0261     case Output::DVIA:
0262         return QStringLiteral("DVI-A");
0263     case Output::DVID:
0264         return QStringLiteral("DVI-D");
0265     case Output::HDMI:
0266         return QStringLiteral("HDMI");
0267     case Output::TV:
0268         return QStringLiteral("TV");
0269     case Output::TVComposite:
0270         return QStringLiteral("TV-Composite");
0271     case Output::TVSVideo:
0272         return QStringLiteral("TV-SVideo");
0273     case Output::TVComponent:
0274         return QStringLiteral("TV-Component");
0275     case Output::TVSCART:
0276         return QStringLiteral("TV-SCART");
0277     case Output::TVC4:
0278         return QStringLiteral("TV-C4");
0279     case Output::DisplayPort:
0280         return QStringLiteral("DisplayPort");
0281     };
0282     return QStringLiteral("Invalid Type") + QString::number(d->type);
0283 }
0284 
0285 QString Output::icon() const
0286 {
0287     return d->icon;
0288 }
0289 
0290 void Output::setIcon(const QString &icon)
0291 {
0292     if (d->icon == icon) {
0293         return;
0294     }
0295     d->icon = icon;
0296     Q_EMIT outputChanged();
0297 }
0298 
0299 ModePtr Output::mode(const QString &id) const
0300 {
0301     if (!d->modeList.contains(id)) {
0302         return ModePtr();
0303     }
0304 
0305     return d->modeList[id];
0306 }
0307 
0308 ModeList Output::modes() const
0309 {
0310     return d->modeList;
0311 }
0312 
0313 void Output::setModes(const ModeList &modes)
0314 {
0315     bool changed = !d->compareModeList(d->modeList, modes);
0316     d->modeList = modes;
0317     if (changed) {
0318         Q_EMIT modesChanged();
0319         Q_EMIT outputChanged();
0320     }
0321 }
0322 
0323 QString Output::currentModeId() const
0324 {
0325     return d->currentMode;
0326 }
0327 
0328 void Output::setCurrentModeId(const QString &mode)
0329 {
0330     if (d->currentMode == mode) {
0331         return;
0332     }
0333     d->currentMode = mode;
0334     Q_EMIT currentModeIdChanged();
0335 }
0336 
0337 ModePtr Output::currentMode() const
0338 {
0339     return d->modeList.value(d->currentMode);
0340 }
0341 
0342 void Output::setPreferredModes(const QStringList &modes)
0343 {
0344     d->preferredMode = QString();
0345     d->preferredModes = modes;
0346 }
0347 
0348 QStringList Output::preferredModes() const
0349 {
0350     return d->preferredModes;
0351 }
0352 
0353 QString Output::preferredModeId() const
0354 {
0355     if (!d->preferredMode.isEmpty()) {
0356         return d->preferredMode;
0357     }
0358     if (d->preferredModes.isEmpty()) {
0359         return d->biggestMode(modes());
0360     }
0361 
0362     int total = 0;
0363     KScreen::ModePtr biggest;
0364     KScreen::ModePtr candidateMode;
0365     for (const QString &modeId : std::as_const(d->preferredModes)) {
0366         candidateMode = mode(modeId);
0367         const int area = candidateMode->size().width() * candidateMode->size().height();
0368         if (area < total) {
0369             continue;
0370         }
0371         if (area == total && biggest && candidateMode->refreshRate() < biggest->refreshRate()) {
0372             continue;
0373         }
0374         if (area == total && biggest && candidateMode->refreshRate() > biggest->refreshRate()) {
0375             biggest = candidateMode;
0376             continue;
0377         }
0378 
0379         total = area;
0380         biggest = candidateMode;
0381     }
0382 
0383     Q_ASSERT_X(biggest, "preferredModeId", "biggest mode must exist");
0384 
0385     d->preferredMode = biggest->id();
0386     return d->preferredMode;
0387 }
0388 
0389 ModePtr Output::preferredMode() const
0390 {
0391     return d->modeList.value(preferredModeId());
0392 }
0393 
0394 QPoint Output::pos() const
0395 {
0396     return d->pos;
0397 }
0398 
0399 void Output::setPos(const QPoint &pos)
0400 {
0401     if (d->pos == pos) {
0402         return;
0403     }
0404     d->pos = pos;
0405     Q_EMIT posChanged();
0406 }
0407 
0408 QSize Output::size() const
0409 {
0410     return d->size;
0411 }
0412 
0413 void Output::setSize(const QSize &size)
0414 {
0415     if (d->size == size) {
0416         return;
0417     }
0418     d->size = size;
0419     Q_EMIT sizeChanged();
0420 }
0421 
0422 // TODO KF6: make the Rotation enum an enum class and align values with Wayland transformation property
0423 Output::Rotation Output::rotation() const
0424 {
0425     return d->rotation;
0426 }
0427 
0428 void Output::setRotation(Output::Rotation rotation)
0429 {
0430     if (d->rotation == rotation) {
0431         return;
0432     }
0433     d->rotation = rotation;
0434     Q_EMIT rotationChanged();
0435 }
0436 
0437 qreal Output::scale() const
0438 {
0439     return d->scale;
0440 }
0441 
0442 void Output::setScale(qreal factor)
0443 {
0444     if (qFuzzyCompare(d->scale, factor)) {
0445         return;
0446     }
0447     d->scale = factor;
0448     Q_EMIT scaleChanged();
0449 }
0450 
0451 QSizeF Output::explicitLogicalSize() const
0452 {
0453     return d->explicitLogicalSize;
0454 }
0455 
0456 void Output::setExplicitLogicalSize(const QSizeF &size)
0457 {
0458     if (qFuzzyCompare(d->explicitLogicalSize.width(), size.width()) && qFuzzyCompare(d->explicitLogicalSize.height(), size.height())) {
0459         return;
0460     }
0461     d->explicitLogicalSize = size;
0462     Q_EMIT explicitLogicalSizeChanged();
0463 }
0464 
0465 bool Output::isConnected() const
0466 {
0467     return d->connected;
0468 }
0469 
0470 void Output::setConnected(bool connected)
0471 {
0472     if (d->connected == connected) {
0473         return;
0474     }
0475     d->connected = connected;
0476     Q_EMIT isConnectedChanged();
0477 }
0478 
0479 bool Output::isEnabled() const
0480 {
0481     return d->enabled;
0482 }
0483 
0484 void Output::setEnabled(bool enabled)
0485 {
0486     if (d->enabled == enabled) {
0487         return;
0488     }
0489     d->enabled = enabled;
0490     Q_EMIT isEnabledChanged();
0491 }
0492 
0493 bool Output::isPrimary() const
0494 {
0495     return d->enabled && (d->priority == 1);
0496 }
0497 
0498 void Output::setPrimary(bool primary)
0499 {
0500     if (primary) {
0501         setPriority(1);
0502     } else {
0503         qCWarning(KSCREEN) << "Calling Output::setPrimary(false) is not supported. Port your code to Config::setPrimaryOutput";
0504     }
0505 }
0506 
0507 uint32_t Output::priority() const
0508 {
0509     return d->priority;
0510 }
0511 
0512 void Output::setPriority(uint32_t priority)
0513 {
0514     if (d->priority == priority) {
0515         return;
0516     }
0517     d->priority = priority;
0518     Q_EMIT priorityChanged();
0519 }
0520 
0521 QList<int> Output::clones() const
0522 {
0523     return d->clones;
0524 }
0525 
0526 void Output::setClones(const QList<int> &outputlist)
0527 {
0528     if (d->clones == outputlist) {
0529         return;
0530     }
0531     d->clones = outputlist;
0532     Q_EMIT clonesChanged();
0533 }
0534 
0535 int Output::replicationSource() const
0536 {
0537     return d->replicationSource;
0538 }
0539 
0540 void Output::setReplicationSource(int source)
0541 {
0542     if (d->replicationSource == source) {
0543         return;
0544     }
0545     d->replicationSource = source;
0546     Q_EMIT replicationSourceChanged();
0547 }
0548 
0549 void Output::setEdid(const QByteArray &rawData)
0550 {
0551     Q_ASSERT(d->edid.isNull());
0552     d->edid.reset(new Edid(rawData));
0553 }
0554 
0555 Edid *Output::edid() const
0556 {
0557     return d->edid.data();
0558 }
0559 
0560 QSize Output::sizeMm() const
0561 {
0562     return d->sizeMm;
0563 }
0564 
0565 void Output::setSizeMm(const QSize &size)
0566 {
0567     d->sizeMm = size;
0568 }
0569 
0570 bool KScreen::Output::followPreferredMode() const
0571 {
0572     return d->followPreferredMode;
0573 }
0574 
0575 void KScreen::Output::setFollowPreferredMode(bool follow)
0576 {
0577     if (follow == d->followPreferredMode) {
0578         return;
0579     }
0580     d->followPreferredMode = follow;
0581     Q_EMIT followPreferredModeChanged(follow);
0582 }
0583 
0584 bool Output::isPositionable() const
0585 {
0586     return isConnected() && isEnabled() && !replicationSource();
0587 }
0588 
0589 QSize Output::enforcedModeSize() const
0590 {
0591     if (const auto mode = currentMode()) {
0592         return mode->size();
0593     } else if (const auto mode = preferredMode()) {
0594         return mode->size();
0595     } else if (d->modeList.count() > 0) {
0596         return d->modeList.first()->size();
0597     }
0598     return QSize();
0599 }
0600 
0601 QRect Output::geometry() const
0602 {
0603     QSize size = explicitLogicalSize().toSize();
0604     if (!size.isValid()) {
0605         return QRect();
0606     }
0607 
0608     return QRect(d->pos, size);
0609 }
0610 
0611 Output::Capabilities Output::capabilities() const
0612 {
0613     return d->capabilities;
0614 }
0615 
0616 void Output::setCapabilities(Capabilities capabilities)
0617 {
0618     if (d->capabilities == capabilities) {
0619         return;
0620     }
0621     d->capabilities = capabilities;
0622     Q_EMIT capabilitiesChanged();
0623 }
0624 
0625 uint32_t Output::overscan() const
0626 {
0627     return d->overscan;
0628 }
0629 
0630 void Output::setOverscan(uint32_t overscan)
0631 {
0632     if (d->overscan == overscan) {
0633         return;
0634     }
0635     d->overscan = overscan;
0636     Q_EMIT overscanChanged();
0637 }
0638 
0639 Output::VrrPolicy Output::vrrPolicy() const
0640 {
0641     return d->vrrPolicy;
0642 }
0643 
0644 void Output::setVrrPolicy(VrrPolicy policy)
0645 {
0646     if (d->vrrPolicy == policy) {
0647         return;
0648     }
0649     d->vrrPolicy = policy;
0650     Q_EMIT vrrPolicyChanged();
0651 }
0652 
0653 Output::RgbRange Output::rgbRange() const
0654 {
0655     return d->rgbRange;
0656 }
0657 
0658 void Output::setRgbRange(Output::RgbRange rgbRange)
0659 {
0660     if (d->rgbRange == rgbRange) {
0661         return;
0662     }
0663     d->rgbRange = rgbRange;
0664     Q_EMIT rgbRangeChanged();
0665 }
0666 
0667 void Output::apply(const OutputPtr &other)
0668 {
0669     typedef void (KScreen::Output::*ChangeSignal)();
0670     QList<ChangeSignal> changes;
0671 
0672     // We block all signals, and emit them only after we have set up everything
0673     // This is necessary in order to prevent clients from accessing inconsistent
0674     // outputs from intermediate change signals
0675     const bool keepBlocked = blockSignals(true);
0676     if (d->name != other->d->name) {
0677         changes << &Output::outputChanged;
0678         setName(other->d->name);
0679     }
0680     if (d->type != other->d->type) {
0681         changes << &Output::outputChanged;
0682         setType(other->d->type);
0683     }
0684     if (d->icon != other->d->icon) {
0685         changes << &Output::outputChanged;
0686         setIcon(other->d->icon);
0687     }
0688     if (d->pos != other->d->pos) {
0689         changes << &Output::posChanged;
0690         setPos(other->pos());
0691     }
0692     if (d->rotation != other->d->rotation) {
0693         changes << &Output::rotationChanged;
0694         setRotation(other->d->rotation);
0695     }
0696     if (!qFuzzyCompare(d->scale, other->d->scale)) {
0697         changes << &Output::scaleChanged;
0698         setScale(other->d->scale);
0699     }
0700     if (d->currentMode != other->d->currentMode) {
0701         changes << &Output::currentModeIdChanged;
0702         setCurrentModeId(other->d->currentMode);
0703     }
0704     if (d->connected != other->d->connected) {
0705         changes << &Output::isConnectedChanged;
0706         setConnected(other->d->connected);
0707     }
0708     if (d->enabled != other->d->enabled) {
0709         changes << &Output::isEnabledChanged;
0710         setEnabled(other->d->enabled);
0711     }
0712     if (d->priority != other->d->priority) {
0713         changes << &Output::priorityChanged;
0714         setPriority(other->d->priority);
0715     }
0716     if (d->clones != other->d->clones) {
0717         changes << &Output::clonesChanged;
0718         setClones(other->d->clones);
0719     }
0720     if (d->replicationSource != other->d->replicationSource) {
0721         changes << &Output::replicationSourceChanged;
0722         setReplicationSource(other->d->replicationSource);
0723     }
0724     if (!d->compareModeList(d->modeList, other->d->modeList)) {
0725         changes << &Output::outputChanged;
0726         changes << &Output::modesChanged;
0727     }
0728 
0729     setPreferredModes(other->d->preferredModes);
0730     ModeList modes;
0731     for (const ModePtr &otherMode : other->modes()) {
0732         modes.insert(otherMode->id(), otherMode->clone());
0733     }
0734     setModes(modes);
0735 
0736     if (d->capabilities != other->d->capabilities) {
0737         changes << &Output::capabilitiesChanged;
0738         setCapabilities(other->d->capabilities);
0739     }
0740     if (d->vrrPolicy != other->d->vrrPolicy) {
0741         changes << &Output::vrrPolicyChanged;
0742         setVrrPolicy(other->d->vrrPolicy);
0743     }
0744     if (d->overscan != other->d->overscan) {
0745         changes << &Output::overscanChanged;
0746         setOverscan(other->d->overscan);
0747     }
0748     if (d->rgbRange != other->d->rgbRange) {
0749         changes << &Output::rgbRangeChanged;
0750         setRgbRange(other->d->rgbRange);
0751     }
0752 
0753     // Non-notifyable changes
0754     if (other->d->edid) {
0755         d->edid.reset(other->d->edid->clone());
0756     }
0757 
0758     blockSignals(keepBlocked);
0759 
0760     while (!changes.isEmpty()) {
0761         const ChangeSignal &sig = changes.first();
0762         Q_EMIT(this->*sig)();
0763         changes.removeAll(sig);
0764     }
0765 }
0766 
0767 QDebug operator<<(QDebug dbg, const KScreen::OutputPtr &output)
0768 {
0769     QDebugStateSaver saver(dbg);
0770     if (!output) {
0771         dbg << "KScreen::Output(NULL)";
0772         return dbg;
0773     }
0774 
0775     // clang-format off
0776     dbg.nospace()
0777         << "KScreen::Output("
0778             << output->id() << ", "
0779             << output->name() << ", "
0780             << (output->isConnected() ? "connected " : "disconnected ")
0781             << (output->isEnabled() ? "enabled" : "disabled")
0782             << " priority " << output->priority()
0783             << ", pos: " << output->pos()
0784             << ", res: " << output->size()
0785             << ", modeId: " << output->currentModeId()
0786             << ", scale: " << output->scale()
0787             << ", clone: " << (output->clones().isEmpty() ? "no" : "yes")
0788             << ", rotation: " << output->rotation()
0789             << ", followPreferredMode: " << output->followPreferredMode()
0790         << ")";
0791     // clang-format on
0792     return dbg;
0793 }