File indexing completed on 2024-04-21 05:31:16

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 }