mirror of
https://github.com/Qortal/Brooklyn.git
synced 2025-02-23 07:35:54 +00:00
560 lines
21 KiB
C++
560 lines
21 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
|
|
|
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
|
*/
|
|
|
|
#include "screenpool.h"
|
|
#include "primaryoutputwatcher.h"
|
|
#include "screenpool-debug.h"
|
|
|
|
#include <KWindowSystem>
|
|
#include <QDebug>
|
|
#include <QGuiApplication>
|
|
#include <QScreen>
|
|
|
|
#ifndef NDEBUG
|
|
#define CHECK_SCREEN_INVARIANTS screenInvariants();
|
|
#else
|
|
#define CHECK_SCREEN_INVARIANTS
|
|
#endif
|
|
|
|
ScreenPool::ScreenPool(const KSharedConfig::Ptr &config, QObject *parent)
|
|
: QObject(parent)
|
|
, m_configGroup(KConfigGroup(config, QStringLiteral("ScreenConnectors")))
|
|
, m_primaryWatcher(new PrimaryOutputWatcher(this))
|
|
{
|
|
connect(qGuiApp, &QGuiApplication::screenAdded, this, &ScreenPool::handleScreenAdded);
|
|
connect(qGuiApp, &QGuiApplication::screenRemoved, this, &ScreenPool::handleScreenRemoved);
|
|
connect(m_primaryWatcher, &PrimaryOutputWatcher::primaryOutputNameChanged, this, &ScreenPool::handlePrimaryOutputNameChanged);
|
|
|
|
m_reconsiderOutputsTimer.setSingleShot(true);
|
|
m_reconsiderOutputsTimer.setInterval(250);
|
|
connect(&m_reconsiderOutputsTimer, &QTimer::timeout, this, &ScreenPool::reconsiderOutputs);
|
|
|
|
m_configSaveTimer.setSingleShot(true);
|
|
connect(&m_configSaveTimer, &QTimer::timeout, this, [this]() {
|
|
m_configGroup.sync();
|
|
});
|
|
}
|
|
|
|
void ScreenPool::load()
|
|
{
|
|
QScreen *primary = m_primaryWatcher->primaryScreen();
|
|
m_primaryConnector = QString();
|
|
m_connectorForId.clear();
|
|
m_idForConnector.clear();
|
|
|
|
if (primary) {
|
|
m_primaryConnector = primary->name();
|
|
if (!m_primaryConnector.isEmpty()) {
|
|
m_connectorForId[0] = m_primaryConnector;
|
|
m_idForConnector[m_primaryConnector] = 0;
|
|
}
|
|
}
|
|
|
|
// restore the known ids to connector mappings
|
|
const auto keys = m_configGroup.keyList();
|
|
for (const QString &key : keys) {
|
|
QString connector = m_configGroup.readEntry(key, QString());
|
|
const int currentId = key.toInt();
|
|
|
|
if (!key.isEmpty() && !connector.isEmpty() && !m_connectorForId.contains(currentId) && !m_idForConnector.contains(connector)) {
|
|
m_connectorForId[currentId] = connector;
|
|
m_idForConnector[connector] = currentId;
|
|
} else if (m_idForConnector.value(connector) != currentId) {
|
|
m_configGroup.deleteEntry(key);
|
|
}
|
|
}
|
|
|
|
// Populate allthe screen based on what's connected at startup
|
|
for (QScreen *screen : qGuiApp->screens()) {
|
|
// On some devices QGuiApp::screenAdded is always emitted for some screens at startup so at this point that screen would already be managed
|
|
if (!m_allSortedScreens.contains(screen)) {
|
|
handleScreenAdded(screen);
|
|
} else if (!m_idForConnector.contains(screen->name())) {
|
|
insertScreenMapping(firstAvailableId(), screen->name());
|
|
}
|
|
}
|
|
CHECK_SCREEN_INVARIANTS
|
|
}
|
|
|
|
ScreenPool::~ScreenPool()
|
|
{
|
|
m_configGroup.sync();
|
|
}
|
|
|
|
QString ScreenPool::primaryConnector() const
|
|
{
|
|
return m_primaryConnector;
|
|
}
|
|
|
|
void ScreenPool::setPrimaryConnector(const QString &primary)
|
|
{
|
|
if (m_primaryConnector == primary) {
|
|
return;
|
|
}
|
|
|
|
int oldIdForPrimary = m_idForConnector.value(primary, -1);
|
|
if (oldIdForPrimary == -1) {
|
|
// move old primary to new free id
|
|
oldIdForPrimary = firstAvailableId();
|
|
}
|
|
|
|
m_idForConnector[primary] = 0;
|
|
m_connectorForId[0] = primary;
|
|
m_idForConnector[m_primaryConnector] = oldIdForPrimary;
|
|
m_connectorForId[oldIdForPrimary] = m_primaryConnector;
|
|
m_primaryConnector = primary;
|
|
save();
|
|
}
|
|
|
|
void ScreenPool::save()
|
|
{
|
|
QMap<int, QString>::const_iterator i;
|
|
for (i = m_connectorForId.constBegin(); i != m_connectorForId.constEnd(); ++i) {
|
|
m_configGroup.writeEntry(QString::number(i.key()), i.value());
|
|
}
|
|
// write to disck every 30 seconds at most
|
|
m_configSaveTimer.start(30000);
|
|
}
|
|
|
|
void ScreenPool::insertScreenMapping(int id, const QString &connector)
|
|
{
|
|
Q_ASSERT(!m_connectorForId.contains(id) || m_connectorForId.value(id) == connector);
|
|
Q_ASSERT(!m_idForConnector.contains(connector) || m_idForConnector.value(connector) == id);
|
|
|
|
if (id == 0) {
|
|
m_primaryConnector = connector;
|
|
}
|
|
|
|
m_connectorForId[id] = connector;
|
|
m_idForConnector[connector] = id;
|
|
save();
|
|
}
|
|
|
|
int ScreenPool::id(const QString &connector) const
|
|
{
|
|
return m_idForConnector.value(connector, -1);
|
|
}
|
|
|
|
QString ScreenPool::connector(int id) const
|
|
{
|
|
Q_ASSERT(m_connectorForId.contains(id));
|
|
|
|
return m_connectorForId.value(id);
|
|
}
|
|
|
|
int ScreenPool::firstAvailableId() const
|
|
{
|
|
int i = 0;
|
|
// find the first integer not stored in m_connectorForId
|
|
// m_connectorForId is the only map, so the ids are sorted
|
|
foreach (int existingId, m_connectorForId.keys()) {
|
|
if (i != existingId) {
|
|
return i;
|
|
}
|
|
++i;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
QList<int> ScreenPool::knownIds() const
|
|
{
|
|
return m_connectorForId.keys();
|
|
}
|
|
|
|
QList<QScreen *> ScreenPool::screens() const
|
|
{
|
|
return m_availableScreens;
|
|
}
|
|
|
|
QScreen *ScreenPool::primaryScreen() const
|
|
{
|
|
QScreen *primary = m_primaryWatcher->primaryScreen();
|
|
if (m_redundantScreens.contains(primary)) {
|
|
return m_redundantScreens[primary];
|
|
} else {
|
|
return primary;
|
|
}
|
|
}
|
|
|
|
QScreen *ScreenPool::screenForId(int id) const
|
|
{
|
|
if (!m_connectorForId.contains(id)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// TODO: do QScreen bookeeping completely in screenpool, cache also available QScreens
|
|
const QString name = m_connectorForId.value(id);
|
|
for (QScreen *screen : m_availableScreens) {
|
|
if (screen->name() == name) {
|
|
return screen;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
QScreen *ScreenPool::screenForConnector(const QString &connector)
|
|
{
|
|
for (QScreen *screen : m_availableScreens) {
|
|
if (screen->name() == connector) {
|
|
return screen;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool ScreenPool::noRealOutputsConnected() const
|
|
{
|
|
if (qApp->screens().count() > 1) {
|
|
return false;
|
|
}
|
|
|
|
return isOutputFake(m_primaryWatcher->primaryScreen());
|
|
}
|
|
|
|
bool ScreenPool::isOutputFake(QScreen *screen) const
|
|
{
|
|
Q_ASSERT(screen);
|
|
// On X11 the output named :0.0 is fake (the geometry is usually valid and whatever the geometry
|
|
// of the last connected screen was), on wayland the fake output has no name and no geometry
|
|
const bool fake = screen->name() == QStringLiteral(":0.0") || screen->geometry().isEmpty() || screen->name().isEmpty();
|
|
// If there is a fake output we can only have one screen left (the fake one)
|
|
// Q_ASSERT(!fake || fake == (qGuiApp->screens().count() == 1));
|
|
return fake;
|
|
}
|
|
|
|
QScreen *ScreenPool::outputRedundantTo(QScreen *screen) const
|
|
{
|
|
Q_ASSERT(screen);
|
|
// Manage separatedly fake screens
|
|
if (isOutputFake(screen)) {
|
|
return nullptr;
|
|
}
|
|
const QRect thisGeometry = screen->geometry();
|
|
|
|
const int thisId = id(screen->name());
|
|
|
|
// FIXME: QScreen doesn't have any idea of "this qscreen is clone of this other one
|
|
// so this ultra inefficient heuristic has to stay until we have a slightly better api
|
|
// logic is:
|
|
// a screen is redundant if:
|
|
//* its geometry is contained in another one
|
|
//* if their resolutions are different, the "biggest" one wins
|
|
//* if they have the same geometry, the one with the lowest id wins (arbitrary, but gives reproducible behavior and makes the primary screen win)
|
|
for (QScreen *s : m_allSortedScreens) {
|
|
// don't compare with itself
|
|
if (screen == s) {
|
|
continue;
|
|
}
|
|
|
|
const QRect otherGeometry = s->geometry();
|
|
|
|
if (otherGeometry.isNull()) {
|
|
continue;
|
|
}
|
|
|
|
const int otherId = id(s->name());
|
|
|
|
if (otherGeometry.contains(thisGeometry, false)
|
|
&& ( // since at this point contains is true, if either
|
|
// measure of othergeometry is bigger, has a bigger area
|
|
otherGeometry.width() > thisGeometry.width() || otherGeometry.height() > thisGeometry.height() ||
|
|
// ids not -1 are considered in descending order of importance
|
|
//-1 means that is a screen not known yet, just arrived and
|
|
// not yet in screenpool: this happens for screens that
|
|
// are hotplugged and weren't known. it does NOT happen
|
|
// at first startup, as screenpool populates on load with all screens connected at the moment before the rest of the shell starts up
|
|
(thisId == -1 && otherId != -1) || (thisId > otherId && otherId != -1))) {
|
|
return s;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void ScreenPool::reconsiderOutputs()
|
|
{
|
|
QScreen *oldPrimaryScreen = primaryScreen();
|
|
for (QScreen *screen : m_allSortedScreens) {
|
|
if (m_redundantScreens.contains(screen)) {
|
|
if (QScreen *toScreen = outputRedundantTo(screen)) {
|
|
// Insert again, redndantTo may have changed
|
|
m_fakeScreens.remove(screen);
|
|
m_redundantScreens.insert(screen, toScreen);
|
|
} else {
|
|
qCDebug(SCREENPOOL) << "not redundant anymore" << screen << (isOutputFake(screen) ? "but is a fake screen" : "");
|
|
Q_ASSERT(!m_availableScreens.contains(screen));
|
|
m_redundantScreens.remove(screen);
|
|
if (isOutputFake(screen)) {
|
|
m_fakeScreens.insert(screen);
|
|
} else {
|
|
m_fakeScreens.remove(screen);
|
|
m_availableScreens.append(screen);
|
|
if (!m_idForConnector.contains(screen->name())) {
|
|
insertScreenMapping(firstAvailableId(), screen->name());
|
|
}
|
|
Q_EMIT screenAdded(screen);
|
|
QScreen *newPrimaryScreen = primaryScreen();
|
|
if (newPrimaryScreen != oldPrimaryScreen) {
|
|
// Primary screen was redundant, not anymore
|
|
setPrimaryConnector(newPrimaryScreen->name());
|
|
Q_EMIT primaryScreenChanged(oldPrimaryScreen, newPrimaryScreen);
|
|
}
|
|
}
|
|
}
|
|
} else if (QScreen *toScreen = outputRedundantTo(screen)) {
|
|
qCDebug(SCREENPOOL) << "new redundant screen" << screen << "with primary screen" << m_primaryWatcher->primaryScreen();
|
|
|
|
m_fakeScreens.remove(screen);
|
|
m_redundantScreens.insert(screen, toScreen);
|
|
if (!m_idForConnector.contains(screen->name())) {
|
|
insertScreenMapping(firstAvailableId(), screen->name());
|
|
}
|
|
if (m_availableScreens.contains(screen)) {
|
|
QScreen *newPrimaryScreen = primaryScreen();
|
|
if (newPrimaryScreen != oldPrimaryScreen) {
|
|
// Primary screen became redundant
|
|
setPrimaryConnector(newPrimaryScreen->name());
|
|
Q_EMIT primaryScreenChanged(oldPrimaryScreen, newPrimaryScreen);
|
|
}
|
|
m_availableScreens.removeAll(screen);
|
|
Q_EMIT screenRemoved(screen);
|
|
}
|
|
} else if (isOutputFake(screen)) {
|
|
// NOTE: order of operations is important
|
|
qCDebug(SCREENPOOL) << "new fake screen" << screen;
|
|
m_redundantScreens.remove(screen);
|
|
m_fakeScreens.insert(screen);
|
|
if (m_availableScreens.contains(screen)) {
|
|
QScreen *newPrimaryScreen = primaryScreen();
|
|
if (newPrimaryScreen != oldPrimaryScreen) {
|
|
// Primary screen became fake
|
|
setPrimaryConnector(newPrimaryScreen->name());
|
|
Q_EMIT primaryScreenChanged(oldPrimaryScreen, newPrimaryScreen);
|
|
}
|
|
m_availableScreens.removeAll(screen);
|
|
Q_EMIT screenRemoved(screen);
|
|
}
|
|
} else if (m_fakeScreens.contains(screen)) {
|
|
Q_ASSERT(!m_availableScreens.contains(screen));
|
|
m_fakeScreens.remove(screen);
|
|
m_availableScreens.append(screen);
|
|
if (!m_idForConnector.contains(screen->name())) {
|
|
insertScreenMapping(firstAvailableId(), screen->name());
|
|
}
|
|
Q_EMIT screenAdded(screen);
|
|
QScreen *newPrimaryScreen = primaryScreen();
|
|
if (newPrimaryScreen != oldPrimaryScreen) {
|
|
// Primary screen was redundant, not anymore
|
|
setPrimaryConnector(newPrimaryScreen->name());
|
|
Q_EMIT primaryScreenChanged(oldPrimaryScreen, newPrimaryScreen);
|
|
}
|
|
} else {
|
|
qCDebug(SCREENPOOL) << "fine screen" << screen;
|
|
}
|
|
}
|
|
|
|
// updateStruts();
|
|
|
|
CHECK_SCREEN_INVARIANTS
|
|
}
|
|
|
|
void ScreenPool::insertSortedScreen(QScreen *screen)
|
|
{
|
|
if (m_allSortedScreens.contains(screen)) {
|
|
// This should happen only when a fake screen isn't anymore
|
|
return;
|
|
}
|
|
auto before = std::find_if(m_allSortedScreens.begin(), m_allSortedScreens.end(), [this, screen](QScreen *otherScreen) {
|
|
return (screen->geometry().width() > otherScreen->geometry().width() && screen->geometry().height() > otherScreen->geometry().height())
|
|
|| id(screen->name()) < id(otherScreen->name());
|
|
});
|
|
m_allSortedScreens.insert(before, screen);
|
|
}
|
|
|
|
void ScreenPool::handleScreenAdded(QScreen *screen)
|
|
{
|
|
qCDebug(SCREENPOOL) << "handleScreenAdded" << screen << screen->geometry();
|
|
connect(
|
|
screen,
|
|
&QScreen::geometryChanged,
|
|
this,
|
|
[this, screen]() {
|
|
m_allSortedScreens.removeAll(screen);
|
|
insertSortedScreen(screen);
|
|
m_reconsiderOutputsTimer.start();
|
|
},
|
|
Qt::UniqueConnection);
|
|
insertSortedScreen(screen);
|
|
|
|
if (isOutputFake(screen)) {
|
|
m_fakeScreens.insert(screen);
|
|
return;
|
|
} else if (!m_idForConnector.contains(screen->name())) {
|
|
insertScreenMapping(firstAvailableId(), screen->name());
|
|
}
|
|
|
|
if (QScreen *toScreen = outputRedundantTo(screen)) {
|
|
m_redundantScreens.insert(screen, toScreen);
|
|
return;
|
|
}
|
|
|
|
if (m_fakeScreens.contains(screen)) {
|
|
qCDebug(SCREENPOOL) << "not fake anymore" << screen;
|
|
m_fakeScreens.remove(screen);
|
|
}
|
|
|
|
m_reconsiderOutputsTimer.start();
|
|
Q_ASSERT(!m_availableScreens.contains(screen));
|
|
m_availableScreens.append(screen);
|
|
Q_EMIT screenAdded(screen);
|
|
}
|
|
|
|
void ScreenPool::handleScreenRemoved(QScreen *screen)
|
|
{
|
|
qCDebug(SCREENPOOL) << "handleScreenRemoved" << screen;
|
|
m_allSortedScreens.removeAll(screen);
|
|
if (m_redundantScreens.contains(screen)) {
|
|
Q_ASSERT(!m_fakeScreens.contains(screen));
|
|
Q_ASSERT(!m_availableScreens.contains(screen));
|
|
m_redundantScreens.remove(screen);
|
|
} else if (m_fakeScreens.contains(screen)) {
|
|
Q_ASSERT(!m_redundantScreens.contains(screen));
|
|
Q_ASSERT(!m_availableScreens.contains(screen));
|
|
m_fakeScreens.remove(screen);
|
|
} else if (isOutputFake(screen)) {
|
|
// This happens when an output is recicled because it was the last one and became fake
|
|
Q_ASSERT(m_availableScreens.contains(screen));
|
|
Q_ASSERT(!m_redundantScreens.contains(screen));
|
|
Q_ASSERT(!m_fakeScreens.contains(screen));
|
|
Q_ASSERT(m_allSortedScreens.isEmpty());
|
|
m_allSortedScreens.append(screen);
|
|
m_availableScreens.removeAll(screen);
|
|
m_fakeScreens.insert(screen);
|
|
} else {
|
|
Q_ASSERT(m_availableScreens.contains(screen));
|
|
Q_ASSERT(!m_redundantScreens.contains(screen));
|
|
Q_ASSERT(!m_fakeScreens.contains(screen));
|
|
m_availableScreens.removeAll(screen);
|
|
reconsiderOutputs();
|
|
Q_EMIT screenRemoved(screen);
|
|
}
|
|
CHECK_SCREEN_INVARIANTS
|
|
}
|
|
|
|
void ScreenPool::handlePrimaryOutputNameChanged(const QString &oldOutputName, const QString &newOutputName)
|
|
{
|
|
// when the appearance of a new primary screen *moves*
|
|
// the position of the now secondary, the two screens will appear overlapped for an instant, and a spurious output redundant would happen here if checked
|
|
// immediately
|
|
m_reconsiderOutputsTimer.start();
|
|
|
|
QScreen *oldPrimary = screenForConnector(oldOutputName);
|
|
QScreen *newPrimary = m_primaryWatcher->primaryScreen();
|
|
// First check if the data arrived is correct, then set the new peimary considering redundants
|
|
Q_ASSERT(newPrimary && newPrimary->name() == newOutputName);
|
|
newPrimary = primaryScreen();
|
|
|
|
// This happens when a screen that was primary because the real primary was redundant becomes the real primary
|
|
if (m_primaryConnector == newPrimary->name()) {
|
|
return;
|
|
}
|
|
|
|
if (!newPrimary || newPrimary == oldPrimary || newPrimary->geometry().isNull()) {
|
|
return;
|
|
}
|
|
|
|
// On X11 we get fake screens as primary
|
|
|
|
// Special case: we are in "no connectors" mode, there is only a (recycled) QScreen instance which is not attached to any output. Treat this as a screen
|
|
// removed This happens only on X, wayland doesn't seem to be getting fake screens
|
|
if (noRealOutputsConnected()) {
|
|
qCDebug(SCREENPOOL) << "EMITTING SCREEN REMOVED" << newPrimary;
|
|
handleScreenRemoved(newPrimary);
|
|
return;
|
|
// On X11, the output named :0.0 is fake
|
|
} else if (oldOutputName == ":0.0" || oldOutputName.isEmpty()) {
|
|
setPrimaryConnector(newOutputName);
|
|
// NOTE: when we go from 0 to 1 screen connected, screens can be renamed in those two followinf cases
|
|
// * last output connected/disconnected -> we go between the fake screen and the single output, renamed
|
|
// * external screen connected to a closed lid laptop, disconnecting the qscreen instance will be recycled from external output to internal
|
|
// In the latter case m_availableScreens will aready contain newPrimary
|
|
// We'll go here also once at startup, for which we don't need to do anything besides setting internally the primary conector name
|
|
handleScreenAdded(newPrimary);
|
|
return;
|
|
} else {
|
|
Q_ASSERT(newPrimary);
|
|
qCDebug(SCREENPOOL) << "PRIMARY CHANGED" << oldPrimary << "-->" << newPrimary;
|
|
setPrimaryConnector(newOutputName);
|
|
Q_EMIT primaryScreenChanged(oldPrimary, newPrimary);
|
|
}
|
|
}
|
|
|
|
void ScreenPool::screenInvariants()
|
|
{
|
|
// 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
|
|
// real outputs scenario
|
|
Q_ASSERT(noRealOutputsConnected() || primaryScreen()->name() == primaryConnector());
|
|
// Is the primary screen available? TODO: it can be redundant
|
|
// Q_ASSERT(m_availableScreens.contains(primaryScreen()));
|
|
|
|
// QScreen bookeeping integrity
|
|
auto allScreens = qGuiApp->screens();
|
|
// Do we actually track every screen?
|
|
|
|
Q_ASSERT((m_availableScreens.count() + m_redundantScreens.count() + m_fakeScreens.count()) == allScreens.count());
|
|
Q_ASSERT(allScreens.count() == m_allSortedScreens.count());
|
|
|
|
// At most one fake output
|
|
Q_ASSERT(m_fakeScreens.count() <= 1);
|
|
if (m_fakeScreens.count() == 1) {
|
|
// If we have a fake output we can't have anything else
|
|
Q_ASSERT(m_availableScreens.count() == 0);
|
|
Q_ASSERT(m_redundantScreens.count() == 0);
|
|
} else {
|
|
for (QScreen *screen : allScreens) {
|
|
if (m_availableScreens.contains(screen)) {
|
|
// If available can't be redundant
|
|
Q_ASSERT(!m_redundantScreens.contains(screen));
|
|
} else if (m_redundantScreens.contains(screen)) {
|
|
// If redundant can't be available
|
|
Q_ASSERT(!m_availableScreens.contains(screen));
|
|
} else {
|
|
// We can't have a screen unaccounted for
|
|
Q_ASSERT(false);
|
|
}
|
|
// Is every screen mapped to an id?
|
|
Q_ASSERT(m_idForConnector.contains(screen->name()));
|
|
// Are the two maps symmetrical?
|
|
Q_ASSERT(connector(id(screen->name())) == screen->name());
|
|
}
|
|
}
|
|
for (QScreen *screen : m_redundantScreens.keys()) {
|
|
Q_ASSERT(outputRedundantTo(screen) != nullptr);
|
|
}
|
|
}
|
|
|
|
QDebug operator<<(QDebug debug, const ScreenPool *pool)
|
|
{
|
|
debug << pool->metaObject()->className() << '(' << static_cast<const void *>(pool) << ") Internal state:\n";
|
|
debug << "Connector Mapping:\n";
|
|
auto it = pool->m_idForConnector.constBegin();
|
|
while (it != pool->m_idForConnector.constEnd()) {
|
|
debug << it.key() << "\t-->\t" << it.value() << '\n';
|
|
it++;
|
|
}
|
|
debug << "Platform primary screen:\t" << pool->m_primaryWatcher->primaryScreen() << '\n';
|
|
debug << "Actual primary screen:\t" << pool->primaryScreen() << '\n';
|
|
debug << "Available screens:\t" << pool->m_availableScreens << '\n';
|
|
debug << "\"Fake\" screens:\t" << pool->m_fakeScreens << '\n';
|
|
debug << "Redundant screens covered by other ones:\t" << pool->m_redundantScreens << '\n';
|
|
debug << "All screens, ordered by size:\t" << pool->m_allSortedScreens << '\n';
|
|
debug << "All screen that QGuiApplication knows:\t" << qGuiApp->screens() << '\n';
|
|
return debug;
|
|
}
|
|
|
|
#include "moc_screenpool.cpp"
|