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 }