diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt index 18b960fbc..a7fdae1fc 100644 --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -1,97 +1,98 @@ configure_file(config-ktexteditor.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ktexteditor.h ) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-plasma.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-plasma.h) add_definitions(-DPLASMA_DEPRECATED=) set(scripting_SRC scripting/appinterface.cpp scripting/applet.cpp scripting/containment.cpp scripting/configgroup.cpp scripting/i18n.cpp scripting/panel.cpp scripting/rect.cpp scripting/scriptengine.cpp scripting/scriptengine_v1.cpp scripting/widget.cpp ) set(plasmashell_dbusXML dbus/org.kde.PlasmaShell.xml) qt5_add_dbus_adaptor(scripting_SRC ${plasmashell_dbusXML} shellcorona.h ShellCorona plasmashelladaptor) ecm_qt_declare_logging_category(plasmashell HEADER debug.h IDENTIFIER PLASMASHELL CATEGORY_NAME kde.plasmashell DEFAULT_SEVERITY Info) set (plasma_shell_SRCS alternativeshelper.cpp main.cpp containmentconfigview.cpp currentcontainmentactionsmodel.cpp desktopview.cpp panelview.cpp panelconfigview.cpp panelshadows.cpp shellcorona.cpp shellmanager.cpp standaloneappcorona osd.cpp coronatesthelper.cpp debug.cpp screenpool.cpp ${scripting_SRC} ) set(krunner_xml ${plasma-workspace_SOURCE_DIR}/krunner/dbus/org.kde.krunner.App.xml) qt5_add_dbus_interface(plasma_shell_SRCS ${krunner_xml} krunner_interface) add_executable(plasmashell ${plasma_shell_SRCS} ) target_link_libraries(plasmashell Qt5::Quick Qt5::DBus KF5::KIOCore KF5::WindowSystem KF5::Crash KF5::Plasma KF5::PlasmaQuick Qt5::Script KF5::Solid KF5::Declarative KF5::I18n KF5::IconThemes KF5::Activities KF5::GlobalAccel KF5::CoreAddons KF5::DBusAddons KF5::Declarative KF5::QuickAddons KF5::XmlGui KF5::Package KF5::WaylandClient + KF5::WindowSystem ) target_include_directories(plasmashell PRIVATE "${CMAKE_BINARY_DIR}") target_compile_definitions(plasmashell PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}") if(HAVE_X11) target_link_libraries(plasmashell ${X11_LIBRARIES} ${XCB_LIBRARIES} ) target_link_libraries(plasmashell Qt5::X11Extras) endif() install(TARGETS plasmashell ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES org.kde.plasmashell.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES org.kde.plasmashell.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR}) install( FILES dbus/org.kde.PlasmaShell.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) install(FILES scripting/plasma-layouttemplate.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) add_subdirectory(packageplugins) if(BUILD_TESTING) add_subdirectory(autotests) endif() diff --git a/shell/autotests/CMakeLists.txt b/shell/autotests/CMakeLists.txt index bcbcacab5..9338449ff 100644 --- a/shell/autotests/CMakeLists.txt +++ b/shell/autotests/CMakeLists.txt @@ -1,27 +1,28 @@ 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 + KF5::WindowSystem ) if(HAVE_X11) target_link_libraries(${_testname} ${X11_LIBRARIES} ${XCB_LIBRARIES} ) target_link_libraries(${_testname} Qt5::X11Extras) endif() if(QT_QTOPENGL_FOUND) target_link_libraries(${_testname} Qt5::OpenGL) endif() add_test(NAME ${_testname} COMMAND ${_testname}) ecm_mark_as_test(${_testname}) ENDFOREACH(_testname) ENDMACRO(PLASMASHELL_UNIT_TESTS) PLASMASHELL_UNIT_TESTS( screenpooltest ) diff --git a/shell/screenpool.cpp b/shell/screenpool.cpp index 4cb0e2751..436dafc62 100644 --- a/shell/screenpool.cpp +++ b/shell/screenpool.cpp @@ -1,206 +1,213 @@ /* * 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 #include +#include #if HAVE_X11 #include #include #include #include #endif 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(); }); + +#if HAVE_X11 + if (KWindowSystem::isPlatformX11()) { + qApp->installNativeEventFilter(this); + const xcb_query_extension_reply_t* reply = xcb_get_extension_data(QX11Info::connection(), &xcb_randr_id); + m_xrandrExtensionOffset = reply->first_event; + } +#endif } void ScreenPool::load() { m_primaryConnector = QString(); m_connectorForId.clear(); m_idForConnector.clear(); 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(); } else if (m_idForConnector.value(connector) != key.toInt()) { m_configGroup.deleteEntry(key); } } // 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()) { if (!m_idForConnector.contains(screen->name())) { insertScreenMapping(firstAvailableId(), screen->name()); } } } 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 oldIdForPrimary = m_idForConnector.value(primary); 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::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(); } 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 (responseType == m_xrandrExtensionOffset + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { if (qGuiApp->primaryScreen()->name() != primaryConnector()) { //new screen? if (id(qGuiApp->primaryScreen()->name()) < 0) { insertScreenMapping(firstAvailableId(), qGuiApp->primaryScreen()->name()); } //switch the primary screen in the pool setPrimaryConnector(qGuiApp->primaryScreen()->name()); } } #endif return false; } #include "moc_screenpool.cpp" diff --git a/shell/screenpool.h b/shell/screenpool.h index 14f799d20..f1ac2dee8 100644 --- a/shell/screenpool.h +++ b/shell/screenpool.h @@ -1,70 +1,71 @@ /* * 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 #include class ScreenPool : public QObject, public QAbstractNativeEventFilter { Q_OBJECT public: ScreenPool(KSharedConfig::Ptr config, QObject *parent = nullptr); void load(); ~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; protected: bool nativeEventFilter(const QByteArray & eventType, void * message, long * result) override; private: void save(); KConfigGroup m_configGroup; QString m_primaryConnector; //order is important QMap m_connectorForId; QHash m_idForConnector; QTimer m_configSaveTimer; + int m_xrandrExtensionOffset; }; #endif // SCREENPOOL_H