File indexing completed on 2024-12-08 13:22:28
0001 /* 0002 * Copyright 2016 Marco Martin <mart@kde.org> 0003 * 0004 * This program is free software; you can redistribute it and/or modify 0005 * it under the terms of the GNU Library General Public License as 0006 * published by the Free Software Foundation; either version 2, or 0007 * (at your option) any later version. 0008 * 0009 * This program is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 * GNU General Public License for more details 0013 * 0014 * You should have received a copy of the GNU Library General Public 0015 * License along with this program; if not, write to the 0016 * Free Software Foundation, Inc., 0017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 0018 */ 0019 0020 #include "screenpool.h" 0021 0022 // local 0023 #include <config-latte.h> 0024 0025 // Qt 0026 #include <QDebug> 0027 #include <QFile> 0028 #include <QGuiApplication> 0029 #include <QScreen> 0030 0031 // KDE 0032 #include <KLocalizedString> 0033 0034 // X11 0035 #if HAVE_X11 0036 #include <QtX11Extras/QX11Info> 0037 #include <xcb/xcb.h> 0038 #include <xcb/randr.h> 0039 #include <xcb/xcb_event.h> 0040 #endif 0041 0042 namespace Latte { 0043 0044 ScreenPool::ScreenPool(KSharedConfig::Ptr config, QObject *parent) 0045 : QObject(parent), 0046 m_configGroup(KConfigGroup(config, QStringLiteral("ScreenConnectors"))) 0047 { 0048 qApp->installNativeEventFilter(this); 0049 0050 m_configSaveTimer.setSingleShot(true); 0051 connect(&m_configSaveTimer, &QTimer::timeout, this, [this]() { 0052 m_configGroup.sync(); 0053 }); 0054 } 0055 0056 void ScreenPool::load() 0057 { 0058 m_primaryConnector = QString(); 0059 m_connectorForId.clear(); 0060 m_idForConnector.clear(); 0061 0062 QScreen *primary = qGuiApp->primaryScreen(); 0063 0064 if (primary) { 0065 m_primaryConnector = primary->name(); 0066 0067 if (!m_primaryConnector.isEmpty()) { 0068 //m_connectorForId[0] = m_primaryConnector; 0069 //m_idForConnector[m_primaryConnector] = 0; 0070 } 0071 } 0072 0073 //restore the known ids to connector mappings 0074 for (const QString &key : m_configGroup.keyList()) { 0075 QString connector = m_configGroup.readEntry(key, QString()); 0076 qDebug() << "connector :" << connector << " - " << key; 0077 0078 if (!key.isEmpty() && !connector.isEmpty() && 0079 !m_connectorForId.contains(key.toInt()) && 0080 !m_idForConnector.contains(connector)) { 0081 m_connectorForId[key.toInt()] = connector; 0082 m_idForConnector[connector] = key.toInt(); 0083 qDebug() << "Known Screen - " << connector << " - " << key.toInt(); 0084 } else if (m_idForConnector.value(connector) != key.toInt()) { 0085 m_configGroup.deleteEntry(key); 0086 } 0087 } 0088 0089 // if there are already connected unknown screens, map those 0090 // all needs to be populated as soon as possible, otherwise 0091 // containment->screen() will return an incorrect -1 0092 // at startup, if it' asked before corona::addOutput() 0093 // is performed, driving to the creation of a new containment 0094 for (QScreen *screen : qGuiApp->screens()) { 0095 if (!m_idForConnector.contains(screen->name())) { 0096 insertScreenMapping(firstAvailableId(), screen->name()); 0097 } 0098 } 0099 } 0100 0101 ScreenPool::~ScreenPool() 0102 { 0103 m_configGroup.sync(); 0104 } 0105 0106 0107 QString ScreenPool::reportHtml(const QList<int> &assignedScreens) const 0108 { 0109 QString report; 0110 0111 report += "<table cellspacing='8'>"; 0112 report += "<tr><td align='center'><b>" + i18nc("screen id","ID") + "</b></td>" + 0113 "<td align='center'><b>" + i18nc("screen name", "Name") + "</b></td>" + 0114 "<td align='center'><b>" + i18nc("screen type", "Type") + "</b></td>" + 0115 "<td align='center'><b>" + i18n("Docks/Panels") + "</b></td></tr>"; 0116 0117 report += "<tr><td colspan='4'><hr></td></tr>"; 0118 0119 for(const QString &connector : m_connectorForId) { 0120 int scrId = id(connector); 0121 bool hasViews = assignedScreens.contains(scrId); 0122 bool primary = m_primaryConnector == connector; 0123 bool secondary = !primary && screenExists(scrId); 0124 0125 report += "<tr>"; 0126 0127 //! ScreenId 0128 QString idStr = "[" + QString::number(scrId) + "]"; 0129 if (primary || secondary) { 0130 idStr = "<b>" + idStr +"</b>"; 0131 } 0132 report += "<td align='center'>" + idStr + "</td>"; 0133 0134 //! ScreenName and Primary flag 0135 QString connectorStr = connector; 0136 if (primary || secondary) { 0137 connectorStr = "<b>"+ connector + "</b>"; 0138 } 0139 report += "<td align='center'>" + connectorStr + "</td>"; 0140 0141 //! ScreenType 0142 QString typeStr = ""; 0143 if (primary) { 0144 typeStr = "<b><font color='green'>[" + i18nc("primary screen","Primary") + "]</font></b>"; 0145 } else if (secondary) { 0146 typeStr = "<b>[" + i18nc("secondary screen","Secondary") + "]</b>"; 0147 } else { 0148 typeStr = "<i>" + i18nc("inactive screen","inactive") + "</i>"; 0149 } 0150 0151 report += "<td align='center'>" + typeStr +"</td>"; 0152 0153 //! Screen has not assigned any docks/panels 0154 QString notAssignedStr = ""; 0155 if (!hasViews) { 0156 notAssignedStr = "<font color='red'><i>[" + i18nc("it has not latte docks/panels", "none") + "]</i></font>"; 0157 } else { 0158 notAssignedStr = " ✔ "; 0159 } 0160 0161 report += "<td align='center'>" + notAssignedStr +"</td>"; 0162 0163 report += "</tr>"; 0164 } 0165 0166 report += "</table>"; 0167 0168 return report; 0169 } 0170 0171 void ScreenPool::reload(QString path) 0172 { 0173 QFile rcfile(QString(path + "/lattedockrc")); 0174 0175 if (rcfile.exists()) { 0176 qDebug() << "load screen connectors from ::: " << rcfile.fileName(); 0177 KSharedConfigPtr newFile = KSharedConfig::openConfig(rcfile.fileName()); 0178 m_configGroup = KConfigGroup(newFile, QStringLiteral("ScreenConnectors")); 0179 load(); 0180 } 0181 0182 0183 0184 } 0185 0186 int ScreenPool::primaryScreenId() const 0187 { 0188 return id(qGuiApp->primaryScreen()->name()); 0189 } 0190 0191 QString ScreenPool::primaryConnector() const 0192 { 0193 return m_primaryConnector; 0194 } 0195 0196 void ScreenPool::setPrimaryConnector(const QString &primary) 0197 { 0198 //the ":" check fixes the strange plasma/qt issues when changing layouts 0199 //there are case that the QScreen instead of the correct screen name 0200 //returns "0:0", this check prevents from breaking the screens database 0201 //from garbage ids 0202 if ((m_primaryConnector == primary) || primary.startsWith(":")) { 0203 return; 0204 } 0205 0206 Q_ASSERT(m_idForConnector.contains(primary)); 0207 0208 /* int oldIdForPrimary = m_idForConnector.value(primary); 0209 0210 m_idForConnector[primary] = 0; 0211 m_connectorForId[0] = primary; 0212 m_idForConnector[m_primaryConnector] = oldIdForPrimary; 0213 m_connectorForId[oldIdForPrimary] = m_primaryConnector; 0214 m_primaryConnector = primary; 0215 */ 0216 save(); 0217 } 0218 0219 void ScreenPool::save() 0220 { 0221 QMap<int, QString>::const_iterator i; 0222 0223 for (i = m_connectorForId.constBegin(); i != m_connectorForId.constEnd(); ++i) { 0224 m_configGroup.writeEntry(QString::number(i.key()), i.value()); 0225 } 0226 0227 //write to disck every 30 seconds at most 0228 m_configSaveTimer.start(30000); 0229 } 0230 0231 void ScreenPool::insertScreenMapping(int id, const QString &connector) 0232 { 0233 //Q_ASSERT(!m_connectorForId.contains(id) || m_connectorForId.value(id) == connector); 0234 //Q_ASSERT(!m_idForConnector.contains(connector) || m_idForConnector.value(connector) == id); 0235 0236 //the ":" check fixes the strange plasma/qt issues when changing layouts 0237 //there are case that the QScreen instead of the correct screen name 0238 //returns "0:0", this check prevents from breaking the screens database 0239 //from garbage ids 0240 if (connector.startsWith(":")) 0241 return; 0242 0243 qDebug() << "add connector..." << connector; 0244 0245 if (id == 0) { 0246 m_primaryConnector = connector; 0247 } else { 0248 m_connectorForId[id] = connector; 0249 m_idForConnector[connector] = id; 0250 } 0251 0252 save(); 0253 } 0254 0255 int ScreenPool::id(const QString &connector) const 0256 { 0257 if (!m_idForConnector.contains(connector)) { 0258 return -1; 0259 } 0260 0261 return m_idForConnector.value(connector); 0262 } 0263 0264 QString ScreenPool::connector(int id) const 0265 { 0266 Q_ASSERT(m_connectorForId.contains(id)); 0267 0268 return m_connectorForId.value(id); 0269 } 0270 0271 int ScreenPool::firstAvailableId() const 0272 { 0273 //start counting from 10, first numbers will 0274 //be used for special cases. 0275 //e.g primaryScreen, id=0 0276 int i = 10; 0277 0278 //find the first integer not stored in m_connectorForId 0279 //m_connectorForId is the only map, so the ids are sorted 0280 for (const int &existingId : m_connectorForId.keys()) { 0281 if (i != existingId) { 0282 return i; 0283 } 0284 0285 ++i; 0286 } 0287 0288 return i; 0289 } 0290 0291 QList <int> ScreenPool::knownIds() const 0292 { 0293 return m_connectorForId.keys(); 0294 } 0295 0296 bool ScreenPool::hasId(int id) const 0297 { 0298 return ((id!=-1) && m_connectorForId.keys().contains(id)); 0299 } 0300 0301 bool ScreenPool::screenExists(int id) const 0302 { 0303 if (id != -1 && knownIds().contains(id)) { 0304 QString scrName = connector(id); 0305 0306 for (const auto scr : qGuiApp->screens()) { 0307 if (scr->name() == scrName) { 0308 return true; 0309 } 0310 } 0311 } 0312 0313 return false; 0314 } 0315 0316 QScreen *ScreenPool::screenForId(int id) 0317 { 0318 const auto screens = qGuiApp->screens(); 0319 QScreen *screen{qGuiApp->primaryScreen()}; 0320 0321 if (id != -1 && knownIds().contains(id)) { 0322 QString scrName = connector(id); 0323 0324 for (const auto scr : screens) { 0325 if (scr->name() == scrName) { 0326 return scr; 0327 } 0328 } 0329 } 0330 0331 return screen; 0332 } 0333 0334 0335 bool ScreenPool::nativeEventFilter(const QByteArray &eventType, void *message, long int *result) 0336 { 0337 Q_UNUSED(result); 0338 #if HAVE_X11 0339 0340 // a particular edge case: when we switch the only enabled screen 0341 // we don't have any signal about it, the primary screen changes but we have the same old QScreen* getting recycled 0342 // see https://bugs.kde.org/show_bug.cgi?id=373880 0343 // 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 0344 if (eventType != "xcb_generic_event_t") { 0345 return false; 0346 } 0347 0348 xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message); 0349 0350 const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev); 0351 0352 const xcb_query_extension_reply_t *reply = xcb_get_extension_data(QX11Info::connection(), &xcb_randr_id); 0353 0354 if (responseType == reply->first_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { 0355 if (qGuiApp->primaryScreen()->name() != primaryConnector()) { 0356 //new screen? 0357 if (id(qGuiApp->primaryScreen()->name()) < 0) { 0358 insertScreenMapping(firstAvailableId(), qGuiApp->primaryScreen()->name()); 0359 } 0360 0361 //switch the primary screen in the pool 0362 setPrimaryConnector(qGuiApp->primaryScreen()->name()); 0363 0364 emit primaryPoolChanged(); 0365 } 0366 } 0367 0368 #endif 0369 return false; 0370 } 0371 0372 } 0373 0374 #include "moc_screenpool.cpp"