/* SPDX-FileCopyrightText: 2016 Marco Martin SPDX-License-Identifier: LGPL-2.0-or-later */ #include "screenpool.h" // local #include // Qt #include #include #include #include // KDE #include // X11 #if HAVE_X11 #include #include #include #include #endif namespace Latte { const int ScreenPool::FIRSTSCREENID; ScreenPool::ScreenPool(KSharedConfig::Ptr config, QObject *parent) : QObject(parent), m_configGroup(KConfigGroup(config, QStringLiteral("ScreenConnectors"))) { qApp->installNativeEventFilter(this); m_configSaveTimer.setSingleShot(true); connect(&m_configSaveTimer, &QTimer::timeout, this, [this]() { m_configGroup.sync(); }); connect(qGuiApp, &QGuiApplication::screenAdded, this, &ScreenPool::onScreenAdded, Qt::UniqueConnection); connect(qGuiApp, &QGuiApplication::screenRemoved, this, &ScreenPool::onScreenRemoved, Qt::UniqueConnection); } ScreenPool::~ScreenPool() { m_configGroup.sync(); } void ScreenPool::load() { m_lastPrimaryConnector = QString(); m_screensTable.clear(); QScreen *primary = qGuiApp->primaryScreen(); if (primary) { m_lastPrimaryConnector = primary->name(); } //restore the known ids to connector mappings for (const QString &key : m_configGroup.keyList()) { if (key.toInt() <= 0) { continue; } QString serialized = m_configGroup.readEntry(key, QString()); Data::Screen screenRecord(key, serialized); //qDebug() << "org.kde.latte ::: " << screenRecord.id << ":" << screenRecord.serialize(); if (!key.isEmpty() && !serialized.isEmpty() && !m_screensTable.containsId(key)) { m_screensTable << screenRecord; qDebug() << "org.kde.latte :: Known Screen - " << screenRecord.id << " : " << screenRecord.name << " : " << screenRecord.geometry; } } // if there are already connected unknown screens, map those // all needs to be populated as soon as possible, otherwise // containment->screen() will return an incorrect -1 // at startup, if it' asked before corona::addOutput() // is performed, driving to the creation of a new containment for (QScreen *screen : qGuiApp->screens()) { onScreenRemoved(screen); if (!m_screensTable.containsName(screen->name())) { insertScreenMapping(screen->name()); } else { updateScreenGeometry(screen); } onScreenAdded(screen); } } void ScreenPool::onScreenAdded(const QScreen *screen) { connect(screen, &QScreen::geometryChanged, this, [&, screen]() { updateScreenGeometry(screen); }); } void ScreenPool::onScreenRemoved(const QScreen *screen) { disconnect(screen, &QScreen::geometryChanged, this, nullptr); } void ScreenPool::updateScreenGeometry(const QScreen *screen) { if (!screen) { return; } if (m_screensTable.containsName(screen->name())) { updateScreenGeometry(id(screen->name()), screen->geometry()); } } void ScreenPool::updateScreenGeometry(const int &screenId, const QRect &screenGeometry) { QString scrIdStr = QString::number(screenId); if (!m_screensTable.containsId(scrIdStr) || m_screensTable[scrIdStr].geometry == screenGeometry) { return; } m_screensTable[scrIdStr].geometry = screenGeometry; save(); emit screenGeometryChanged(); } Latte::Data::ScreensTable ScreenPool::screensTable() { return m_screensTable; } void ScreenPool::reload(QString path) { QFile rcfile(QString(path + "/lattedockrc")); if (rcfile.exists()) { qDebug() << "load screen connectors from ::: " << rcfile.fileName(); KSharedConfigPtr newFile = KSharedConfig::openConfig(rcfile.fileName()); m_configGroup = KConfigGroup(newFile, QStringLiteral("ScreenConnectors")); load(); } } void ScreenPool::removeScreens(const Latte::Data::ScreensTable &obsoleteScreens) { for (int i=0; iprimaryScreen()->name()); } void ScreenPool::save() { QMap::const_iterator i; for (int i=0; i= FIRSTSCREENID) { m_configGroup.writeEntry(screenRecord.id, screenRecord.serialize()); } } //write to disck every 10 seconds at most m_configSaveTimer.start(10000); } void ScreenPool::insertScreenMapping(const QString &connector) { //the ":" check fixes the strange plasma/qt issues when changing layouts //there are case that the QScreen instead of the correct screen name //returns "0:0", this check prevents from breaking the screens database //from garbage ids if (m_screensTable.containsName(connector) || connector.startsWith(":")) { return; } qDebug() << "add connector..." << connector; Data::Screen screenRecord; screenRecord.id = QString::number(firstAvailableId()); screenRecord.name = connector; //! update screen geometry for (const auto scr : qGuiApp->screens()) { if (scr->name() == connector) { screenRecord.geometry = scr->geometry(); break; } } m_screensTable << screenRecord; save(); } int ScreenPool::id(const QString &connector) const { QString screenId = m_screensTable.idForName(connector); return screenId.isEmpty() ? -1 : screenId.toInt(); } QString ScreenPool::connector(int id) const { QString idStr = QString::number(id); return (m_screensTable.containsId(idStr) ? m_screensTable[idStr].name : QString()); } int ScreenPool::firstAvailableId() const { //start counting from 10, first numbers will be used for special cases e.g. primaryScreen, id=0 int availableId = FIRSTSCREENID; for (int row=0; row=0) && m_screensTable.containsId(QString::number(screenId))); } bool ScreenPool::isScreenActive(int screenId) const { if (hasScreenId(screenId)) { QString scrName = connector(screenId); for (const auto scr : qGuiApp->screens()) { if (scr->name() == scrName) { return true; } } } return false; } QScreen *ScreenPool::screenForId(int id) { const auto screens = qGuiApp->screens(); QScreen *screen{qGuiApp->primaryScreen()}; if (hasScreenId(id)) { QString scrName = connector(id); for (const auto scr : screens) { if (scr->name() == scrName) { return scr; } } } return screen; } bool ScreenPool::nativeEventFilter(const QByteArray &eventType, void *message, long int *result) { Q_UNUSED(result); #if HAVE_X11 // a particular edge case: when we switch the only enabled screen // we don't have any signal about it, the primary screen changes but we have the same old QScreen* getting recycled // see https://bugs.kde.org/show_bug.cgi?id=373880 // if this slot will be invoked many times, their//second time on will do nothing as name and primaryconnector will be the same by then if (eventType != "xcb_generic_event_t") { return false; } xcb_generic_event_t *ev = static_cast(message); const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev); const xcb_query_extension_reply_t *reply = xcb_get_extension_data(QX11Info::connection(), &xcb_randr_id); if (responseType == reply->first_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { if (qGuiApp->primaryScreen()->name() != m_lastPrimaryConnector) { //new screen? if (id(qGuiApp->primaryScreen()->name()) < 0) { insertScreenMapping(qGuiApp->primaryScreen()->name()); } m_lastPrimaryConnector = qGuiApp->primaryScreen()->name(); emit primaryPoolChanged(); } } #endif return false; } } #include "moc_screenpool.cpp"