File indexing completed on 2025-02-16 05:05:11
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 0009 // local 0010 #include <config-latte.h> 0011 #include "primaryoutputwatcher.h" 0012 0013 // Qt 0014 #include <QDebug> 0015 #include <QFile> 0016 #include <QGuiApplication> 0017 #include <QScreen> 0018 0019 // KDE 0020 #include <KLocalizedString> 0021 #include <KWindowSystem> 0022 0023 // X11 0024 #if HAVE_X11 0025 #include <QtX11Extras/QX11Info> 0026 #include <xcb/xcb.h> 0027 #include <xcb/randr.h> 0028 #include <xcb/xcb_event.h> 0029 #endif 0030 0031 namespace Latte { 0032 0033 const int ScreenPool::FIRSTSCREENID; 0034 0035 ScreenPool::ScreenPool(KSharedConfig::Ptr config, QObject *parent) 0036 : QObject(parent), 0037 m_configGroup(KConfigGroup(config, QStringLiteral("ScreenConnectors"))), 0038 m_primaryWatcher(new PrimaryOutputWatcher(this)) 0039 { 0040 m_configSaveTimer.setSingleShot(true); 0041 connect(&m_configSaveTimer, &QTimer::timeout, this, [this]() { 0042 m_configGroup.sync(); 0043 }); 0044 0045 connect(qGuiApp, &QGuiApplication::screenAdded, this, &ScreenPool::onScreenAdded, Qt::UniqueConnection); 0046 connect(qGuiApp, &QGuiApplication::screenRemoved, this, &ScreenPool::onScreenRemoved, Qt::UniqueConnection); 0047 } 0048 0049 ScreenPool::~ScreenPool() 0050 { 0051 m_configGroup.sync(); 0052 } 0053 0054 void ScreenPool::load() 0055 { 0056 m_screensTable.clear(); 0057 0058 //restore the known ids to connector mappings 0059 for (const QString &key : m_configGroup.keyList()) { 0060 if (key.toInt() <= 0) { 0061 continue; 0062 } 0063 0064 QString serialized = m_configGroup.readEntry(key, QString()); 0065 0066 Data::Screen screenRecord(key, serialized); 0067 //qDebug() << "org.kde.latte ::: " << screenRecord.id << ":" << screenRecord.serialize(); 0068 0069 if (!key.isEmpty() && !serialized.isEmpty() && !m_screensTable.containsId(key)) { 0070 m_screensTable << screenRecord; 0071 qDebug() << "org.kde.latte :: Known Screen - " << screenRecord.id << " : " << screenRecord.name << " : " << screenRecord.geometry; 0072 } 0073 } 0074 0075 // if there are already connected unknown screens, map those 0076 // all needs to be populated as soon as possible, otherwise 0077 // containment->screen() will return an incorrect -1 0078 // at startup, if it' asked before corona::addOutput() 0079 // is performed, driving to the creation of a new containment 0080 for (QScreen *screen : qGuiApp->screens()) { 0081 onScreenRemoved(screen); 0082 0083 if (!m_screensTable.containsName(screen->name())) { 0084 insertScreenMapping(screen->name()); 0085 } else { 0086 updateScreenGeometry(screen); 0087 } 0088 0089 onScreenAdded(screen); 0090 } 0091 0092 if (KWindowSystem::isPlatformX11()) { 0093 connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &ScreenPool::primaryScreenChanged, Qt::UniqueConnection); 0094 } 0095 0096 connect(m_primaryWatcher, &PrimaryOutputWatcher::primaryOutputNameChanged, this, &ScreenPool::onPrimaryOutputNameChanged, Qt::UniqueConnection); 0097 } 0098 0099 void ScreenPool::onPrimaryOutputNameChanged(const QString &oldOutputName, const QString &newOutputName) 0100 { 0101 Q_UNUSED(oldOutputName); 0102 Q_UNUSED(newOutputName); 0103 0104 emit primaryScreenChanged(m_primaryWatcher->primaryScreen()); 0105 } 0106 0107 void ScreenPool::onScreenAdded(const QScreen *screen) 0108 { 0109 connect(screen, &QScreen::geometryChanged, this, [&, screen]() { 0110 updateScreenGeometry(screen); 0111 }); 0112 } 0113 0114 void ScreenPool::onScreenRemoved(const QScreen *screen) 0115 { 0116 disconnect(screen, &QScreen::geometryChanged, this, nullptr); 0117 } 0118 0119 void ScreenPool::updateScreenGeometry(const QScreen *screen) 0120 { 0121 if (!screen) { 0122 return; 0123 } 0124 0125 if (m_screensTable.containsName(screen->name())) { 0126 updateScreenGeometry(id(screen->name()), screen->geometry()); 0127 } 0128 } 0129 0130 void ScreenPool::updateScreenGeometry(const int &screenId, const QRect &screenGeometry) 0131 { 0132 QString scrIdStr = QString::number(screenId); 0133 0134 if (!m_screensTable.containsId(scrIdStr) || m_screensTable[scrIdStr].geometry == screenGeometry) { 0135 return; 0136 } 0137 0138 m_screensTable[scrIdStr].geometry = screenGeometry; 0139 save(); 0140 0141 emit screenGeometryChanged(); 0142 } 0143 0144 0145 Latte::Data::ScreensTable ScreenPool::screensTable() 0146 { 0147 return m_screensTable; 0148 } 0149 0150 void ScreenPool::reload(QString path) 0151 { 0152 QFile rcfile(QString(path + "/lattedockrc")); 0153 0154 if (rcfile.exists()) { 0155 qDebug() << "load screen connectors from ::: " << rcfile.fileName(); 0156 KSharedConfigPtr newFile = KSharedConfig::openConfig(rcfile.fileName()); 0157 m_configGroup = KConfigGroup(newFile, QStringLiteral("ScreenConnectors")); 0158 load(); 0159 } 0160 } 0161 0162 void ScreenPool::removeScreens(const Latte::Data::ScreensTable &obsoleteScreens) 0163 { 0164 for (int i=0; i<obsoleteScreens.rowCount(); ++i) { 0165 if (!m_screensTable.containsId(obsoleteScreens[i].id)) { 0166 return; 0167 } 0168 0169 m_screensTable.remove(obsoleteScreens[i].id); 0170 m_configGroup.deleteEntry(obsoleteScreens[i].id); 0171 } 0172 } 0173 0174 int ScreenPool::primaryScreenId() const 0175 { 0176 return id(primaryScreen()->name()); 0177 } 0178 0179 QList<int> ScreenPool::secondaryScreenIds() const 0180 { 0181 QList<int> secondaryscreens; 0182 0183 QScreen *primary{primaryScreen()}; 0184 0185 for (const auto scr : qGuiApp->screens()) { 0186 if (scr == primary) { 0187 continue; 0188 } 0189 0190 secondaryscreens << id(scr->name()); 0191 } 0192 0193 return secondaryscreens; 0194 } 0195 0196 void ScreenPool::save() 0197 { 0198 QMap<int, QString>::const_iterator i; 0199 0200 for (int i=0; i<m_screensTable.rowCount(); ++i) { 0201 Data::Screen screenRecord = m_screensTable[i]; 0202 if (screenRecord.id.toInt() >= FIRSTSCREENID) { 0203 m_configGroup.writeEntry(screenRecord.id, screenRecord.serialize()); 0204 } 0205 } 0206 0207 //write to disck every 10 seconds at most 0208 m_configSaveTimer.start(10000); 0209 } 0210 0211 void ScreenPool::insertScreenMapping(const QString &connector) 0212 { 0213 //the ":" check fixes the strange plasma/qt issues when changing layouts 0214 //there are case that the QScreen instead of the correct screen name 0215 //returns "0:0", this check prevents from breaking the screens database 0216 //from garbage ids 0217 if (m_screensTable.containsName(connector) || connector.startsWith(":")) { 0218 return; 0219 } 0220 0221 qDebug() << "add connector..." << connector; 0222 0223 Data::Screen screenRecord; 0224 screenRecord.id = QString::number(firstAvailableId()); 0225 screenRecord.name = connector; 0226 0227 //! update screen geometry 0228 for (const auto scr : qGuiApp->screens()) { 0229 if (scr->name() == connector) { 0230 screenRecord.geometry = scr->geometry(); 0231 break; 0232 } 0233 } 0234 0235 m_screensTable << screenRecord; 0236 save(); 0237 } 0238 0239 int ScreenPool::id(const QString &connector) const 0240 { 0241 QString screenId = m_screensTable.idForName(connector); 0242 return screenId.isEmpty() ? NOSCREENID : screenId.toInt(); 0243 } 0244 0245 QString ScreenPool::connector(int id) const 0246 { 0247 QString idStr = QString::number(id); 0248 return (m_screensTable.containsId(idStr) ? m_screensTable[idStr].name : QString()); 0249 } 0250 0251 int ScreenPool::firstAvailableId() const 0252 { 0253 //start counting from 10, first numbers will be used for special cases e.g. primaryScreen, id=0 0254 int availableId = FIRSTSCREENID; 0255 0256 for (int row=0; row<m_screensTable.rowCount(); ++row) { 0257 if (!m_screensTable.containsId(QString::number(availableId))) { 0258 return availableId; 0259 } 0260 0261 availableId++; 0262 } 0263 0264 return availableId; 0265 } 0266 0267 bool ScreenPool::hasScreenId(int screenId) const 0268 { 0269 return ((screenId>=0) && m_screensTable.containsId(QString::number(screenId))); 0270 } 0271 0272 bool ScreenPool::isScreenActive(int screenId) const 0273 { 0274 if (hasScreenId(screenId)) { 0275 QString scrName = connector(screenId); 0276 0277 for (const auto scr : qGuiApp->screens()) { 0278 if (scr->name() == scrName) { 0279 return true; 0280 } 0281 } 0282 } 0283 0284 return false; 0285 } 0286 0287 QScreen *ScreenPool::primaryScreen() const 0288 { 0289 return m_primaryWatcher->primaryScreen(); 0290 } 0291 0292 QScreen *ScreenPool::screenForId(int id) 0293 { 0294 const auto screens = qGuiApp->screens(); 0295 QScreen *screen{primaryScreen()}; 0296 0297 if (hasScreenId(id)) { 0298 QString scrName = connector(id); 0299 0300 for (const auto scr : screens) { 0301 if (scr->name() == scrName) { 0302 return scr; 0303 } 0304 } 0305 } 0306 0307 return screen; 0308 } 0309 0310 }