File indexing completed on 2024-04-28 05:36:00
0001 /* 0002 SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "screenpool.h" 0008 #include "outputorderwatcher.h" 0009 #include "screenpool-debug.h" 0010 0011 #include <KWindowSystem> 0012 #include <QDebug> 0013 #include <QGuiApplication> 0014 #include <QScreen> 0015 0016 #ifndef NDEBUG 0017 #define CHECK_SCREEN_INVARIANTS screenInvariants(); 0018 #else 0019 #define CHECK_SCREEN_INVARIANTS 0020 #endif 0021 0022 #include <chrono> 0023 0024 using namespace std::chrono_literals; 0025 0026 ScreenPool::ScreenPool(QObject *parent) 0027 : QObject(parent) 0028 { 0029 qRegisterMetaType<QList<QScreen *>>("QList<QScreen *>"); 0030 connect(qGuiApp, &QGuiApplication::screenAdded, this, [this](QScreen *screen) { 0031 connect(screen, &QScreen::geometryChanged, this, [this, screen]() { 0032 handleScreenGeometryChanged(screen); 0033 }); 0034 handleScreenAdded(screen); 0035 }); 0036 0037 for (auto screen : qApp->screens()) { 0038 connect(screen, &QScreen::geometryChanged, this, [this, screen]() { 0039 handleScreenGeometryChanged(screen); 0040 }); 0041 } 0042 0043 connect(qGuiApp, &QGuiApplication::screenRemoved, this, &ScreenPool::handleScreenRemoved); 0044 0045 // Note that the ScreenPool must process the QGuiApplication::screenAdded signal 0046 // before the primary output watcher. 0047 m_outputOrderWatcher = OutputOrderWatcher::instance(this); 0048 connect(m_outputOrderWatcher, &OutputOrderWatcher::outputOrderChanged, this, &ScreenPool::handleOutputOrderChanged); 0049 0050 reconsiderOutputOrder(); 0051 } 0052 0053 ScreenPool::~ScreenPool() 0054 { 0055 } 0056 0057 int ScreenPool::idForName(const QString &connector) const 0058 { 0059 int i = 0; 0060 for (auto *s : m_availableScreens) { 0061 if (s->name() == connector) { 0062 return i; 0063 } 0064 ++i; 0065 } 0066 return -1; 0067 } 0068 0069 QList<QScreen *> ScreenPool::screenOrder() const 0070 { 0071 return m_availableScreens; 0072 } 0073 0074 QScreen *ScreenPool::primaryScreen() const 0075 { 0076 if (m_availableScreens.isEmpty()) { 0077 return nullptr; 0078 } 0079 return m_availableScreens.first(); 0080 } 0081 0082 QScreen *ScreenPool::screenForId(int id) const 0083 { 0084 if (id < 0 || m_availableScreens.size() <= id) { 0085 return nullptr; 0086 } 0087 0088 return m_availableScreens[id]; 0089 } 0090 0091 int ScreenPool::idForScreen(const QScreen *screen) const 0092 { 0093 return m_availableScreens.indexOf(screen); 0094 } 0095 0096 bool ScreenPool::noRealOutputsConnected() const 0097 { 0098 if (qApp->screens().count() > 1) { 0099 return false; 0100 } else if (m_availableScreens.isEmpty()) { 0101 return true; 0102 } 0103 0104 return isOutputFake(m_availableScreens.first()); 0105 } 0106 0107 bool ScreenPool::isOutputFake(QScreen *screen) const 0108 { 0109 Q_ASSERT(screen); 0110 // On X11 the output named :0.0 is fake (the geometry is usually valid and whatever the geometry 0111 // of the last connected screen was), on wayland the fake output has no name and no geometry 0112 const bool fake = screen->name() == QStringLiteral(":0.0") || screen->geometry().isEmpty() || screen->name().isEmpty(); 0113 // If there is a fake output we can only have one screen left (the fake one) 0114 // Q_ASSERT(!fake || fake == (qGuiApp->screens().count() == 1)); 0115 return fake; 0116 } 0117 0118 QScreen *ScreenPool::outputRedundantTo(QScreen *screen) const 0119 { 0120 Q_ASSERT(screen); 0121 // Manage fake screens separately 0122 if (isOutputFake(screen)) { 0123 return nullptr; 0124 } 0125 const QRect thisGeometry = screen->geometry(); 0126 0127 // Don't use it as we don't want to consider redundants 0128 const int thisId = m_outputOrderWatcher->outputOrder().indexOf(screen->name()); 0129 0130 // FIXME: QScreen doesn't have any idea of "this qscreen is clone of this other one 0131 // so this ultra inefficient heuristic has to stay until we have a slightly better api 0132 // logic is: 0133 // a screen is redundant if: 0134 //* its geometry is contained in another one 0135 //* if their resolutions are different, the "biggest" one wins 0136 //* if they have the same geometry, the one with the lowest id wins (arbitrary, but gives reproducible behavior and makes the primary screen win) 0137 for (QScreen *s : m_sizeSortedScreens) { 0138 // don't compare with itself 0139 if (screen == s) { 0140 continue; 0141 } 0142 0143 const QRect otherGeometry = s->geometry(); 0144 0145 if (otherGeometry.isNull()) { 0146 continue; 0147 } 0148 0149 const int otherId = m_outputOrderWatcher->outputOrder().indexOf(s->name()); 0150 0151 if (otherGeometry.contains(thisGeometry, false) 0152 && ( // since at this point contains is true, if either 0153 // measure of othergeometry is bigger, has a bigger area 0154 otherGeometry.width() > thisGeometry.width() || otherGeometry.height() > thisGeometry.height() || 0155 // ids not -1 are considered in descending order of importance 0156 //-1 means that is a screen not known yet, just arrived and 0157 // not yet in screenpool: this happens for screens that 0158 // are hotplugged and weren't known. it does NOT happen 0159 // at first startup, as screenpool populates on load with all screens connected at the moment before the rest of the shell starts up 0160 (thisId == -1 && otherId != -1) || (thisId > otherId && otherId != -1))) { 0161 return s; 0162 } 0163 } 0164 0165 return nullptr; 0166 } 0167 0168 void ScreenPool::insertSortedScreen(QScreen *screen) 0169 { 0170 if (m_sizeSortedScreens.contains(screen)) { 0171 // This should happen only when a fake screen isn't anymore 0172 return; 0173 } 0174 auto before = std::find_if(m_sizeSortedScreens.begin(), m_sizeSortedScreens.end(), [this, screen](QScreen *otherScreen) { 0175 return (screen->geometry().width() > otherScreen->geometry().width() && screen->geometry().height() > otherScreen->geometry().height()) 0176 || idForName(screen->name()) < idForName(otherScreen->name()); 0177 }); 0178 m_sizeSortedScreens.insert(before, screen); 0179 } 0180 0181 void ScreenPool::handleScreenGeometryChanged(QScreen *screen) 0182 { 0183 m_sizeSortedScreens.removeAll(screen); 0184 insertSortedScreen(screen); 0185 reconsiderOutputOrder(); 0186 } 0187 0188 void ScreenPool::handleScreenAdded(QScreen *screen) 0189 { 0190 qCDebug(SCREENPOOL) << "handleScreenAdded" << screen << screen->geometry(); 0191 0192 insertSortedScreen(screen); 0193 0194 if (isOutputFake(screen)) { 0195 m_fakeScreens.insert(screen); 0196 return; 0197 } else { 0198 m_fakeScreens.remove(screen); 0199 } 0200 0201 // TODO: remove? 0202 if (QScreen *toScreen = outputRedundantTo(screen)) { 0203 m_redundantScreens.insert(screen, toScreen); 0204 return; 0205 } 0206 0207 if (m_fakeScreens.contains(screen)) { 0208 qCDebug(SCREENPOOL) << "not fake anymore" << screen; 0209 m_fakeScreens.remove(screen); 0210 } 0211 0212 Q_ASSERT(!m_availableScreens.contains(screen)); 0213 } 0214 0215 void ScreenPool::handleScreenRemoved(QScreen *screen) 0216 { 0217 qCDebug(SCREENPOOL) << "handleScreenRemoved" << screen; 0218 0219 auto it = std::find_if(m_redundantScreens.begin(), m_redundantScreens.end(), [screen](QScreen *value) { 0220 return (value == screen); 0221 }); 0222 0223 if (it != m_redundantScreens.end()) { 0224 m_redundantScreens.erase(it); 0225 } 0226 0227 m_sizeSortedScreens.removeAll(screen); 0228 if (m_redundantScreens.contains(screen)) { 0229 Q_ASSERT(!m_fakeScreens.contains(screen)); 0230 Q_ASSERT(!m_availableScreens.contains(screen)); 0231 m_redundantScreens.remove(screen); 0232 } else if (m_fakeScreens.contains(screen)) { 0233 // If is in fake screens, then must be fake, this will only happen on Wayland 0234 Q_ASSERT(isOutputFake(screen)); 0235 Q_ASSERT(!m_redundantScreens.contains(screen)); 0236 Q_ASSERT(!m_availableScreens.contains(screen)); 0237 m_fakeScreens.remove(screen); 0238 } else if (isOutputFake(screen)) { 0239 // Fake but not in m_fakeScreens can only happen on X11, where the last output quietly renames itself to ":0.0" without signals 0240 #if HAVE_X11 0241 Q_ASSERT(KWindowSystem::isPlatformX11()); 0242 #endif 0243 Q_ASSERT(m_availableScreens.contains(screen)); 0244 Q_ASSERT(!m_redundantScreens.contains(screen)); 0245 Q_ASSERT(!m_fakeScreens.contains(screen)); 0246 m_availableScreens.removeAll(screen); 0247 } else if (m_availableScreens.contains(screen)) { 0248 // It's possible that the screen was already "removed" by handleOutputOrderChanged 0249 Q_ASSERT(m_availableScreens.contains(screen)); 0250 Q_ASSERT(!m_redundantScreens.contains(screen)); 0251 Q_ASSERT(!m_fakeScreens.contains(screen)); 0252 m_orderChangedPendingSignal = true; 0253 Q_EMIT screenRemoved(screen); 0254 m_availableScreens.removeAll(screen); 0255 } 0256 0257 // We can't call CHECK_SCREEN_INVARIANTS here, but on handleOutputOrderChanged which is about to arrive 0258 } 0259 0260 void ScreenPool::handleOutputOrderChanged(const QStringList &newOrder) 0261 { 0262 QHash<QString, QScreen *> connMap; 0263 for (auto s : qApp->screens()) { 0264 connMap[s->name()] = s; 0265 } 0266 0267 QList<QScreen *> newAvailableScreens; 0268 m_redundantScreens.clear(); 0269 0270 for (const auto &c : newOrder) { 0271 // When a screen is removed we reevaluate the order, but the order changed signal may not have happened yet, 0272 // so filter out outputs which now are invalid 0273 if (!connMap.contains(c)) { 0274 continue; 0275 } 0276 auto *s = connMap[c]; 0277 if (!m_sizeSortedScreens.contains(s)) { 0278 handleScreenAdded(s); 0279 } 0280 if (isOutputFake(s)) { 0281 m_fakeScreens.insert(s); 0282 continue; 0283 } 0284 0285 auto *toScreen = outputRedundantTo(s); 0286 if (!toScreen) { 0287 newAvailableScreens.append(s); 0288 } else { 0289 m_redundantScreens.insert(s, toScreen); 0290 } 0291 } 0292 0293 // always emit removals before updating the sorted list 0294 for (auto screen : std::as_const(m_availableScreens)) { 0295 if (!newAvailableScreens.contains(screen)) { 0296 Q_EMIT screenRemoved(screen); 0297 } 0298 } 0299 0300 if (m_orderChangedPendingSignal || newAvailableScreens != m_availableScreens) { 0301 qCDebug(SCREENPOOL) << "screenOrderChanged, old order:" << m_availableScreens << "new order:" << newAvailableScreens; 0302 m_availableScreens = newAvailableScreens; 0303 Q_EMIT screenOrderChanged(m_availableScreens); 0304 } 0305 0306 m_orderChangedPendingSignal = false; 0307 0308 CHECK_SCREEN_INVARIANTS 0309 // FIXME Async as the status can be incoherent before a fake screen goes away? 0310 } 0311 0312 void ScreenPool::reconsiderOutputOrder() 0313 { 0314 // prevent race condition between us and the watcher. depending on who receives signals first, 0315 // the current screens may not be the ones the watcher knows about -> force an update. 0316 m_outputOrderWatcher->refresh(); 0317 handleOutputOrderChanged(m_outputOrderWatcher->outputOrder()); 0318 } 0319 0320 void ScreenPool::screenInvariants() 0321 { 0322 if (m_outputOrderWatcher->outputOrder().isEmpty()) { 0323 return; 0324 } 0325 // Is the primary connector in sync with the actual primaryScreen? The only way it can get out of sync with primaryConnector() is the single fake screen/no 0326 // real outputs scenario 0327 Q_ASSERT(noRealOutputsConnected() || !m_availableScreens.isEmpty()); 0328 // Is the primary screen available? TODO: it can be redundant 0329 // Q_ASSERT(m_availableScreens.contains(primaryScreen())); 0330 0331 // QScreen bookeeping integrity 0332 auto allScreens = qGuiApp->screens(); 0333 // Do we actually track every screen? 0334 qCDebug(SCREENPOOL) << "Checking screens: available:" << m_availableScreens << "redundant:" << m_redundantScreens << "fake:" << m_fakeScreens 0335 << "all:" << allScreens; 0336 Q_ASSERT((m_availableScreens.count() + m_redundantScreens.count()) == m_outputOrderWatcher->outputOrder().count()); 0337 Q_ASSERT(allScreens.count() == m_sizeSortedScreens.count()); 0338 0339 // At most one fake output 0340 Q_ASSERT(m_fakeScreens.count() <= 1); 0341 for (QScreen *screen : allScreens) { 0342 // ignore screens filtered out by the output order 0343 if (!m_outputOrderWatcher->outputOrder().contains(screen->name())) { 0344 continue; 0345 } 0346 0347 if (m_availableScreens.contains(screen)) { 0348 // If available can't be redundant 0349 Q_ASSERT(!m_redundantScreens.contains(screen)); 0350 } else if (m_redundantScreens.contains(screen)) { 0351 // If redundant can't be available 0352 Q_ASSERT(!m_availableScreens.contains(screen)); 0353 } else if (m_fakeScreens.contains(screen)) { 0354 // A fake screen can't be anywhere else 0355 // This branch is quite rare, happens only during the transition from a fake screen to a real one, for a brief moment both screens can be there 0356 Q_ASSERT(!m_availableScreens.contains(screen)); 0357 Q_ASSERT(!m_redundantScreens.contains(screen)); 0358 } else { 0359 // We can't have a screen unaccounted for 0360 Q_ASSERT(false); 0361 } 0362 } 0363 0364 for (QScreen *screen : m_redundantScreens.keys()) { 0365 Q_ASSERT(outputRedundantTo(screen) != nullptr); 0366 } 0367 } 0368 0369 QDebug operator<<(QDebug debug, const ScreenPool *pool) 0370 { 0371 debug << pool->metaObject()->className() << '(' << static_cast<const void *>(pool) << ") Internal state:\n"; 0372 debug << "Screen Order:\t" << pool->m_availableScreens << '\n'; 0373 debug << "\"Fake\" screens:\t" << pool->m_fakeScreens << '\n'; 0374 debug << "Redundant screens covered by other ones:\t" << pool->m_redundantScreens << '\n'; 0375 debug << "All screens, ordered by size:\t" << pool->m_sizeSortedScreens << '\n'; 0376 debug << "All screen that QGuiApplication knows:\t" << qGuiApp->screens() << '\n'; 0377 return debug; 0378 } 0379 0380 #include "moc_screenpool.cpp"