diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -61,6 +61,7 @@ waylanddialogfilter.cpp coronatesthelper.cpp debug.cpp + screenpool.cpp ${scripting_SRC} ) @@ -111,3 +112,4 @@ DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) add_subdirectory(packageplugins) +add_subdirectory(autotests) diff --git a/shell/autotests/CMakeLists.txt b/shell/autotests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/shell/autotests/CMakeLists.txt @@ -0,0 +1,23 @@ +include(ECMAddTests) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}/.. ${CMAKE_CURRENT_SOURCE_DIR}/..) + +MACRO(PLASMASHELL_UNIT_TESTS) + FOREACH(_testname ${ARGN}) + add_executable(${_testname} ${_testname}.cpp ../screenpool.cpp ) + target_link_libraries(${_testname} + Qt5::Test + Qt5::Gui + KF5::Service + ) + if(QT_QTOPENGL_FOUND) + target_link_libraries(${_testname} Qt5::OpenGL) + endif() + add_test(${_testname} ${_testname}) + ecm_mark_as_test(${_testname}) + ENDFOREACH(_testname) +ENDMACRO(PLASMASHELL_UNIT_TESTS) + +PLASMASHELL_UNIT_TESTS( + screenpooltest +) diff --git a/shell/autotests/desktopview.h b/shell/autotests/desktopview.h new file mode 100644 --- /dev/null +++ b/shell/autotests/desktopview.h @@ -0,0 +1,47 @@ +/* + * Copyright 2016 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef DESKTOPVIEW_H +#define DESKTOPVIEW_H + +#include +#include +#include + +class QScreen; + + +class DesktopView : public QWindow +{ + Q_OBJECT + +public: + explicit DesktopView(Plasma::Corona *c, QScreen *targetScreen = nullptr); + ~DesktopView() override; + + /*This is different from screen() as is always there, even if the window is + temporarly outside the screen or if is hidden: only plasmashell will ever + change this property, unlike QWindow::screen()*/ + void setScreenToFollow(QScreen *screen); + QScreen *screenToFollow() const; + +private: + QPointer m_screenToFollow; +}; + +#endif // DESKTOPVIEW_H diff --git a/shell/autotests/desktopview.cpp b/shell/autotests/desktopview.cpp new file mode 100644 --- /dev/null +++ b/shell/autotests/desktopview.cpp @@ -0,0 +1,50 @@ +/* + * Copyright 2016 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "desktopview.h" + +#include + +DesktopView::DesktopView(Plasma::Corona *c, QScreen *targetScreen) + : QWindow(targetScreen) +{ + if (targetScreen) { + setScreenToFollow(targetScreen); + setScreen(targetScreen); + setGeometry(targetScreen->geometry()); + } +} + +DesktopView::~DesktopView() +{ +} + +void DesktopView::setScreenToFollow(QScreen *screen) +{ + if (screen == m_screenToFollow) { + return; + } + + m_screenToFollow = screen; + setScreen(screen); +} + +QScreen *DesktopView::screenToFollow() const +{ + return m_screenToFollow; +} diff --git a/shell/autotests/screenpooltest.cpp b/shell/autotests/screenpooltest.cpp new file mode 100644 --- /dev/null +++ b/shell/autotests/screenpooltest.cpp @@ -0,0 +1,85 @@ +/******************************************************************** +Copyright 2016 Marco Martin + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ + +#include + +#include +#include +#include +#include + +#include "../screenpool.h" + +class ScreenPoolTest : public QObject +{ +Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void testScreenInsertion(); + void testPrimarySwap(); + +private: + ScreenPool *m_screenPool; +}; + +void ScreenPoolTest::initTestCase() +{ + QStandardPaths::enableTestMode(true); + + m_screenPool = new ScreenPool(KSharedConfig::openConfig(), this); +} + +void ScreenPoolTest::cleanupTestCase() +{ +} + +void ScreenPoolTest::testScreenInsertion() +{ + m_screenPool->insertScreenMapping(0, QStringLiteral("DVI-0")); + QCOMPARE(m_screenPool->knownIds().count(), 1); + QCOMPARE(m_screenPool->connector(0), QStringLiteral("DVI-0")); + QCOMPARE(m_screenPool->id(QStringLiteral("DVI-0")), 0); + + m_screenPool->insertScreenMapping(1, QStringLiteral("VGA-0")); + QCOMPARE(m_screenPool->knownIds().count(), 2); + QCOMPARE(m_screenPool->connector(1), QStringLiteral("VGA-0")); + QCOMPARE(m_screenPool->id(QStringLiteral("VGA-0")), 1); +} + +void ScreenPoolTest::testPrimarySwap() +{ + QCOMPARE(m_screenPool->primaryConnector(), QStringLiteral("DVI-0")); + m_screenPool->setPrimaryConnector(QStringLiteral("VGA-0")); + + QCOMPARE(m_screenPool->knownIds().count(), 2); + + QCOMPARE(m_screenPool->connector(0), QStringLiteral("VGA-0")); + QCOMPARE(m_screenPool->id(QStringLiteral("VGA-0")), 0); + + QCOMPARE(m_screenPool->connector(1), QStringLiteral("DVI-0")); + QCOMPARE(m_screenPool->id(QStringLiteral("DVI-0")), 1); +} + +QTEST_MAIN(ScreenPoolTest) + +#include "screenpooltest.moc" diff --git a/shell/desktopview.h b/shell/desktopview.h --- a/shell/desktopview.h +++ b/shell/desktopview.h @@ -58,6 +58,12 @@ explicit DesktopView(Plasma::Corona *corona, QScreen *targetScreen = 0); ~DesktopView() override; + /*This is different from screen() as is always there, even if the window is + temporarly outside the screen or if is hidden: only plasmashell will ever + change this property, unlike QWindow::screen()*/ + void setScreenToFollow(QScreen *screen); + QScreen *screenToFollow() const; + void adaptToScreen(); void showEvent(QShowEvent*) override; @@ -90,6 +96,7 @@ QPointer m_configView; QPointer m_oldScreen; + QPointer m_screenToFollow; WindowType m_windowType; KWayland::Client::PlasmaShellSurface *m_shellSurface; }; diff --git a/shell/desktopview.cpp b/shell/desktopview.cpp --- a/shell/desktopview.cpp +++ b/shell/desktopview.cpp @@ -42,6 +42,7 @@ m_shellSurface(nullptr) { if (targetScreen) { + setScreenToFollow(targetScreen); setScreen(targetScreen); setGeometry(targetScreen->geometry()); } @@ -78,33 +79,55 @@ adaptToScreen(); } +void DesktopView::setScreenToFollow(QScreen *screen) +{ + if (screen == m_screenToFollow) { + return; + } + + /*connect(screen, &QObject::destroyed, this, [this]() { + if (DesktopView::screen()) { + m_screenToFollow = DesktopView::screen(); + adaptToScreen(); + } + });*/ + m_screenToFollow = screen; + setScreen(screen); + adaptToScreen(); +} + +QScreen *DesktopView::screenToFollow() const +{ + return m_screenToFollow; +} + void DesktopView::adaptToScreen() { ensureWindowType(); //This happens sometimes, when shutting down the process - if (!screen() || m_oldScreen==screen()) { + if (!m_screenToFollow || m_oldScreen==m_screenToFollow) { return; } if(m_oldScreen) { disconnect(m_oldScreen.data(), &QScreen::geometryChanged, this, &DesktopView::screenGeometryChanged); } -// qDebug() << "adapting to screen" << screen()->name() << this; +// qDebug() << "adapting to screen" << m_screenToFollow->name() << this; + if(m_oldScreen) { + disconnect(m_oldScreen.data(), &QScreen::geometryChanged, + this, &DesktopView::screenGeometryChanged); + } + if ((m_windowType == Desktop || m_windowType == WindowedDesktop) && !ShellManager::s_forceWindowed) { screenGeometryChanged(); - if(m_oldScreen) { - disconnect(m_oldScreen.data(), &QScreen::geometryChanged, - this, &DesktopView::screenGeometryChanged); - } - connect(screen(), &QScreen::geometryChanged, + connect(m_screenToFollow.data(), &QScreen::geometryChanged, this, &DesktopView::screenGeometryChanged, Qt::UniqueConnection); - } - m_oldScreen = screen(); + m_oldScreen = m_screenToFollow; } DesktopView::WindowType DesktopView::windowType() const @@ -265,7 +288,7 @@ void DesktopView::screenGeometryChanged() { - const QRect geo = screen()->geometry(); + const QRect geo = m_screenToFollow->geometry(); // qDebug() << "newGeometry" << this << geo << geometry(); setGeometry(geo); setMinimumSize(geo.size()); @@ -299,6 +322,6 @@ return; } m_shellSurface = interface->createSurface(s, this); - m_shellSurface->setPosition(screen()->geometry().topLeft()); + m_shellSurface->setPosition(m_screenToFollow->geometry().topLeft()); } } diff --git a/shell/panelview.h b/shell/panelview.h --- a/shell/panelview.h +++ b/shell/panelview.h @@ -86,7 +86,7 @@ /** * informations about the screen in which the panel is in */ - Q_PROPERTY(QScreen *screen READ screen WRITE setScreen NOTIFY screenChangedProxy) + Q_PROPERTY(QScreen *screen READ screenToFollow WRITE setScreenToFollow NOTIFY screenToFollowChanged) /** * how the panel behaves, visible, autohide etc. @@ -146,6 +146,12 @@ void updateStruts(); + /*This is different from screen() as is always there, even if the window is + temporarly outside the screen or if is hidden: only plasmashell will ever + change this property, unlike QWindow::screen()*/ + void setScreenToFollow(QScreen* screen); + QScreen* screenToFollow() const; + protected: void resizeEvent(QResizeEvent *ev) override; void showEvent(QShowEvent *event) override; @@ -165,7 +171,7 @@ void enabledBordersChanged(); //QWindow does not have a property for screen. Adding this property requires re-implementing the signal - void screenChangedProxy(QScreen *screen); + void screenToFollowChanged(QScreen *screen); void visibilityModeChanged(); protected Q_SLOTS: @@ -184,9 +190,9 @@ void statusChanged(Plasma::Types::ItemStatus); void restoreAutoHide(); void screenDestroyed(QObject* screen); + void adaptToScreen(); private: - void moveScreen(QScreen* screen); void resizePanel(); void integrateScreen(); bool containmentContainsPosition(const QPointF &point) const; @@ -213,7 +219,8 @@ Plasma::FrameSvg *m_background; Plasma::FrameSvg::EnabledBorders m_enabledBorders = Plasma::FrameSvg::AllBorders; KWayland::Client::PlasmaShellSurface *m_shellSurface; - QWeakPointer m_lastScreen; + QPointer m_lastScreen; + QPointer m_screenToFollow; static const int STRUTSTIMERDELAY = 200; }; diff --git a/shell/panelview.cpp b/shell/panelview.cpp --- a/shell/panelview.cpp +++ b/shell/panelview.cpp @@ -22,6 +22,7 @@ #include "shellcorona.h" #include "panelshadows_p.h" #include "panelconfigview.h" +#include "screenpool.h" #include #include @@ -64,6 +65,7 @@ { if (targetScreen) { setPosition(targetScreen->geometry().center()); + setScreenToFollow(targetScreen); setScreen(targetScreen); } setResizeMode(QuickViewSharedEngine::SizeRootObjectToView); @@ -87,8 +89,6 @@ this, &PanelView::restoreAutoHide); m_lastScreen = targetScreen; - connect(screen(), SIGNAL(geometryChanged(QRect)), - &m_positionPaneltimer, SLOT(start())); connect(this, SIGNAL(locationChanged(Plasma::Types::Location)), &m_positionPaneltimer, SLOT(start())); connect(this, SIGNAL(containmentChanged()), @@ -132,16 +132,16 @@ KConfigGroup PanelView::config() const { - return panelConfig(m_corona, containment(), screen()); + return panelConfig(m_corona, containment(), m_screenToFollow); } void PanelView::maximize() { int length; if (containment()->formFactor() == Plasma::Types::Vertical) { - length = screen()->size().height(); + length = m_screenToFollow->size().height(); } else { - length = screen()->size().width(); + length = m_screenToFollow->size().width(); } setOffset(0); setMinimumLength(length); @@ -177,12 +177,12 @@ } if (formFactor() == Plasma::Types::Vertical) { - if (offset + m_maxLength > screen()->size().height()) { - setMaximumLength( -m_offset + screen()->size().height() ); + if (offset + m_maxLength > m_screenToFollow->size().height()) { + setMaximumLength( -m_offset + m_screenToFollow->size().height() ); } } else { - if (offset + m_maxLength > screen()->size().width()) { - setMaximumLength( -m_offset + screen()->size().width() ); + if (offset + m_maxLength > m_screenToFollow->size().width()) { + setMaximumLength( -m_offset + m_screenToFollow->size().width() ); } } @@ -358,6 +358,7 @@ } const QPoint pos = geometryByDistance(m_distance).topLeft(); setPosition(pos); + if (m_shellSurface) { m_shellSurface->setPosition(pos); } @@ -369,7 +370,7 @@ QRect PanelView::geometryByDistance(int distance) const { - QScreen *s = screen(); + QScreen *s = m_screenToFollow; QPoint position; const QRect screenGeometry = s->geometry(); @@ -440,13 +441,13 @@ { if (formFactor() == Plasma::Types::Vertical) { const int minSize = qMax(MINSIZE, m_minLength); - const int maxSize = qMin(m_maxLength, screen()->size().height() - m_offset); + const int maxSize = qMin(m_maxLength, m_screenToFollow->size().height() - m_offset); setMinimumSize(QSize(thickness(), minSize)); setMaximumSize(QSize(thickness(), maxSize)); resize(thickness(), qBound(minSize, m_contentLength, maxSize)); } else { const int minSize = qMax(MINSIZE, m_minLength); - const int maxSize = qMin(m_maxLength, screen()->size().width() - m_offset); + const int maxSize = qMin(m_maxLength, m_screenToFollow->size().width() - m_offset); setMinimumSize(QSize(minSize, thickness())); setMaximumSize(QSize(maxSize, thickness())); resize(qBound(minSize, m_contentLength, maxSize), thickness()); @@ -476,27 +477,27 @@ setMinimumSize(QSize(-1, -1)); //FIXME: an invalid size doesn't work with QWindows - setMaximumSize(screen()->size()); + setMaximumSize(m_screenToFollow->size()); if (containment()->formFactor() == Plasma::Types::Vertical) { - defaultMaxLength = screen()->size().height(); - defaultMinLength = screen()->size().height(); + defaultMaxLength = m_screenToFollow->size().height(); + defaultMinLength = m_screenToFollow->size().height(); m_maxLength = config().readEntry("maxLength", defaultMaxLength); m_minLength = config().readEntry("minLength", defaultMinLength); - const int maxSize = screen()->size().height() - m_offset; + const int maxSize = m_screenToFollow->size().height() - m_offset; m_maxLength = qBound(MINSIZE, m_maxLength, maxSize); m_minLength = qBound(MINSIZE, m_minLength, maxSize); //Horizontal } else { - defaultMaxLength = screen()->size().width(); - defaultMinLength = screen()->size().width(); + defaultMaxLength = m_screenToFollow->size().width(); + defaultMinLength = m_screenToFollow->size().width(); m_maxLength = config().readEntry("maxLength", defaultMaxLength); m_minLength = config().readEntry("minLength", defaultMinLength); - const int maxSize = screen()->size().width() - m_offset; + const int maxSize = m_screenToFollow->size().width() - m_offset; m_maxLength = qBound(MINSIZE, m_maxLength, maxSize); m_minLength = qBound(MINSIZE, m_minLength, maxSize); } @@ -643,6 +644,8 @@ void PanelView::integrateScreen() { + connect(m_screenToFollow, SIGNAL(geometryChanged(QRect)), + this, SLOT(positionPanel())); themeChanged(); KWindowSystem::setOnAllDesktops(winId(), true); KWindowSystem::setType(winId(), NET::Dock); @@ -652,27 +655,51 @@ } setVisibilityMode(m_visibilityMode); - containment()->reactToScreenChange(); + if (containment()) { + containment()->reactToScreenChange(); + } } void PanelView::showEvent(QShowEvent *event) { PanelShadows::self()->addWindow(this, enabledBorders()); PlasmaQuick::ContainmentView::showEvent(event); - //When the screen is set, the screen is recreated internally, so we need to - //set anything that depends on the winId() - connect(this, &QWindow::screenChanged, this, &PanelView::moveScreen, Qt::UniqueConnection); integrateScreen(); } -void PanelView::moveScreen(QScreen* screen) +void PanelView::setScreenToFollow(QScreen *screen) { - emit screenChangedProxy(screen); - m_lastScreen = screen; + if (screen == m_screenToFollow) { + return; + } + + /*connect(screen, &QObject::destroyed, this, [this]() { + if (PanelView::screen()) { + m_screenToFollow = PanelView::screen(); + adaptToScreen(); + } + });*/ - if (!screen) + m_screenToFollow = screen; + setScreen(screen); + adaptToScreen(); +} + +QScreen *PanelView::screenToFollow() const +{ + return m_screenToFollow; +} + +void PanelView::adaptToScreen() +{ + emit screenToFollowChanged(m_screenToFollow); + m_lastScreen = m_screenToFollow; + + if (!m_screenToFollow) { return; + } + integrateScreen(); showTemporarily(); m_positionPaneltimer.start(); @@ -846,27 +873,26 @@ void PanelView::updateStruts() { - if (!containment() || !screen()) { + if (!containment() || !m_screenToFollow) { return; } NETExtendedStrut strut; if (m_visibilityMode == NormalPanel) { - const QRect thisScreen = screen()->geometry(); + const QRect thisScreen = m_screenToFollow->geometry(); // QScreen::virtualGeometry() is very unreliable (Qt 5.5) - const QRect wholeScreen = QRect(QPoint(0, 0), screen()->virtualSize()); + const QRect wholeScreen = QRect(QPoint(0, 0), m_screenToFollow->virtualSize()); //Extended struts against a screen edge near to another screen are really harmful, so windows maximized under the panel is a lesser pain //TODO: force "windows can cover" in those cases? - const int numScreens = corona()->numScreens(); - for (int i = 0; i < numScreens; ++i) { - if (i == containment()->screen()) { + foreach (int id, m_corona->screenIds()) { + if (id == containment()->screen()) { continue; } - const QRect otherScreen = corona()->screenGeometry(i); + const QRect otherScreen = corona()->screenGeometry(id); if (!otherScreen.isValid()) { continue; } @@ -1019,8 +1045,8 @@ // NOTE: this is overriding the screen destroyed slot, we need to do this because // otherwise Qt goes mental and starts moving our panels. See: // https://codereview.qt-project.org/#/c/88351/ -// if(screen == this->screen()) { -// DO NOTHING, panels are moved by ::removeScreen +// if(screen == this->m_screenToFollow) { +// DO NOTHING, panels are moved by ::readaptToScreen // } } @@ -1070,16 +1096,16 @@ break; } - if (x() <= screen()->geometry().x()) { + if (x() <= m_screenToFollow->geometry().x()) { borders &= ~Plasma::FrameSvg::LeftBorder; } - if (x() + width() >= screen()->geometry().x() + screen()->geometry().width()) { + if (x() + width() >= m_screenToFollow->geometry().x() + m_screenToFollow->geometry().width()) { borders &= ~Plasma::FrameSvg::RightBorder; } - if (y() <= screen()->geometry().y()) { + if (y() <= m_screenToFollow->geometry().y()) { borders &= ~Plasma::FrameSvg::TopBorder; } - if (y() + height() >= screen()->geometry().y() + screen()->geometry().height()) { + if (y() + height() >= m_screenToFollow->geometry().y() + m_screenToFollow->geometry().height()) { borders &= ~Plasma::FrameSvg::BottomBorder; } diff --git a/shell/screenpool.h b/shell/screenpool.h new file mode 100644 --- /dev/null +++ b/shell/screenpool.h @@ -0,0 +1,64 @@ +/* + * Copyright 2016 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SCREENPOOL_H +#define SCREENPOOL_H + +#include +#include +#include +#include + +#include +#include + +class ScreenPool : public QObject { + Q_OBJECT + +public: + ScreenPool(KSharedConfig::Ptr config, QObject *parent = nullptr); + ~ScreenPool() override; + + QString primaryConnector() const; + void setPrimaryConnector(const QString &primary); + + void insertScreenMapping(int id, const QString &connector); + + int id(const QString &connector) const; + + QString connector(int id) const; + + int firstAvailableId() const; + + //all ids that are known, included screens not enabled at the moment + QList knownIds() const; + +private: + void save(); + + KConfigGroup m_configGroup; + QString m_primaryConnector; + //order is important + QMap m_connectorForId; + QHash m_idForConnector; + + QTimer m_configSaveTimer; +}; + +#endif // SCREENPOOL_H diff --git a/shell/screenpool.cpp b/shell/screenpool.cpp new file mode 100644 --- /dev/null +++ b/shell/screenpool.cpp @@ -0,0 +1,143 @@ +/* + * Copyright 2016 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "screenpool.h" + +#include +#include + +ScreenPool::ScreenPool(KSharedConfig::Ptr config, QObject *parent) + : QObject(parent), + m_configGroup(KConfigGroup(config, QStringLiteral("ScreenConnectors"))) +{ + m_configSaveTimer.setSingleShot(true); + connect(&m_configSaveTimer, &QTimer::timeout, this, [this](){ + m_configGroup.sync(); + }); + QScreen *primary = qGuiApp->primaryScreen(); + 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 + foreach (const QString &key, m_configGroup.keyList()) { + QString connector = m_configGroup.readEntry(key, QString()); + if (!key.isEmpty() && !connector.isEmpty() && + !m_connectorForId.contains(key.toInt()) && + !m_idForConnector.contains(connector)) { + m_connectorForId[key.toInt()] = connector; + m_idForConnector[connector] = key.toInt(); + } + } +} + +ScreenPool::~ScreenPool() +{ + m_configGroup.sync(); +} + +QString ScreenPool::primaryConnector() const +{ + return m_primaryConnector; +} + +void ScreenPool::setPrimaryConnector(const QString &primary) +{ + if (m_primaryConnector == primary) { + return; + } + Q_ASSERT(m_idForConnector.contains(primary)); + + int oldId = m_idForConnector.value(primary); + + m_idForConnector[primary] = 0; + m_connectorForId[0] = primary; + m_idForConnector[m_primaryConnector] = oldId; + m_connectorForId[oldId] = m_primaryConnector; + + m_primaryConnector = primary; + save(); +} + +void ScreenPool::save() +{ + QMap::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 +{ + if (!m_idForConnector.contains(connector)) { + return -1; + } + + return m_idForConnector.value(connector); +} + +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 ScreenPool::knownIds() const +{ + return m_connectorForId.keys(); +} + +#include "moc_screenpool.cpp" + diff --git a/shell/scripting/scriptengine.cpp b/shell/scripting/scriptengine.cpp --- a/shell/scripting/scriptengine.cpp +++ b/shell/scripting/scriptengine.cpp @@ -51,6 +51,7 @@ #include "widget.h" #include "../shellcorona.h" #include "../standaloneappcorona.h" +#include "../screenpool.h" QScriptValue constructQRectFClass(QScriptEngine *engine); @@ -158,15 +159,17 @@ // this can happen when the activity already exists but has never been activated // with the current shell package and layout.js is run to set up the shell for the // first time - const int numScreens = env->m_corona->numScreens(); - for (int i = 0; i < numScreens; ++i) { - ShellCorona *sc = qobject_cast(env->m_corona); - StandaloneAppCorona *ac = qobject_cast(env->m_corona); - if (sc) { + ShellCorona *sc = qobject_cast(env->m_corona); + StandaloneAppCorona *ac = qobject_cast(env->m_corona); + if (sc) { + foreach (int i, sc->screenIds()) { Plasma::Containment *c = sc->createContainmentForActivity(id, i); containments.setProperty(count, env->wrap(c)); ++count; - } else if (ac) { + } + } else if (ac) { + const int numScreens = env->m_corona->numScreens(); + for (int i = 0; i < numScreens; ++i) { Plasma::Containment *c = ac->createContainmentForActivity(id, i); containments.setProperty(count, env->wrap(c)); ++count; diff --git a/shell/shellcorona.h b/shell/shellcorona.h --- a/shell/shellcorona.h +++ b/shell/shellcorona.h @@ -36,6 +36,7 @@ class PanelView; class QMenu; class QScreen; +class ScreenPool; namespace KActivities { @@ -96,16 +97,19 @@ Plasma::Containment *setContainmentTypeForScreen(int screen, const QString &plugin); - QScreen *screenForId(int screenId) const; - void remove(DesktopView *desktopView); + void removeDesktop(DesktopView *desktopView); /** * @returns a new containment associated with the specified @p activity and @p screen. */ Plasma::Containment *createContainmentForActivity(const QString &activity, int screenNum); KWayland::Client::PlasmaShell *waylandPlasmaShellInterface() const; + ScreenPool *screenPool() const; + + QList screenIds() const; + protected: bool eventFilter(QObject *watched, QEvent *event) override; @@ -193,8 +197,6 @@ private: void updateStruts(); - QScreen *insertScreen(QScreen *screen, int idx); - void removeView(int idx); bool isOutputRedundant(QScreen* screen) const; void reconsiderOutputs(); QList panelsForScreen(QScreen *screen) const; @@ -208,9 +210,11 @@ void insertContainment(const QString &activity, int screenNum, Plasma::Containment *containment); + ScreenPool *m_screenPool; QString m_shell; - QList m_views; KActivities::Controller *m_activityController; + //map from screen number to desktop view, qmap as order is important + QMap m_desktopViewforId; QHash m_panelViews; KConfigGroup m_desktopDefaultsConfig; QList m_waitingPanels; diff --git a/shell/shellcorona.cpp b/shell/shellcorona.cpp --- a/shell/shellcorona.cpp +++ b/shell/shellcorona.cpp @@ -59,6 +59,7 @@ #include "plasmaquick/configview.h" #include "shellmanager.h" #include "osd.h" +#include "screenpool.h" #include "waylanddialogfilter.h" #include "plasmashelladaptor.h" @@ -81,6 +82,7 @@ ShellCorona::ShellCorona(QObject *parent) : Plasma::Corona(parent), + m_screenPool(new ScreenPool(KSharedConfig::openConfig(), this)), m_activityController(new KActivities::Controller(this)), m_addPanelAction(nullptr), m_addPanelsMenu(nullptr), @@ -198,8 +200,6 @@ ShellCorona::~ShellCorona() { - qDeleteAll(m_views); - m_views.clear(); while (!containments().isEmpty()) { //deleting a containment will remove it from the list due to QObject::destroyed connect in Corona delete containments().first(); @@ -312,23 +312,6 @@ return m_shell; } -bool outputLess(QScreen* a, QScreen* b) -{ - const QPoint aPos = a->geometry().topLeft(); - const QPoint bPos = b->geometry().topLeft(); - - return (qGuiApp->primaryScreen() == a - || (qGuiApp->primaryScreen() != b && (aPos.x() < bPos.x() - || (aPos.x() == bPos.x() && aPos.y() < bPos.y())))); -} - -static QList sortOutputs(const QList &outputs) -{ - QList ret = outputs; - std::sort(ret.begin(), ret.end(), outputLess); - return ret; -} - void ShellCorona::load() { if (m_shell.isEmpty() || @@ -366,7 +349,7 @@ } } - for (QScreen* screen : sortOutputs(qGuiApp->screens())) { + for (QScreen* screen : qGuiApp->screens()) { addOutput(screen); } connect(qGuiApp, &QGuiApplication::screenAdded, this, &ShellCorona::addOutput); @@ -388,53 +371,35 @@ void ShellCorona::primaryOutputChanged() { - if (m_views.isEmpty()) { + if (!m_desktopViewforId.contains(0)) { return; } - QScreen *oldPrimary = m_views[0]->screen(); + QScreen *oldPrimary = m_desktopViewforId.value(0)->screen(); QScreen *newPrimary = qGuiApp->primaryScreen(); if (!newPrimary || newPrimary == oldPrimary) { return; } - bool screenAlreadyUsed = false; - foreach(DesktopView *view, m_views){ - if(view->screen() == newPrimary) - screenAlreadyUsed = true; - } + qWarning()<<"Old primary output:"<id(newPrimary->name()); + //swap order in m_desktopViewforId + if (m_desktopViewforId.contains(0) && m_desktopViewforId.contains(oldIdOfPrimary)) { + DesktopView *oldPrimaryDesktop = m_desktopViewforId.value(0); + DesktopView *newPrimaryDesktop = m_desktopViewforId.value(oldIdOfPrimary); - if(!screenAlreadyUsed){ - //This happens when a new primary output gets connected and primaryOutputChanged() is called before addOutput() - //addOutput will take care of setting the primary screen - return; + oldPrimaryDesktop->setScreenToFollow(newPrimaryDesktop->screenToFollow()); + newPrimaryDesktop->setScreenToFollow(newPrimary); + m_desktopViewforId[0] = newPrimaryDesktop; + m_desktopViewforId[oldIdOfPrimary] = oldPrimaryDesktop; } - - qDebug() << "primary changed!" << oldPrimary->name() << newPrimary->name(); - - foreach (DesktopView *view, m_views) { - if (view->screen() == newPrimary) { - Q_ASSERT(m_views[0]->screen() != view->screen()); - - Q_ASSERT(oldPrimary != newPrimary); - Q_ASSERT(m_views[0]->screen() == oldPrimary); - Q_ASSERT(m_views[0]->screen() != newPrimary); -// Q_ASSERT(m_views[0]->geometry() == oldPrimary->geometry()); - qDebug() << "adapting" << newPrimary->geometry() << oldPrimary->geometry(); - - view->setScreen(oldPrimary); - break; - } - } - - m_views[0]->setScreen(newPrimary); - Q_ASSERT(m_views[0]->screen()==newPrimary); + m_screenPool->setPrimaryConnector(newPrimary->name()); foreach (PanelView *panel, m_panelViews) { if (panel->screen() == oldPrimary) { - panel->setScreen(newPrimary); + panel->setScreenToFollow(newPrimary); } else if (panel->screen() == newPrimary) { - panel->setScreen(oldPrimary); + panel->setScreenToFollow(oldPrimary); } } @@ -444,41 +409,40 @@ #ifndef NDEBUG void ShellCorona::screenInvariants() const { - Q_ASSERT(m_views.count() <= QGuiApplication::screens().count()); - QScreen *s = m_views.isEmpty() ? nullptr : m_views[0]->screen(); + Q_ASSERT(m_desktopViewforId.keys().count() <= QGuiApplication::screens().count()); + QScreen *s = !m_desktopViewforId.contains(0) ? nullptr : m_desktopViewforId.value(0)->screen(); QScreen* ks = qGuiApp->primaryScreen(); Q_ASSERT(ks == s); QSet screens; - int i = 0; - foreach (const DesktopView *view, m_views) { + foreach (const int id, m_desktopViewforId.keys()) { + const DesktopView *view = m_desktopViewforId.value(id); QScreen *screen = view->screen(); Q_ASSERT(!screens.contains(screen)); Q_ASSERT(!m_redundantOutputs.contains(screen)); // commented out because a different part of the code-base is responsible for this // and sometimes is not yet called here. // Q_ASSERT(!view->fillScreen() || view->geometry() == screen->geometry()); Q_ASSERT(view->containment()); - Q_ASSERT(view->containment()->screen() == i || view->containment()->screen() == -1); - Q_ASSERT(view->containment()->lastScreen() == i || view->containment()->lastScreen() == -1); + Q_ASSERT(view->containment()->screen() == id || view->containment()->screen() == -1); + Q_ASSERT(view->containment()->lastScreen() == id || view->containment()->lastScreen() == -1); Q_ASSERT(view->isVisible()); foreach (const PanelView *panel, panelsForScreen(screen)) { Q_ASSERT(panel->containment()); - Q_ASSERT(panel->containment()->screen() == i || panel->containment()->screen() == -1); + Q_ASSERT(panel->containment()->screen() == id || panel->containment()->screen() == -1); Q_ASSERT(panel->isVisible()); } screens.insert(screen); - ++i; } foreach (QScreen* out, m_redundantOutputs) { Q_ASSERT(isOutputRedundant(out)); } - if (m_views.isEmpty()) { + if (m_desktopViewforId.isEmpty()) { qWarning() << "no screens!!"; } } @@ -619,23 +583,23 @@ QRect ShellCorona::screenGeometry(int id) const { - if (id >= m_views.count() || id < 0) { + if (!m_desktopViewforId.contains(id)) { qWarning() << "requesting unexisting screen" << id; QScreen *s = qGuiApp->primaryScreen(); return s ? s->geometry() : QRect(); } - return m_views[id]->geometry(); + return m_desktopViewforId.value(id)->geometry(); } QRegion ShellCorona::availableScreenRegion(int id) const { - if (id >= m_views.count() || id < 0) { + if (!m_desktopViewforId.contains(id)) { //each screen should have a view qWarning() << "requesting unexisting screen" << id; QScreen *s = qGuiApp->primaryScreen(); return s ? s->availableGeometry() : QRegion(); } - DesktopView *view = m_views[id]; + DesktopView *view = m_desktopViewforId.value(id); QRegion r = view->geometry(); foreach (const PanelView *v, m_panelViews) { @@ -649,14 +613,14 @@ QRect ShellCorona::availableScreenRect(int id) const { - if (id >= m_views.count() || id < 0) { + if (!m_desktopViewforId.contains(id)) { //each screen should have a view qWarning() << "requesting unexisting screen" << id; QScreen *s = qGuiApp->primaryScreen(); return s ? s->availableGeometry() : QRect(); } - DesktopView *view = m_views[id]; + DesktopView *view = m_desktopViewforId.value(id); QRect r = view->geometry(); foreach (PanelView *v, m_panelViews) { @@ -686,15 +650,28 @@ return m_activityContainmentPlugins.keys(); } -QScreen *ShellCorona::screenForId(int screenId) const +void ShellCorona::removeDesktop(DesktopView *desktopView) { - DesktopView *v = m_views.value(screenId); - return v ? v->screen() : nullptr; -} + const int idx = m_screenPool->id(desktopView->screenToFollow()->name()); -void ShellCorona::remove(DesktopView *desktopView) -{ - removeView(m_views.indexOf(desktopView)); + if (!m_desktopViewforId.contains(idx)) { + return; + } + + QMutableHashIterator it(m_panelViews); + while (it.hasNext()) { + it.next(); + PanelView *panelView = it.value(); + + if (panelView->containment()->screen() == idx) { + m_waitingPanels << panelView->containment(); + it.remove(); + delete panelView; + } + } + + delete m_desktopViewforId.value(idx); + m_desktopViewforId.remove(idx); } PanelView *ShellCorona::panelView(Plasma::Containment *containment) const @@ -717,65 +694,14 @@ DesktopView* ShellCorona::desktopForScreen(QScreen* screen) const { - foreach (DesktopView *v, m_views) { - if (v->screen() == screen) { - return v; - } - } - return Q_NULLPTR; -} - -void ShellCorona::removeView(int idx) -{ - if (idx < 0 || idx >= m_views.count() || m_views.isEmpty()) { - return; - } - - bool panelsAltered = false; - - const QScreen *lastScreen = m_views.last()->screen(); - QMutableHashIterator it(m_panelViews); - while (it.hasNext()) { - it.next(); - PanelView *panelView = it.value(); - - if (panelView->screen() == lastScreen) { - m_waitingPanels << panelView->containment(); - it.remove(); - delete panelView; - panelsAltered = true; - } - } - - for (int i = m_views.count() - 2; i >= idx; --i) { - QScreen *screen = m_views[i + 1]->screen(); - QScreen *oldScreen = m_views[i]->screen(); - - const bool wasVisible = m_views[idx]->isVisible(); - m_views[i]->setScreen(screen); - - if (wasVisible) { - m_views[idx]->show(); //when destroying the screen, QScreen might have hidden the window - } - - const QList panels = panelsForScreen(oldScreen); - panelsAltered = panelsAltered || !panels.isEmpty(); - foreach (PanelView *p, panels) { - p->setScreen(screen); - } - } - - delete m_views.takeLast(); - - if (panelsAltered) { - emit availableScreenRectChanged(); - } + return m_desktopViewforId.value(m_screenPool->id(screen->name())); } void ShellCorona::screenRemoved(QScreen* screen) { - if (DesktopView* v = desktopForScreen(screen)) - remove(v); + if (DesktopView* v = desktopForScreen(screen)) { + removeDesktop(v); + } m_reconsiderOutputsTimer.start(); m_redundantOutputs.remove(screen); @@ -816,7 +742,7 @@ qDebug() << "new redundant screen" << screen; if (DesktopView* v = desktopForScreen(screen)) - remove(v); + removeDesktop(v); m_redundantOutputs.insert(screen); } @@ -844,32 +770,27 @@ m_redundantOutputs.remove(screen); } - int insertPosition = 0; - foreach (DesktopView *view, m_views) { - if (outputLess(screen, view->screen())) { - break; - } - - insertPosition++; + int insertPosition = m_screenPool->id(screen->name()); + if (insertPosition < 0) { + insertPosition = m_screenPool->firstAvailableId(); } - QScreen* newScreen = insertScreen(screen, insertPosition); - - DesktopView *view = new DesktopView(this, newScreen); + DesktopView *view = new DesktopView(this, screen); connect(view, &QQuickWindow::sceneGraphError, this, &ShellCorona::showOpenGLNotCompatibleWarning); - Plasma::Containment *containment = createContainmentForActivity(m_activityController->currentActivity(), m_views.count()); + Plasma::Containment *containment = createContainmentForActivity(m_activityController->currentActivity(), insertPosition); Q_ASSERT(containment); QAction *removeAction = containment->actions()->action(QStringLiteral("remove")); if (removeAction) { removeAction->deleteLater(); } - m_views.append(view); + m_screenPool->insertScreenMapping(insertPosition, screen->name()); + m_desktopViewforId[insertPosition] = view; view->setContainment(containment); view->show(); - Q_ASSERT(newScreen == view->screen()); + Q_ASSERT(screen == view->screen()); //need to specifically call the reactToScreenChange, since when the screen is shown it's not yet //in the list. We still don't want to have an invisible view added. @@ -885,25 +806,6 @@ CHECK_SCREEN_INVARIANTS } -QScreen* ShellCorona::insertScreen(QScreen *screen, int idx) -{ - if (idx == m_views.count()) { - return screen; - } - - DesktopView *v = m_views[idx]; - QScreen *oldScreen = v->screen(); - v->setScreen(screen); - Q_ASSERT(v->screen() == screen); - foreach (PanelView *panel, m_panelViews) { - if (panel->screen() == oldScreen) { - panel->setScreen(screen); - } - } - - return insertScreen(oldScreen, idx+1); -} - Plasma::Containment *ShellCorona::createContainmentForActivity(const QString& activity, int screenNum) { if (m_desktopContainments.contains(activity)) { @@ -939,24 +841,24 @@ //ignore non existing (yet?) screens int requestedScreen = cont->lastScreen(); if (requestedScreen < 0) { - ++requestedScreen; + requestedScreen = 0; } - if (requestedScreen > (m_views.count() - 1)) { + if (!m_desktopViewforId.contains(requestedScreen)) { stillWaitingPanels << cont; continue; } - Q_ASSERT(qBound(0, requestedScreen, m_views.count() - 1) == requestedScreen); - QScreen *screen = m_views[requestedScreen]->screen(); + //TODO: does a similar check make sense? + //Q_ASSERT(qBound(0, requestedScreen, m_screenPool->count() - 1) == requestedScreen); + QScreen *screen = m_desktopViewforId.value(requestedScreen)->screenToFollow(); PanelView* panel = new PanelView(this, screen); connect(panel, &QQuickWindow::sceneGraphError, this, &ShellCorona::showOpenGLNotCompatibleWarning); connect(panel, &QWindow::visibleChanged, this, &Plasma::Corona::availableScreenRectChanged); connect(panel, &PanelView::locationChanged, this, &Plasma::Corona::availableScreenRectChanged); connect(panel, &PanelView::visibilityModeChanged, this, &Plasma::Corona::availableScreenRectChanged); connect(panel, &PanelView::thicknessChanged, this, &Plasma::Corona::availableScreenRectChanged); - m_panelViews[cont] = panel; panel->setContainment(cont); panel->show(); @@ -1034,7 +936,7 @@ void ShellCorona::toggleWidgetExplorer() { const QPoint cursorPos = QCursor::pos(); - foreach (DesktopView *view, m_views) { + foreach (DesktopView *view, m_desktopViewforId.values()) { if (view->screen()->geometry().contains(cursorPos)) { //The view QML has to provide something to display the widget explorer view->rootObject()->metaObject()->invokeMethod(view->rootObject(), "toggleWidgetExplorer", Q_ARG(QVariant, QVariant::fromValue(sender()))); @@ -1046,7 +948,7 @@ void ShellCorona::toggleActivityManager() { const QPoint cursorPos = QCursor::pos(); - foreach (DesktopView *view, m_views) { + foreach (DesktopView *view, m_desktopViewforId.values()) { if (view->screen()->geometry().contains(cursorPos)) { //The view QML has to provide something to display the activity explorer view->rootObject()->metaObject()->invokeMethod(view->rootObject(), "toggleActivityManager", Qt::QueuedConnection); @@ -1201,14 +1103,14 @@ { // qDebug() << "Activity changed:" << newActivity; - for (int i = 0; i < m_views.count(); ++i) { - Plasma::Containment *c = createContainmentForActivity(newActivity, i); + foreach (int id, m_desktopViewforId.keys()) { + Plasma::Containment *c = createContainmentForActivity(newActivity, id); QAction *removeAction = c->actions()->action(QStringLiteral("remove")); if (removeAction) { removeAction->deleteLater(); } - m_views[i]->setContainment(c); + m_desktopViewforId.value(id)->setContainment(c); } } @@ -1252,10 +1154,10 @@ } m_activityContainmentPlugins.insert(id, plugin); - for (int i = 0; i < m_views.count(); ++i) { - Plasma::Containment *c = createContainmentForActivity(id, i); + foreach (int screenId, m_desktopViewforId.keys()) { + Plasma::Containment *c = createContainmentForActivity(id, screenId); if (c) { - c->config().writeEntry("lastScreen", i); + c->config().writeEntry("lastScreen", screenId); } } } @@ -1274,7 +1176,7 @@ } DesktopView *view = 0; - foreach (DesktopView *v, m_views) { + foreach (DesktopView *v, m_desktopViewforId.values()) { if (v->containment() == oldContainment) { view = v; break; @@ -1527,7 +1429,7 @@ foreach (QScreen *screen, QGuiApplication::screens()) { //m_panelViews.contains(panel) == false iff addPanel is executed in a startup script if (screen->geometry().contains(cursorPos) && m_panelViews.contains(panel)) { - m_panelViews[panel]->setScreen(screen); + m_panelViews[panel]->setScreenToFollow(screen); break; } } @@ -1546,21 +1448,16 @@ } //if the desktop views already exist, base the decision upon them - for (int i = 0; i < m_views.count(); i++) { - if (m_views[i]->containment() == containment && containment->activity() == m_activityController->currentActivity()) { - return i; + foreach (int id, m_desktopViewforId.keys()) { + if (m_desktopViewforId.value(id)->containment() == containment && containment->activity() == m_activityController->currentActivity()) { + return id; } } //if the panel views already exist, base upon them PanelView *view = m_panelViews.value(containment); if (view) { - QScreen *screen = view->screen(); - for (int i = 0; i < m_views.count(); i++) { - if (m_views[i]->screen() == screen) { - return i; - } - } + return m_screenPool->id(view->screenToFollow()->name()); } //Failed? fallback on lastScreen() @@ -1699,6 +1596,16 @@ return m_waylandPlasmaShell; } +ScreenPool *ShellCorona::screenPool() const +{ + return m_screenPool; +} + +QList ShellCorona::screenIds() const +{ + return m_desktopViewforId.keys(); +} + void ShellCorona::updateStruts() { foreach(PanelView* view, m_panelViews) {