diff --git a/.arclint b/.arclint new file mode 100644 --- /dev/null +++ b/.arclint @@ -0,0 +1,20 @@ +{ + "exclude": "(^test/)", + "linters": { + "spelling": { + "type": "spelling" + }, + "merge-conflict": { + "type": "merge-conflict" + }, + "xml": { + "type": "xml", + "include": "(\\.xml$)" + }, + "cppcheck": { + "type": "cppcheck", + "include": "(\\.(cpp|h|cxx|hpp)$)", + "flags": ["--language=c++", "--std=c++11"] + } + } +} diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,11 @@ cmake_minimum_required(VERSION 3.0) -set(KF5_VERSION "5.46.0") # handled by release scripts +set(KF5_VERSION "5.48.0") # handled by release scripts project(KWayland VERSION ${KF5_VERSION}) # ECM setup include(FeatureSummary) -find_package(ECM 5.46.0 NO_MODULE) +find_package(ECM 5.47.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) @@ -59,8 +59,11 @@ ecm_install_po_files_as_qm(po) endif() add_subdirectory(src) -add_subdirectory(autotests) -add_subdirectory(tests) + +if (BUILD_TESTING) + add_subdirectory(autotests) + add_subdirectory(tests) +endif() # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KF5Wayland") diff --git a/autotests/client/CMakeLists.txt b/autotests/client/CMakeLists.txt --- a/autotests/client/CMakeLists.txt +++ b/autotests/client/CMakeLists.txt @@ -442,6 +442,17 @@ ecm_mark_as_test(testRemoteAccess) ######################################################## +# Test VirtualDesktop +######################################################## +set( testPlasmaVirtualDesktop_SRCS + test_plasma_virtual_desktop.cpp + ) +add_executable(testPlasmaVirtualDesktop ${testPlasmaVirtualDesktop_SRCS}) +target_link_libraries( testPlasmaVirtualDesktop Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer) +add_test(NAME kwayland-testPlasmaVirtualDesktop COMMAND testPlasmaVirtualDesktop) +ecm_mark_as_test(testPlasmaVirtualDesktop) + +######################################################## # Test XDG Output ######################################################## set( testXdgOutput_SRCS @@ -451,3 +462,4 @@ target_link_libraries( testXdgOutput Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client Wayland::Server) add_test(NAME kwayland-testXdgOutput COMMAND testXdgOutput) ecm_mark_as_test(testXdgOutput) + diff --git a/autotests/client/test_datadevice.cpp b/autotests/client/test_datadevice.cpp --- a/autotests/client/test_datadevice.cpp +++ b/autotests/client/test_datadevice.cpp @@ -512,6 +512,12 @@ QCOMPARE(selectionOfferedSpy.count(), 2); QVERIFY(sourceCancelled2Spy.isEmpty()); + // replace the data source with itself, ensure that it did not get cancelled + dataDevice->setSelection(1, dataSource2.data()); + QVERIFY(!sourceCancelled2Spy.wait(500)); + QCOMPARE(selectionOfferedSpy.count(), 2); + QVERIFY(sourceCancelled2Spy.isEmpty()); + // create a new DataDevice and replace previous one QScopedPointer dataDevice2(m_dataDeviceManager->getDataDevice(m_seat)); QVERIFY(dataDevice2->isValid()); diff --git a/autotests/client/test_plasma_virtual_desktop.cpp b/autotests/client/test_plasma_virtual_desktop.cpp new file mode 100644 --- /dev/null +++ b/autotests/client/test_plasma_virtual_desktop.cpp @@ -0,0 +1,547 @@ +/******************************************************************** +Copyright 2018 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 . +*********************************************************************/ +// Qt +#include +// KWin +#include "../../src/client/compositor.h" +#include "../../src/client/connection_thread.h" +#include "../../src/client/event_queue.h" +#include "../../src/client/region.h" +#include "../../src/client/registry.h" +#include "../../src/client/surface.h" +#include "../../src/client/plasmavirtualdesktop.h" +#include "../../src/server/display.h" +#include "../../src/server/compositor_interface.h" +#include "../../src/server/region_interface.h" +#include "../../src/server/plasmavirtualdesktop_interface.h" +#include "../../src/server/plasmawindowmanagement_interface.h" +#include "../../src/client/plasmawindowmanagement.h" + +using namespace KWayland::Client; + +class TestVirtualDesktop : public QObject +{ + Q_OBJECT +public: + explicit TestVirtualDesktop(QObject *parent = nullptr); +private Q_SLOTS: + void init(); + void cleanup(); + + void testCreate(); + void testConnectNewClient(); + void testDestroy(); + void testActivate(); + + void testEnterLeaveDesktop(); + void testAllDesktops(); + void testCreateRequested(); + void testRemoveRequested(); + +private: + KWayland::Server::Display *m_display; + KWayland::Server::CompositorInterface *m_compositorInterface; + KWayland::Server::PlasmaVirtualDesktopManagementInterface *m_plasmaVirtualDesktopManagementInterface; + KWayland::Server::PlasmaWindowManagementInterface *m_windowManagementInterface; + KWayland::Server::PlasmaWindowInterface *m_windowInterface; + + KWayland::Client::ConnectionThread *m_connection; + KWayland::Client::Compositor *m_compositor; + KWayland::Client::PlasmaVirtualDesktopManagement *m_plasmaVirtualDesktopManagement; + KWayland::Client::EventQueue *m_queue; + KWayland::Client::PlasmaWindowManagement *m_windowManagement; + KWayland::Client::PlasmaWindow *m_window; + + QThread *m_thread; +}; + +static const QString s_socketName = QStringLiteral("kwayland-test-wayland-virtual-desktop-0"); + +TestVirtualDesktop::TestVirtualDesktop(QObject *parent) + : QObject(parent) + , m_display(nullptr) + , m_compositorInterface(nullptr) + , m_connection(nullptr) + , m_compositor(nullptr) + , m_queue(nullptr) + , m_thread(nullptr) +{ +} + +void TestVirtualDesktop::init() +{ + using namespace KWayland::Server; + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + + // setup connection + m_connection = new KWayland::Client::ConnectionThread; + QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); + QVERIFY(connectedSpy.isValid()); + m_connection->setSocketName(s_socketName); + + m_thread = new QThread(this); + m_connection->moveToThread(m_thread); + m_thread->start(); + + m_connection->initConnection(); + QVERIFY(connectedSpy.wait()); + + m_queue = new KWayland::Client::EventQueue(this); + QVERIFY(!m_queue->isValid()); + m_queue->setup(m_connection); + QVERIFY(m_queue->isValid()); + + Registry registry; + QSignalSpy compositorSpy(®istry, &Registry::compositorAnnounced); + QVERIFY(compositorSpy.isValid()); + + QSignalSpy plasmaVirtualDesktopManagementSpy(®istry, &Registry::plasmaVirtualDesktopManagementAnnounced); + QVERIFY(plasmaVirtualDesktopManagementSpy.isValid()); + + QSignalSpy windowManagementSpy(®istry, SIGNAL(plasmaWindowManagementAnnounced(quint32,quint32))); + QVERIFY(windowManagementSpy.isValid()); + + QVERIFY(!registry.eventQueue()); + registry.setEventQueue(m_queue); + QCOMPARE(registry.eventQueue(), m_queue); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + + m_compositorInterface = m_display->createCompositor(m_display); + m_compositorInterface->create(); + QVERIFY(m_compositorInterface->isValid()); + + QVERIFY(compositorSpy.wait()); + m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); + + m_plasmaVirtualDesktopManagementInterface = m_display->createPlasmaVirtualDesktopManagement(m_display); + m_plasmaVirtualDesktopManagementInterface->create(); + QVERIFY(m_plasmaVirtualDesktopManagementInterface->isValid()); + + QVERIFY(plasmaVirtualDesktopManagementSpy.wait()); + m_plasmaVirtualDesktopManagement = registry.createPlasmaVirtualDesktopManagement(plasmaVirtualDesktopManagementSpy.first().first().value(), plasmaVirtualDesktopManagementSpy.first().last().value(), this); + + m_windowManagementInterface = m_display->createPlasmaWindowManagement(m_display); + m_windowManagementInterface->create(); + QVERIFY(m_windowManagementInterface->isValid()); + m_windowManagementInterface->setPlasmaVirtualDesktopManagementInterface(m_plasmaVirtualDesktopManagementInterface); + + QVERIFY(windowManagementSpy.wait()); + m_windowManagement = registry.createPlasmaWindowManagement(windowManagementSpy.first().first().value(), windowManagementSpy.first().last().value(), this); + + QSignalSpy windowSpy(m_windowManagement, SIGNAL(windowCreated(KWayland::Client::PlasmaWindow *))); + QVERIFY(windowSpy.isValid()); + m_windowInterface = m_windowManagementInterface->createWindow(this); + m_windowInterface->setPid(1337); + + QVERIFY(windowSpy.wait()); + m_window = windowSpy.first().first().value(); +} + +void TestVirtualDesktop::cleanup() +{ +#define CLEANUP(variable) \ + if (variable) { \ + delete variable; \ + variable = nullptr; \ + } + CLEANUP(m_compositor) + CLEANUP(m_plasmaVirtualDesktopManagement) + CLEANUP(m_windowManagement) + CLEANUP(m_queue) + if (m_connection) { + m_connection->deleteLater(); + m_connection = nullptr; + } + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + CLEANUP(m_compositorInterface) + CLEANUP(m_plasmaVirtualDesktopManagementInterface) + CLEANUP(m_windowManagementInterface) + CLEANUP(m_display) +#undef CLEANUP +} + +void TestVirtualDesktop::testCreate() +{ + QSignalSpy desktopCreatedSpy(m_plasmaVirtualDesktopManagement, &PlasmaVirtualDesktopManagement::desktopCreated); + QSignalSpy managementDoneSpy(m_plasmaVirtualDesktopManagement, &PlasmaVirtualDesktopManagement::done); + + + //on this createDesktop bind() isn't called already, the desktopadded signals will be sent after bind happened + KWayland::Server::PlasmaVirtualDesktopInterface *desktop1Int = m_plasmaVirtualDesktopManagementInterface->createDesktop(QStringLiteral("0-1")); + desktop1Int->setName("Desktop 1"); + + desktopCreatedSpy.wait(); + QList arguments = desktopCreatedSpy.takeFirst(); + QCOMPARE(arguments.at(0).toString(), QStringLiteral("0-1")); + QCOMPARE(arguments.at(1).toUInt(), (quint32)0); + m_plasmaVirtualDesktopManagementInterface->sendDone(); + managementDoneSpy.wait(); + + + QCOMPARE(m_plasmaVirtualDesktopManagement->desktops().length(), 1); + + KWayland::Client::PlasmaVirtualDesktop *desktop1 = m_plasmaVirtualDesktopManagement->desktops().first(); + QSignalSpy desktop1DoneSpy(desktop1, &PlasmaVirtualDesktop::done); + desktop1Int->sendDone(); + desktop1DoneSpy.wait(); + + QCOMPARE(desktop1->id(), QStringLiteral("0-1")); + QCOMPARE(desktop1->name(), QStringLiteral("Desktop 1")); + + + //on those createDesktop the bind will already be done + KWayland::Server::PlasmaVirtualDesktopInterface *desktop2Int = m_plasmaVirtualDesktopManagementInterface->createDesktop(QStringLiteral("0-2")); + desktop2Int->setName("Desktop 2"); + desktopCreatedSpy.wait(); + arguments = desktopCreatedSpy.takeFirst(); + QCOMPARE(arguments.at(0).toString(), QStringLiteral("0-2")); + QCOMPARE(arguments.at(1).toUInt(), (quint32)1); + QCOMPARE(m_plasmaVirtualDesktopManagement->desktops().length(), 2); + + KWayland::Server::PlasmaVirtualDesktopInterface *desktop3Int = m_plasmaVirtualDesktopManagementInterface->createDesktop(QStringLiteral("0-3")); + desktop3Int->setName("Desktop 3"); + desktopCreatedSpy.wait(); + arguments = desktopCreatedSpy.takeFirst(); + QCOMPARE(arguments.at(0).toString(), QStringLiteral("0-3")); + QCOMPARE(m_plasmaVirtualDesktopManagement->desktops().length(), 3); + + m_plasmaVirtualDesktopManagementInterface->sendDone(); + managementDoneSpy.wait(); + + + //get the clients + KWayland::Client::PlasmaVirtualDesktop *desktop2 = m_plasmaVirtualDesktopManagement->desktops()[1]; + QSignalSpy desktop2DoneSpy(desktop2, &PlasmaVirtualDesktop::done); + desktop2Int->sendDone(); + desktop2DoneSpy.wait(); + + KWayland::Client::PlasmaVirtualDesktop *desktop3 = m_plasmaVirtualDesktopManagement->desktops()[2]; + QSignalSpy desktop3DoneSpy(desktop3, &PlasmaVirtualDesktop::done); + desktop3Int->sendDone(); + desktop3DoneSpy.wait(); + + + QCOMPARE(desktop1->id(), QStringLiteral("0-1")); + QCOMPARE(desktop1->name(), QStringLiteral("Desktop 1")); + + QCOMPARE(desktop2->id(), QStringLiteral("0-2")); + QCOMPARE(desktop2->name(), QStringLiteral("Desktop 2")); + + QCOMPARE(desktop3->id(), QStringLiteral("0-3")); + QCOMPARE(desktop3->name(), QStringLiteral("Desktop 3")); + + //coherence of order between client and server + QCOMPARE(m_plasmaVirtualDesktopManagementInterface->desktops().length(), 3); + QCOMPARE(m_plasmaVirtualDesktopManagement->desktops().length(), 3); + + for (int i = 0; i < m_plasmaVirtualDesktopManagement->desktops().length(); ++i) { + QCOMPARE(m_plasmaVirtualDesktopManagementInterface->desktops().at(i)->id(), m_plasmaVirtualDesktopManagement->desktops().at(i)->id()); + } +} + +void TestVirtualDesktop::testConnectNewClient() +{ + //rebuild some desktops + testCreate(); + + Registry registry; + QVERIFY(!registry.eventQueue()); + registry.setEventQueue(m_queue); + QCOMPARE(registry.eventQueue(), m_queue); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + + QSignalSpy plasmaVirtualDesktopManagementSpy(®istry, &Registry::plasmaVirtualDesktopManagementAnnounced); + QVERIFY(plasmaVirtualDesktopManagementSpy.isValid()); + + QVERIFY(plasmaVirtualDesktopManagementSpy.wait()); + + KWayland::Client::PlasmaVirtualDesktopManagement *otherPlasmaVirtualDesktopManagement = registry.createPlasmaVirtualDesktopManagement(plasmaVirtualDesktopManagementSpy.first().first().value(), plasmaVirtualDesktopManagementSpy.first().last().value(), this); + + QSignalSpy managementDoneSpy(otherPlasmaVirtualDesktopManagement, &PlasmaVirtualDesktopManagement::done); + + managementDoneSpy.wait(); + QCOMPARE(otherPlasmaVirtualDesktopManagement->desktops().length(), 3); + + delete otherPlasmaVirtualDesktopManagement; +} + +void TestVirtualDesktop::testDestroy() +{ + //rebuild some desktops + testCreate(); + + KWayland::Server::PlasmaVirtualDesktopInterface *desktop1Int = m_plasmaVirtualDesktopManagementInterface->desktops().first(); + KWayland::Client::PlasmaVirtualDesktop *desktop1 = m_plasmaVirtualDesktopManagement->desktops().first(); + + + QSignalSpy desktop1IntDestroyedSpy(desktop1Int, &QObject::destroyed); + QSignalSpy desktop1DestroyedSpy(desktop1, &QObject::destroyed); + QSignalSpy desktop1RemovedSpy(desktop1, &KWayland::Client::PlasmaVirtualDesktop::removed); + m_plasmaVirtualDesktopManagementInterface->removeDesktop(QStringLiteral("0-1")); + + //test that both server and client desktoip interfaces go away + desktop1IntDestroyedSpy.wait(); + desktop1RemovedSpy.wait(); + desktop1DestroyedSpy.wait(); + + //coherence of order between client and server + QCOMPARE(m_plasmaVirtualDesktopManagementInterface->desktops().length(), 2); + QCOMPARE(m_plasmaVirtualDesktopManagement->desktops().length(), 2); + + for (int i = 0; i < m_plasmaVirtualDesktopManagement->desktops().length(); ++i) { + QCOMPARE(m_plasmaVirtualDesktopManagementInterface->desktops().at(i)->id(), m_plasmaVirtualDesktopManagement->desktops().at(i)->id()); + } + + //Test the desktopRemoved signal of the manager, remove another desktop as the signals can't be tested at the same time + QSignalSpy desktopManagerRemovedSpy(m_plasmaVirtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopRemoved); + m_plasmaVirtualDesktopManagementInterface->removeDesktop(QStringLiteral("0-2")); + desktopManagerRemovedSpy.wait(); + QCOMPARE(desktopManagerRemovedSpy.takeFirst().at(0).toString(), QStringLiteral("0-2")); + + QCOMPARE(m_plasmaVirtualDesktopManagementInterface->desktops().length(), 1); + QCOMPARE(m_plasmaVirtualDesktopManagement->desktops().length(), 1); +} + +void TestVirtualDesktop::testActivate() +{ + //rebuild some desktops + testCreate(); + + KWayland::Server::PlasmaVirtualDesktopInterface *desktop1Int = m_plasmaVirtualDesktopManagementInterface->desktops().first(); + KWayland::Client::PlasmaVirtualDesktop *desktop1 = m_plasmaVirtualDesktopManagement->desktops().first(); + QVERIFY(desktop1->isActive()); + QVERIFY(desktop1Int->isActive()); + + KWayland::Server::PlasmaVirtualDesktopInterface *desktop2Int = m_plasmaVirtualDesktopManagementInterface->desktops()[1]; + KWayland::Client::PlasmaVirtualDesktop *desktop2 = m_plasmaVirtualDesktopManagement->desktops()[1]; + QVERIFY(!desktop2Int->isActive()); + + QSignalSpy requestActivateSpy(desktop2Int, &KWayland::Server::PlasmaVirtualDesktopInterface::activateRequested); + QSignalSpy activatedSpy(desktop2, &KWayland::Client::PlasmaVirtualDesktop::activated); + + desktop2->requestActivate(); + requestActivateSpy.wait(); + + //This simulates a compositor which supports only one active desktop at a time + for (auto *deskInt : m_plasmaVirtualDesktopManagementInterface->desktops()) { + if (deskInt->id() == desktop2->id()) { + deskInt->setActive(true); + } else { + deskInt->setActive(false); + } + } + activatedSpy.wait(); + + //correct state in the server + QVERIFY(desktop2Int->isActive()); + QVERIFY(!desktop1Int->isActive()); + //correct state in the client + QVERIFY(desktop2Int->isActive()); + QVERIFY(!desktop1Int->isActive()); + + //Test the deactivated signal + QSignalSpy deactivatedSpy(desktop2, &KWayland::Client::PlasmaVirtualDesktop::deactivated); + + for (auto *deskInt : m_plasmaVirtualDesktopManagementInterface->desktops()) { + if (deskInt->id() == desktop1->id()) { + deskInt->setActive(true); + } else { + deskInt->setActive(false); + } + } + deactivatedSpy.wait(); +} + +void TestVirtualDesktop::testEnterLeaveDesktop() +{ + testCreate(); + + QSignalSpy enterRequestedSpy(m_windowInterface, &KWayland::Server::PlasmaWindowInterface::enterPlasmaVirtualDesktopRequested); + m_window->requestEnterVirtualDesktop(QStringLiteral("0-1")); + enterRequestedSpy.wait(); + + QCOMPARE(enterRequestedSpy.takeFirst().at(0).toString(), QStringLiteral("0-1")); + + QSignalSpy virtualDesktopEnteredSpy(m_window, &KWayland::Client::PlasmaWindow::plasmaVirtualDesktopEntered); + + //agree to the request + m_windowInterface->addPlasmaVirtualDesktop(QStringLiteral("0-1")); + QCOMPARE(m_windowInterface->plasmaVirtualDesktops().length(), 1); + QCOMPARE(m_windowInterface->plasmaVirtualDesktops().first(), QStringLiteral("0-1")); + + //check if the client received the enter + virtualDesktopEnteredSpy.wait(); + QCOMPARE(virtualDesktopEnteredSpy.takeFirst().at(0).toString(), QStringLiteral("0-1")); + QCOMPARE(m_window->plasmaVirtualDesktops().length(), 1); + QCOMPARE(m_window->plasmaVirtualDesktops().first(), QStringLiteral("0-1")); + + //add another desktop, server side + m_windowInterface->addPlasmaVirtualDesktop(QStringLiteral("0-3")); + virtualDesktopEnteredSpy.wait(); + QCOMPARE(virtualDesktopEnteredSpy.takeFirst().at(0).toString(), QStringLiteral("0-3")); + QCOMPARE(m_windowInterface->plasmaVirtualDesktops().length(), 2); + QCOMPARE(m_window->plasmaVirtualDesktops().length(), 2); + QCOMPARE(m_window->plasmaVirtualDesktops()[1], QStringLiteral("0-3")); + + + + //try to add an invalid desktop + m_windowInterface->addPlasmaVirtualDesktop(QStringLiteral("invalid")); + QCOMPARE(m_window->plasmaVirtualDesktops().length(), 2); + + //remove a desktop + QSignalSpy leaveRequestedSpy(m_windowInterface, &KWayland::Server::PlasmaWindowInterface::leavePlasmaVirtualDesktopRequested); + m_window->requestLeaveVirtualDesktop(QStringLiteral("0-1")); + leaveRequestedSpy.wait(); + + QCOMPARE(leaveRequestedSpy.takeFirst().at(0).toString(), QStringLiteral("0-1")); + + QSignalSpy virtualDesktopLeftSpy(m_window, &KWayland::Client::PlasmaWindow::plasmaVirtualDesktopLeft); + + //agree to the request + m_windowInterface->removePlasmaVirtualDesktop(QStringLiteral("0-1")); + QCOMPARE(m_windowInterface->plasmaVirtualDesktops().length(), 1); + QCOMPARE(m_windowInterface->plasmaVirtualDesktops().first(), QStringLiteral("0-3")); + + //check if the client received the leave + virtualDesktopLeftSpy.wait(); + QCOMPARE(virtualDesktopLeftSpy.takeFirst().at(0).toString(), QStringLiteral("0-1")); + QCOMPARE(m_window->plasmaVirtualDesktops().length(), 1); + QCOMPARE(m_window->plasmaVirtualDesktops().first(), QStringLiteral("0-3")); + + //Destroy desktop 2 + m_plasmaVirtualDesktopManagementInterface->removeDesktop(QStringLiteral("0-3")); + //the window should receive a left signal from the destroyed desktop + virtualDesktopLeftSpy.wait(); + + QCOMPARE(m_window->plasmaVirtualDesktops().length(), 0); +} + +void TestVirtualDesktop::testAllDesktops() +{ + testCreate(); + QSignalSpy virtualDesktopEnteredSpy(m_window, &KWayland::Client::PlasmaWindow::plasmaVirtualDesktopEntered); + QSignalSpy virtualDesktopLeftSpy(m_window, &KWayland::Client::PlasmaWindow::plasmaVirtualDesktopLeft); + + //in the beginning the window is on desktop 1 and desktop 3 + m_windowInterface->addPlasmaVirtualDesktop(QStringLiteral("0-1")); + m_windowInterface->addPlasmaVirtualDesktop(QStringLiteral("0-3")); + virtualDesktopEnteredSpy.wait(); + + //setting on all desktops + QCOMPARE(m_window->plasmaVirtualDesktops().length(), 2); + m_windowInterface->setOnAllDesktops(true); + //setting on all desktops, the window will leave every desktop + + virtualDesktopLeftSpy.wait(); + QCOMPARE(virtualDesktopLeftSpy.count(), 2); + + QCOMPARE(m_window->plasmaVirtualDesktops().length(), 0); + QVERIFY(m_window->isOnAllDesktops()); + + //return to the active desktop (0-1) + m_windowInterface->setOnAllDesktops(false); + virtualDesktopEnteredSpy.wait(); + QCOMPARE(m_window->plasmaVirtualDesktops().length(), 1); + QCOMPARE(m_windowInterface->plasmaVirtualDesktops().first(), QStringLiteral("0-1")); + QVERIFY(!m_window->isOnAllDesktops()); + + //try setting on virtual desktops again but by setting every desktop by hand + m_windowInterface->addPlasmaVirtualDesktop(QStringLiteral("0-3")); + virtualDesktopEnteredSpy.wait(); + + virtualDesktopEnteredSpy.clear(); + virtualDesktopLeftSpy.clear(); + + m_windowInterface->addPlasmaVirtualDesktop(QStringLiteral("0-2")); + virtualDesktopLeftSpy.wait(); + QCOMPARE(virtualDesktopLeftSpy.count(), 2); + //note that virtualDesktopEntered should *not* have been emitted for 0-2 + QCOMPARE(virtualDesktopEnteredSpy.count(), 0); + + QCOMPARE(m_window->plasmaVirtualDesktops().length(), 0); + QVERIFY(m_window->isOnAllDesktops()); +} + +void TestVirtualDesktop::testCreateRequested() +{ + //rebuild some desktops + testCreate(); + + QSignalSpy desktopCreateRequestedSpy(m_plasmaVirtualDesktopManagementInterface, &KWayland::Server::PlasmaVirtualDesktopManagementInterface::desktopCreateRequested); + QSignalSpy desktopCreatedSpy(m_plasmaVirtualDesktopManagement, &PlasmaVirtualDesktopManagement::desktopCreated); + + //listen for createdRequested + m_plasmaVirtualDesktopManagement->requestCreateVirtualDesktop(QStringLiteral("Desktop"), 1); + desktopCreateRequestedSpy.wait(); + QCOMPARE(desktopCreateRequestedSpy.first().first().toString(), QStringLiteral("Desktop")); + QCOMPARE(desktopCreateRequestedSpy.first().at(1).toUInt(), 1); + + //actually create + m_plasmaVirtualDesktopManagementInterface->createDesktop(QStringLiteral("0-4"), 1); + KWayland::Server::PlasmaVirtualDesktopInterface *desktopInt = m_plasmaVirtualDesktopManagementInterface->desktops().at(1); + + QCOMPARE(desktopInt->id(), QStringLiteral("0-4")); + desktopInt->setName(QStringLiteral("Desktop")); + + desktopCreatedSpy.wait(); + + QCOMPARE(desktopCreatedSpy.first().first().toString(), QStringLiteral("0-4")); + QCOMPARE(m_plasmaVirtualDesktopManagement->desktops().count(), 4); + + PlasmaVirtualDesktop *desktop = m_plasmaVirtualDesktopManagement->desktops().at(1); + QSignalSpy desktopDoneSpy(desktop, &PlasmaVirtualDesktop::done); + desktopInt->sendDone(); + // desktopDoneSpy.wait(); + //check the order is correct + QCOMPARE(m_plasmaVirtualDesktopManagement->desktops().at(0)->id(), QStringLiteral("0-1")); + QCOMPARE(desktop->id(), QStringLiteral("0-4")); + QCOMPARE(m_plasmaVirtualDesktopManagement->desktops().at(2)->id(), QStringLiteral("0-2")); + QCOMPARE(m_plasmaVirtualDesktopManagement->desktops().at(3)->id(), QStringLiteral("0-3")); +} + +void TestVirtualDesktop::testRemoveRequested() +{ + //rebuild some desktops + testCreate(); + + QSignalSpy desktopRemoveRequestedSpy(m_plasmaVirtualDesktopManagementInterface, &KWayland::Server::PlasmaVirtualDesktopManagementInterface::desktopRemoveRequested); + + //request a remove, just check the request arrived, ignore the request. + m_plasmaVirtualDesktopManagement->requestRemoveVirtualDesktop(QStringLiteral("0-1")); + desktopRemoveRequestedSpy.wait(); + QCOMPARE(desktopRemoveRequestedSpy.first().first().toString(), QStringLiteral("0-1")); +} + +QTEST_GUILESS_MAIN(TestVirtualDesktop) +#include "test_plasma_virtual_desktop.moc" diff --git a/autotests/client/test_plasma_window_model.cpp b/autotests/client/test_plasma_window_model.cpp --- a/autotests/client/test_plasma_window_model.cpp +++ b/autotests/client/test_plasma_window_model.cpp @@ -70,6 +70,7 @@ void testIsOnAllDesktops(); void testIsDemandingAttention(); void testSkipTaskbar(); + void testSkipSwitcher(); void testIsShadeable(); void testIsShaded(); void testIsMovable(); @@ -235,6 +236,7 @@ QTest::newRow("IsOnAllDesktops") << int(PlasmaWindowModel::IsOnAllDesktops) << QByteArrayLiteral("IsOnAllDesktops"); QTest::newRow("IsDemandingAttention") << int(PlasmaWindowModel::IsDemandingAttention) << QByteArrayLiteral("IsDemandingAttention"); QTest::newRow("SkipTaskbar") << int(PlasmaWindowModel::SkipTaskbar) << QByteArrayLiteral("SkipTaskbar"); + QTest::newRow("SkipSwitcher") << int(PlasmaWindowModel::SkipSwitcher) << QByteArrayLiteral("SkipSwitcher"); QTest::newRow("IsShadeable") << int(PlasmaWindowModel::IsShadeable) << QByteArrayLiteral("IsShadeable"); QTest::newRow("IsShaded") << int(PlasmaWindowModel::IsShaded) << QByteArrayLiteral("IsShaded"); QTest::newRow("IsMovable") << int(PlasmaWindowModel::IsMovable) << QByteArrayLiteral("IsMovable"); @@ -414,6 +416,11 @@ QVERIFY(testBooleanData(PlasmaWindowModel::SkipTaskbar, &PlasmaWindowInterface::setSkipTaskbar)); } +void PlasmaWindowModelTest::testSkipSwitcher() +{ + QVERIFY(testBooleanData(PlasmaWindowModel::SkipSwitcher, &PlasmaWindowInterface::setSkipSwitcher)); +} + void PlasmaWindowModelTest::testIsShadeable() { QVERIFY(testBooleanData(PlasmaWindowModel::IsShadeable, &PlasmaWindowInterface::setShadeable)); diff --git a/autotests/client/test_plasmashell.cpp b/autotests/client/test_plasmashell.cpp --- a/autotests/client/test_plasmashell.cpp +++ b/autotests/client/test_plasmashell.cpp @@ -47,6 +47,7 @@ void testRole(); void testPosition(); void testSkipTaskbar(); + void testSkipSwitcher(); void testPanelBehavior_data(); void testPanelBehavior(); void testAutoHidePanel(); @@ -297,6 +298,41 @@ QVERIFY(!sps->skipTaskbar()); } +void TestPlasmaShell::testSkipSwitcher() +{ + // this test verifies that Skip Switcher is properly passed to server + QSignalSpy plasmaSurfaceCreatedSpy(m_plasmaShellInterface, &PlasmaShellInterface::surfaceCreated); + QVERIFY(plasmaSurfaceCreatedSpy.isValid()); + + QScopedPointer s(m_compositor->createSurface()); + QScopedPointer ps(m_plasmaShell->createSurface(s.data())); + QVERIFY(plasmaSurfaceCreatedSpy.wait()); + QCOMPARE(plasmaSurfaceCreatedSpy.count(), 1); + + // verify that we got a plasma shell surface + auto sps = plasmaSurfaceCreatedSpy.first().first().value(); + QVERIFY(sps); + QVERIFY(sps->surface()); + QVERIFY(!sps->skipSwitcher()); + + // now change + QSignalSpy skipSwitcherChangedSpy(sps, &PlasmaShellSurfaceInterface::skipSwitcherChanged); + QVERIFY(skipSwitcherChangedSpy.isValid()); + ps->setSkipSwitcher(true); + QVERIFY(skipSwitcherChangedSpy.wait()); + QVERIFY(sps->skipSwitcher()); + // setting to same again should not emit the signal + ps->setSkipSwitcher(true); + QEXPECT_FAIL("", "Should not be emitted if not changed", Continue); + QVERIFY(!skipSwitcherChangedSpy.wait(100)); + QVERIFY(sps->skipSwitcher()); + + // setting to false should change again + ps->setSkipSwitcher(false); + QVERIFY(skipSwitcherChangedSpy.wait()); + QVERIFY(!sps->skipSwitcher()); +} + void TestPlasmaShell::testPanelBehavior_data() { QTest::addColumn("client"); diff --git a/autotests/client/test_wayland_registry.cpp b/autotests/client/test_wayland_registry.cpp --- a/autotests/client/test_wayland_registry.cpp +++ b/autotests/client/test_wayland_registry.cpp @@ -598,7 +598,7 @@ //server removes the global //(simultaneously) the client legimitely uses the bound resource to the global - //client then gets the server events...it should no-op, not be a protcol error + //client then gets the server events...it should no-op, not be a protocol error using namespace KWayland::Client; KWayland::Client::ConnectionThread connection; diff --git a/autotests/client/test_wayland_surface.cpp b/autotests/client/test_wayland_surface.cpp --- a/autotests/client/test_wayland_surface.cpp +++ b/autotests/client/test_wayland_surface.cpp @@ -71,12 +71,12 @@ private: KWayland::Server::Display *m_display; KWayland::Server::CompositorInterface *m_compositorInterface; - KWayland::Server::IdleInhibitManagerInterface *m_idleInhibitInterface = nullptr; + KWayland::Server::IdleInhibitManagerInterface *m_idleInhibitInterface; KWayland::Client::ConnectionThread *m_connection; KWayland::Client::Compositor *m_compositor; KWayland::Client::ShmPool *m_shm; KWayland::Client::EventQueue *m_queue; - KWayland::Client::IdleInhibitManager *m_idleInhibitManager = nullptr; + KWayland::Client::IdleInhibitManager *m_idleInhibitManager; QThread *m_thread; }; @@ -190,6 +190,9 @@ delete m_compositorInterface; m_compositorInterface = nullptr; + delete m_idleInhibitInterface; + m_idleInhibitInterface = nullptr; + delete m_display; m_display = nullptr; } @@ -779,13 +782,19 @@ connect(m_connection, &ConnectionThread::connectionDied, m_compositor, &Compositor::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_shm, &ShmPool::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_queue, &EventQueue::destroy); + connect(m_connection, &ConnectionThread::connectionDied, m_idleInhibitManager, &IdleInhibitManager::destroy); QVERIFY(s->isValid()); QSignalSpy connectionDiedSpy(m_connection, SIGNAL(connectionDied())); QVERIFY(connectionDiedSpy.isValid()); + + delete m_compositorInterface; + m_compositorInterface = nullptr; + delete m_idleInhibitInterface; + m_idleInhibitInterface = nullptr; delete m_display; m_display = nullptr; - m_compositorInterface = nullptr; + QVERIFY(connectionDiedSpy.wait()); // now the Surface should be destroyed; diff --git a/autotests/client/test_wayland_windowmanagement.cpp b/autotests/client/test_wayland_windowmanagement.cpp --- a/autotests/client/test_wayland_windowmanagement.cpp +++ b/autotests/client/test_wayland_windowmanagement.cpp @@ -407,6 +407,7 @@ QTest::newRow("maximizable") << &PlasmaWindowInterface::maximizeableRequested << int(ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_MAXIMIZABLE); QTest::newRow("fullscreenable") << &PlasmaWindowInterface::fullscreenableRequested << int(ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_FULLSCREENABLE); QTest::newRow("skiptaskbar") << &PlasmaWindowInterface::skipTaskbarRequested << int(ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_SKIPTASKBAR); + QTest::newRow("skipSwitcher") << &PlasmaWindowInterface::skipSwitcherRequested << int(ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_SKIPSWITCHER); QTest::newRow("shadeable") << &PlasmaWindowInterface::shadeableRequested << int(ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_SHADEABLE); QTest::newRow("shaded") << &PlasmaWindowInterface::shadedRequested << int(ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_SHADED); QTest::newRow("movable") << &PlasmaWindowInterface::movableRequested << int(ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_MOVABLE); diff --git a/autotests/client/test_xdg_output.cpp b/autotests/client/test_xdg_output.cpp new file mode 100644 --- /dev/null +++ b/autotests/client/test_xdg_output.cpp @@ -0,0 +1,174 @@ +/******************************************************************** +Copyright 2018 David Edmundson + +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 . +*********************************************************************/ +// Qt +#include +// KWin +#include "../../src/client/connection_thread.h" +#include "../../src/client/event_queue.h" +#include "../../src/client/dpms.h" +#include "../../src/client/output.h" +#include "../../src/client/xdgoutput.h" +#include "../../src/client/registry.h" +#include "../../src/server/display.h" +#include "../../src/server/dpms_interface.h" +#include "../../src/server/output_interface.h" +#include "../../src/server/xdgoutput_interface.h" + +// Wayland + +class TestXdgOutput : public QObject +{ + Q_OBJECT +public: + explicit TestXdgOutput(QObject *parent = nullptr); +private Q_SLOTS: + void init(); + void cleanup(); + void testChanges(); +private: + KWayland::Server::Display *m_display; + KWayland::Server::OutputInterface *m_serverOutput; + KWayland::Server::XdgOutputManagerInterface *m_serverXdgOutputManager; + KWayland::Server::XdgOutputInterface *m_serverXdgOutput; + KWayland::Client::ConnectionThread *m_connection; + KWayland::Client::EventQueue *m_queue; + QThread *m_thread; +}; + +static const QString s_socketName = QStringLiteral("kwin-test-xdg-output-0"); + +TestXdgOutput::TestXdgOutput(QObject *parent) + : QObject(parent) + , m_display(nullptr) + , m_serverOutput(nullptr) + , m_connection(nullptr) + , m_thread(nullptr) +{ +} + +void TestXdgOutput::init() +{ + using namespace KWayland::Server; + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + + m_serverOutput = m_display->createOutput(this); + m_serverOutput->addMode(QSize(1920, 1080), OutputInterface::ModeFlags(OutputInterface::ModeFlag::Preferred)); + m_serverOutput->setCurrentMode(QSize(1920, 1080)); + m_serverOutput->create(); + + m_serverXdgOutputManager = m_display->createXdgOutputManager(this); + m_serverXdgOutputManager->create(); + m_serverXdgOutput = m_serverXdgOutputManager->createXdgOutput(m_serverOutput, this); + m_serverXdgOutput->setLogicalSize(QSize(1280, 720)); //a 1.5 scale factor + m_serverXdgOutput->setLogicalPosition(QPoint(11,12)); //not a sensible value for one monitor, but works for this test + m_serverXdgOutput->done(); + + // setup connection + m_connection = new KWayland::Client::ConnectionThread; + QSignalSpy connectedSpy(m_connection, SIGNAL(connected())); + m_connection->setSocketName(s_socketName); + + m_thread = new QThread(this); + m_connection->moveToThread(m_thread); + m_thread->start(); + + m_connection->initConnection(); + QVERIFY(connectedSpy.wait()); + + m_queue = new KWayland::Client::EventQueue(this); + QVERIFY(!m_queue->isValid()); + m_queue->setup(m_connection); + QVERIFY(m_queue->isValid()); +} + +void TestXdgOutput::cleanup() +{ + if (m_queue) { + delete m_queue; + m_queue = nullptr; + } + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + delete m_connection; + m_connection = nullptr; + + delete m_serverOutput; + m_serverOutput = nullptr; + + delete m_display; + m_display = nullptr; +} + +void TestXdgOutput::testChanges() +{ + // verify the server modes + using namespace KWayland::Server; + using namespace KWayland::Client; + KWayland::Client::Registry registry; + QSignalSpy announced(®istry, SIGNAL(outputAnnounced(quint32,quint32))); + QSignalSpy xdgOutputAnnounced(®istry, SIGNAL(xdgOutputAnnounced(quint32,quint32))); + + registry.setEventQueue(m_queue); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + QVERIFY(announced.wait()); + if (xdgOutputAnnounced.count() != 1) { + QVERIFY(xdgOutputAnnounced.wait()); + } + + KWayland::Client::Output output; + QSignalSpy outputChanged(&output, SIGNAL(changed())); + + output.setup(registry.bindOutput(announced.first().first().value(), announced.first().last().value())); + QVERIFY(outputChanged.wait()); + + QScopedPointer xdgOutputManager(registry.createXdgOutputManager(xdgOutputAnnounced.first().first().value(), xdgOutputAnnounced.first().last().value(), this)); + + QScopedPointer xdgOutput(xdgOutputManager->getXdgOutput(&output, this)); + QSignalSpy xdgOutputChanged(xdgOutput.data(), SIGNAL(changed())); + + //check details are sent on client bind + QVERIFY(xdgOutputChanged.wait()); + xdgOutputChanged.clear(); + QCOMPARE(xdgOutput->logicalPosition(), QPoint(11,12)); + QCOMPARE(xdgOutput->logicalSize(), QSize(1280,720)); + + //dynamic updates + m_serverXdgOutput->setLogicalPosition(QPoint(1000, 2000)); + m_serverXdgOutput->setLogicalSize(QSize(100,200)); + m_serverXdgOutput->done(); + + QVERIFY(xdgOutputChanged.wait()); + QCOMPARE(xdgOutputChanged.count(), 1); + QCOMPARE(xdgOutput->logicalPosition(), QPoint(1000, 2000)); + QCOMPARE(xdgOutput->logicalSize(), QSize(100,200)); +} + +QTEST_GUILESS_MAIN(TestXdgOutput) +#include "test_xdg_output.moc" diff --git a/autotests/client/test_xdg_shell_v6.cpp b/autotests/client/test_xdg_shell_v6.cpp --- a/autotests/client/test_xdg_shell_v6.cpp +++ b/autotests/client/test_xdg_shell_v6.cpp @@ -68,7 +68,7 @@ void XdgShellTestV6::testPopup_data() { - QTest::addColumn("positioners"); + QTest::addColumn("positioner"); XdgPositioner positioner(QSize(10,10), QRect(100,100,50,50)); QTest::newRow("default") << positioner; @@ -105,7 +105,7 @@ QVERIFY(xdgTopLevelCreatedSpy.wait()); auto serverXdgTopLevel = xdgTopLevelCreatedSpy.first().first().value(); - XdgPositioner positioner(QSize(10,10), QRect(100,100,50,50)); + QFETCH(XdgPositioner, positioner); QScopedPointer surface(m_compositor->createSurface()); QScopedPointer xdgSurface(m_xdgShell->createPopup(surface.data(), xdgParentSurface.data(), positioner)); diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -34,6 +34,7 @@ pointerconstraints.cpp pointergestures.cpp plasmashell.cpp + plasmavirtualdesktop.cpp plasmawindowmanagement.cpp plasmawindowmodel.cpp region.cpp @@ -81,6 +82,12 @@ PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/plasma-shell.xml BASENAME plasma-shell ) + +ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/plasma-virtual-desktop.xml + BASENAME plasma-virtual-desktop +) + ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/plasma-window-management.xml BASENAME plasma-window-management @@ -181,6 +188,7 @@ ${CMAKE_CURRENT_BINARY_DIR}/wayland-output-management-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-org_kde_kwin_outputdevice-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-plasma-shell-client-protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/wayland-plasma-shell-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-plasma-window-management-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-idle-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-fake-input-client-protocol.h @@ -260,6 +268,7 @@ pointer.h pointerconstraints.h plasmashell.h + plasmavirtualdesktop.h plasmawindowmanagement.h plasmawindowmodel.h pointergestures.h diff --git a/src/client/plasmashell.h b/src/client/plasmashell.h --- a/src/client/plasmashell.h +++ b/src/client/plasmashell.h @@ -288,6 +288,13 @@ void setSkipTaskbar(bool skip); /** + * Setting this bit on a window will indicate it does not prefer + * to be included in a window switcher. + * @since 5.47 + */ + void setSkipSwitcher(bool skip); + + /** * Requests to hide a surface with Role Panel and PanelBahvior AutoHide. * * Once the compositor has hidden the panel the signal {@link autoHidePanelHidden} gets diff --git a/src/client/plasmashell.cpp b/src/client/plasmashell.cpp --- a/src/client/plasmashell.cpp +++ b/src/client/plasmashell.cpp @@ -322,6 +322,11 @@ org_kde_plasma_surface_set_skip_taskbar(d->surface, skip); } +void PlasmaShellSurface::setSkipSwitcher(bool skip) +{ + org_kde_plasma_surface_set_skip_switcher(d->surface, skip); +} + void PlasmaShellSurface::requestHideAutoHidingPanel() { org_kde_plasma_surface_panel_auto_hide_hide(d->surface); diff --git a/src/client/plasmavirtualdesktop.h b/src/client/plasmavirtualdesktop.h new file mode 100644 --- /dev/null +++ b/src/client/plasmavirtualdesktop.h @@ -0,0 +1,286 @@ +/**************************************************************************** +Copyright 2018 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 . +****************************************************************************/ +#ifndef KWAYLAND_CLIENT_PLASMAVIRTUALDESKTOP_H +#define KWAYLAND_CLIENT_PLASMAVIRTUALDESKTOP_H + +#include + +#include + +struct org_kde_plasma_virtual_desktop_management; +struct org_kde_plasma_virtual_desktop; + +namespace KWayland +{ +namespace Client +{ + +class EventQueue; +class PlasmaVirtualDesktop; + +/** + * @short Wrapper for the org_kde_plasma_virtual_desktop_management interface. + * + * This class provides a convenient wrapper for the org_kde_plasma_virtual_desktop_management interface. + * + * To use this class one needs to interact with the Registry. There are two + * possible ways to create the PlasmaVirtualDesktopManagement interface: + * @code + * PlasmaVirtualDesktopManagement *c = registry->createPlasmaVirtualDesktopManagement(name, version); + * @endcode + * + * This creates the PlasmaVirtualDesktopManagement and sets it up directly. As an alternative this + * can also be done in a more low level way: + * @code + * PlasmaVirtualDesktopManagement *c = new PlasmaVirtualDesktopManagement; + * c->setup(registry->bindPlasmaVirtualDesktopManagement(name, version)); + * @endcode + * + * The PlasmaVirtualDesktopManagement can be used as a drop-in replacement for any org_kde_plasma_virtual_desktop_management + * pointer as it provides matching cast operators. + * @since 5.46 + * + * @see Registry + **/ +class KWAYLANDCLIENT_EXPORT PlasmaVirtualDesktopManagement : public QObject +{ + Q_OBJECT +public: + /** + * Creates a new PlasmaVirtualDesktopManagement. + * Note: after constructing the PlasmaVirtualDesktopManagement it is not yet valid and one needs + * to call setup. In order to get a ready to use PlasmaVirtualDesktopManagement prefer using + * Registry::createPlasmaVirtualDesktopManagement. + **/ + explicit PlasmaVirtualDesktopManagement(QObject *parent = nullptr); + virtual ~PlasmaVirtualDesktopManagement(); + + /** + * Setup this PlasmaVirtualDesktopManagement to manage the @p plasmavirtualdesktopmanagement. + * When using Registry::createPlasmaVirtualDesktopManagement there is no need to call this + * method. + **/ + void setup(org_kde_plasma_virtual_desktop_management *plasmavirtualdesktopmanagement); + /** + * @returns @c true if managing a org_kde_plasma_virtual_desktop_management. + **/ + bool isValid() const; + /** + * Releases the org_kde_plasma_virtual_desktop_management interface. + * After the interface has been released the PlasmaVirtualDesktopManagement instance is no + * longer valid and can be setup with another org_kde_plasma_virtual_desktop_management interface. + **/ + void release(); + /** + * Destroys the data held by this PlasmaVirtualDesktopManagement. + * This method is supposed to be used when the connection to the Wayland + * server goes away. If the connection is not valid anymore, it's not + * possible to call release anymore as that calls into the Wayland + * connection and the call would fail. This method cleans up the data, so + * that the instance can be deleted or set up to a new org_kde_plasma_virtual_desktop_management interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, plasmavirtualdesktopmanagement, &PlasmaVirtualDesktopManagement::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + /** + * Sets the @p queue to use for creating objects with this PlasmaVirtualDesktopManagement. + **/ + void setEventQueue(EventQueue *queue); + + /** + * @returns The event queue to use for creating objects with this PlasmaVirtualDesktopManagement. + * The object is owned by the manager and the caller should not delete it. + **/ + EventQueue *eventQueue(); + + /** + * @returns the PlasmaVirtualDesktop representing the desktop id. + * The PlasmaVirtualDesktop instance is guaranteed to be unique for each id. + */ + PlasmaVirtualDesktop *getVirtualDesktop(const QString &id); + + /** + * Requests for the desktop identified by id to be removed. + * The server may or may not acconsent to the request. + */ + void requestRemoveVirtualDesktop(const QString &id); + + /** + * Ask the server to create a new virtual desktop, and position it at a specified position. + * If the position is zero or less, it will be positioned at the beginning, + * if the cosition is the count or more, it will be positioned at the end. + * @param name The name we want for the desktop + * @param position The position for the desktop to be created + */ + void requestCreateVirtualDesktop(const QString &name, quint32 position = std::numeric_limits::max()); + + /** + * @returns All the existent virtual desktops + */ + QList desktops() const; + + operator org_kde_plasma_virtual_desktop_management*(); + operator org_kde_plasma_virtual_desktop_management*() const; + +Q_SIGNALS: + void removed(); + + /** + * Emitted when a new desktop has been added + */ + void desktopCreated(const QString &id, quint32 position); + + /** + * Emitted when a desktop has been removed + */ + void desktopRemoved(const QString &id); + + /** + * This event is sent after all other properties has been + * sent after binding to the desktop manager object and after any + * other property changes done after that. This allows + * changes to the org_kde_plasma_virtual_desktop_management properties + * to be seen as atomic, even if they happen via multiple events. + */ + void done(); + +private: + class Private; + QScopedPointer d; +}; + +class KWAYLANDCLIENT_EXPORT PlasmaVirtualDesktop : public QObject +{ + Q_OBJECT +public: + virtual ~PlasmaVirtualDesktop(); + + /** + * Setup this PlasmaVirtualDesktop to manage the @p plasmavirtualdesktop. + * When using PlasmaVirtualDesktopManagement::createPlasmaVirtualDesktop there is no need to call this + * method. + **/ + void setup(org_kde_plasma_virtual_desktop *plasmavirtualdesktop); + + /** + * @returns @c true if managing a org_kde_plasma_virtual_desktop. + **/ + bool isValid() const; + + /** + * Releases the org_kde_plasma_virtual_desktop interface. + * After the interface has been released the PlasmaVirtualDesktop instance is no + * longer valid and can be setup with another org_kde_plasma_virtual_desktop interface. + **/ + void release(); + + /** + * Destroys the data held by this PlasmaVirtualDesktop. + * This method is supposed to be used when the connection to the Wayland + * server goes away. If the connection is not valid anymore, it's not + * possible to call release anymore as that calls into the Wayland + * connection and the call would fail. This method cleans up the data, so + * that the instance can be deleted or set up to a new org_kde_plasma_virtual_desktop interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, plasmavirtualdesktop, &PlasmaVirtualDesktop::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + /** + * Requests this desktop to be activated. + * The server may or may not decide to consent to the request. + */ + void requestActivate(); + + /** + * @returns The unique id of this desktop. The format of the id is decided by the compositor + */ + QString id() const; + + + /** + * @returns User readable name for the desktop. + */ + QString name() const; + + /** + * @returns True if the desktop is the active one. + * when this property changes, activated or deactivated will be emitted. + * @see activated + * @see deactivated + */ + bool isActive() const; + + operator org_kde_plasma_virtual_desktop*(); + operator org_kde_plasma_virtual_desktop*() const; + +Q_SIGNALS: + /** + * TODO: activeChanged(bool)? + * Emitted when this desktop has been activated by the server + */ + void activated(); + + /** + * Emitted when this desktop has been activated by the server + */ + void deactivated(); + + /** + * This event is sent after all other properties has been + * sent after binding to the desktop manager object and after any + * other property changes done after that. This allows + * changes to the org_kde_plasma_virtual_desktop properties + * to be seen as atomic, even if they happen via multiple events. + */ + void done(); + + /** + * This virtual desktop has just been removed by the server: + * This object itself is about to be deleted. All windows will + * lose the association to this desktop. + */ + void removed(); + +private: + friend class PlasmaVirtualDesktopManagement; + explicit PlasmaVirtualDesktop(QObject *parent = nullptr); + class Private; + QScopedPointer d; +}; + + +} +} + +#endif diff --git a/src/client/plasmavirtualdesktop.cpp b/src/client/plasmavirtualdesktop.cpp new file mode 100644 --- /dev/null +++ b/src/client/plasmavirtualdesktop.cpp @@ -0,0 +1,384 @@ +/**************************************************************************** +Copyright 2018 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 "plasmavirtualdesktop.h" +#include "event_queue.h" +#include "wayland_pointer_p.h" + +#include +#include + +#include + +namespace KWayland +{ +namespace Client +{ + +class Q_DECL_HIDDEN PlasmaVirtualDesktopManagement::Private +{ +public: + Private(PlasmaVirtualDesktopManagement *q); + + void setup(org_kde_plasma_virtual_desktop_management *arg); + + WaylandPointer plasmavirtualdesktopmanagement; + EventQueue *queue = nullptr; + + QList desktops; + + inline QList::const_iterator constFindDesktop(const QString &id); + inline QList::iterator findDesktop(const QString &id); + +private: + static void createdCallback(void *data, org_kde_plasma_virtual_desktop_management *org_kde_plasma_virtual_desktop_management, const char *id, uint32_t position); + static void removedCallback(void *data, org_kde_plasma_virtual_desktop_management *org_kde_plasma_virtual_desktop_management, const char *id); + static void doneCallback(void *data, org_kde_plasma_virtual_desktop_management *org_kde_plasma_virtual_desktop_management); + + PlasmaVirtualDesktopManagement *q; + + static const org_kde_plasma_virtual_desktop_management_listener s_listener; +}; + +class Q_DECL_HIDDEN PlasmaVirtualDesktop::Private +{ +public: + Private(PlasmaVirtualDesktop *q); + + void setup(org_kde_plasma_virtual_desktop *arg); + + WaylandPointer plasmavirtualdesktop; + + QString id; + QString name; + bool active = false; + +private: + PlasmaVirtualDesktop *q; + +private: + static void idCallback(void *data, org_kde_plasma_virtual_desktop *org_kde_plasma_virtual_desktop, const char * id); + static void nameCallback(void *data, org_kde_plasma_virtual_desktop *org_kde_plasma_virtual_desktop, const char * name); + + static void activatedCallback(void *data, org_kde_plasma_virtual_desktop *org_kde_plasma_virtual_desktop); + static void deactivatedCallback(void *data, org_kde_plasma_virtual_desktop *org_kde_plasma_virtual_desktop); + static void doneCallback(void *data, org_kde_plasma_virtual_desktop *org_kde_plasma_virtual_desktop); + static void removedCallback(void *data, org_kde_plasma_virtual_desktop *org_kde_plasma_virtual_desktop); + + static const org_kde_plasma_virtual_desktop_listener s_listener; +}; + + + +inline QList::const_iterator PlasmaVirtualDesktopManagement::Private::constFindDesktop(const QString &id) +{ + return std::find_if( desktops.constBegin(), + desktops.constEnd(), + [id]( const PlasmaVirtualDesktop *desk ){ return desk->id() == id; } ); +} + +inline QList::iterator PlasmaVirtualDesktopManagement::Private::findDesktop(const QString &id) +{ + return std::find_if( desktops.begin(), + desktops.end(), + [id]( const PlasmaVirtualDesktop *desk ){ return desk->id() == id; } ); +} + +const org_kde_plasma_virtual_desktop_management_listener PlasmaVirtualDesktopManagement::Private::s_listener = { + createdCallback, + removedCallback, + doneCallback +}; + +void PlasmaVirtualDesktopManagement::Private::createdCallback(void *data, org_kde_plasma_virtual_desktop_management *org_kde_plasma_virtual_desktop_management, const char *id, uint32_t position) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->plasmavirtualdesktopmanagement == org_kde_plasma_virtual_desktop_management); + const QString stringId = QString::fromUtf8(id); + PlasmaVirtualDesktop *vd = p->q->getVirtualDesktop(stringId); + Q_ASSERT(vd); + + p->desktops.insert(position, vd); + //TODO: emit a lot of desktopMoved? + + emit p->q->desktopCreated(stringId, position); +} + +void PlasmaVirtualDesktopManagement::Private::removedCallback(void *data, org_kde_plasma_virtual_desktop_management *org_kde_plasma_virtual_desktop_management, const char *id) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->plasmavirtualdesktopmanagement == org_kde_plasma_virtual_desktop_management); + const QString stringId = QString::fromUtf8(id); + PlasmaVirtualDesktop *vd = p->q->getVirtualDesktop(stringId); + //TODO: emit a lot of desktopMoved? + Q_ASSERT(vd); + auto i = p->findDesktop(stringId); + p->desktops.erase(i); + vd->release(); + vd->destroy(); + vd->deleteLater(); + emit p->q->desktopRemoved(stringId); +} + +void PlasmaVirtualDesktopManagement::Private::doneCallback(void *data, org_kde_plasma_virtual_desktop_management *org_kde_plasma_virtual_desktop_management) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->plasmavirtualdesktopmanagement == org_kde_plasma_virtual_desktop_management); + emit p->q->done(); +} + +PlasmaVirtualDesktopManagement::PlasmaVirtualDesktopManagement(QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ +} + +PlasmaVirtualDesktopManagement::Private::Private(PlasmaVirtualDesktopManagement *q) + : q(q) +{} + +void PlasmaVirtualDesktopManagement::Private::setup(org_kde_plasma_virtual_desktop_management *arg) +{ + Q_ASSERT(arg); + Q_ASSERT(!plasmavirtualdesktopmanagement); + plasmavirtualdesktopmanagement.setup(arg); + org_kde_plasma_virtual_desktop_management_add_listener(plasmavirtualdesktopmanagement, &s_listener, this); +} + +PlasmaVirtualDesktopManagement::~PlasmaVirtualDesktopManagement() +{ + release(); +} + +void PlasmaVirtualDesktopManagement::setup(org_kde_plasma_virtual_desktop_management *plasmavirtualdesktopmanagement) +{ + d->setup(plasmavirtualdesktopmanagement); +} + +void PlasmaVirtualDesktopManagement::release() +{ + d->plasmavirtualdesktopmanagement.release(); +} + +void PlasmaVirtualDesktopManagement::destroy() +{ + d->plasmavirtualdesktopmanagement.destroy(); +} + +PlasmaVirtualDesktopManagement::operator org_kde_plasma_virtual_desktop_management*() { + return d->plasmavirtualdesktopmanagement; +} + +PlasmaVirtualDesktopManagement::operator org_kde_plasma_virtual_desktop_management*() const { + return d->plasmavirtualdesktopmanagement; +} + +bool PlasmaVirtualDesktopManagement::isValid() const +{ + return d->plasmavirtualdesktopmanagement.isValid(); +} + +void PlasmaVirtualDesktopManagement::setEventQueue(EventQueue *queue) +{ + d->queue = queue; +} + +EventQueue *PlasmaVirtualDesktopManagement::eventQueue() +{ + return d->queue; +} + +PlasmaVirtualDesktop *PlasmaVirtualDesktopManagement::getVirtualDesktop(const QString &id) +{ + Q_ASSERT(isValid()); + + if (id.isEmpty()) { + return nullptr; + } + + auto i = d->constFindDesktop(id); + if (i != d->desktops.constEnd()) { + return *i; + } + + auto w = org_kde_plasma_virtual_desktop_management_get_virtual_desktop(d->plasmavirtualdesktopmanagement, id.toUtf8()); + + if (!w) { + return nullptr; + } + + if (d->queue) { + d->queue->addProxy(w); + } + + auto desktop = new PlasmaVirtualDesktop(this); + desktop->setup(w); + desktop->d->id = id; + + return desktop; +} + +void PlasmaVirtualDesktopManagement::requestRemoveVirtualDesktop(const QString &id) +{ + Q_ASSERT(isValid()); + + org_kde_plasma_virtual_desktop_management_request_remove_virtual_desktop(d->plasmavirtualdesktopmanagement, id.toUtf8()); +} + +void PlasmaVirtualDesktopManagement::requestCreateVirtualDesktop(const QString &name, quint32 position) +{ + Q_ASSERT(isValid()); + + org_kde_plasma_virtual_desktop_management_request_create_virtual_desktop(d->plasmavirtualdesktopmanagement, name.toUtf8(), position); +} + +QList PlasmaVirtualDesktopManagement::desktops() const +{ + return d->desktops; +} + +const org_kde_plasma_virtual_desktop_listener PlasmaVirtualDesktop::Private::s_listener = { + idCallback, + nameCallback, + activatedCallback, + deactivatedCallback, + doneCallback, + removedCallback +}; + +void PlasmaVirtualDesktop::Private::idCallback(void *data, org_kde_plasma_virtual_desktop *org_kde_plasma_virtual_desktop, const char * id) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->plasmavirtualdesktop == org_kde_plasma_virtual_desktop); + p->id = QString::fromUtf8(id); +} + +void PlasmaVirtualDesktop::Private::nameCallback(void *data, org_kde_plasma_virtual_desktop *org_kde_plasma_virtual_desktop, const char * name) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->plasmavirtualdesktop == org_kde_plasma_virtual_desktop); + p->name = QString::fromUtf8(name); +} + +void PlasmaVirtualDesktop::Private::activatedCallback(void *data, org_kde_plasma_virtual_desktop *org_kde_plasma_virtual_desktop) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->plasmavirtualdesktop == org_kde_plasma_virtual_desktop); + p->active = true; + emit p->q->activated(); +} + +void PlasmaVirtualDesktop::Private::deactivatedCallback(void *data, org_kde_plasma_virtual_desktop *org_kde_plasma_virtual_desktop) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->plasmavirtualdesktop == org_kde_plasma_virtual_desktop); + p->active = false; + emit p->q->deactivated(); +} + +void PlasmaVirtualDesktop::Private::doneCallback(void *data, org_kde_plasma_virtual_desktop *org_kde_plasma_virtual_desktop) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->plasmavirtualdesktop == org_kde_plasma_virtual_desktop); + emit p->q->done(); +} + +void PlasmaVirtualDesktop::Private::removedCallback(void *data, org_kde_plasma_virtual_desktop *org_kde_plasma_virtual_desktop) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->plasmavirtualdesktop == org_kde_plasma_virtual_desktop); + emit p->q->removed(); +} + +PlasmaVirtualDesktop::Private::Private(PlasmaVirtualDesktop *q) + : q(q) +{ +} + +PlasmaVirtualDesktop::PlasmaVirtualDesktop(QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ +} + +void PlasmaVirtualDesktop::Private::setup(org_kde_plasma_virtual_desktop *arg) +{ + Q_ASSERT(arg); + Q_ASSERT(!plasmavirtualdesktop); + plasmavirtualdesktop.setup(arg); + org_kde_plasma_virtual_desktop_add_listener(plasmavirtualdesktop, &s_listener, this); +} + +PlasmaVirtualDesktop::~PlasmaVirtualDesktop() +{ + release(); +} + +void PlasmaVirtualDesktop::setup(org_kde_plasma_virtual_desktop *plasmavirtualdesktop) +{ + d->setup(plasmavirtualdesktop); +} + +void PlasmaVirtualDesktop::release() +{ + d->plasmavirtualdesktop.release(); +} + +void PlasmaVirtualDesktop::destroy() +{ + d->plasmavirtualdesktop.destroy(); +} + +PlasmaVirtualDesktop::operator org_kde_plasma_virtual_desktop*() { + return d->plasmavirtualdesktop; +} + +PlasmaVirtualDesktop::operator org_kde_plasma_virtual_desktop*() const { + return d->plasmavirtualdesktop; +} + +bool PlasmaVirtualDesktop::isValid() const +{ + return d->plasmavirtualdesktop.isValid(); +} + +void PlasmaVirtualDesktop::requestActivate() +{ + Q_ASSERT(isValid()); + org_kde_plasma_virtual_desktop_request_activate(d->plasmavirtualdesktop); +} + +QString PlasmaVirtualDesktop::id() const +{ + return d->id; +} + +QString PlasmaVirtualDesktop::name() const +{ + return d->name; +} + +bool PlasmaVirtualDesktop::isActive() const +{ + return d->active; +} + +} +} + diff --git a/src/client/plasmawindowmanagement.h b/src/client/plasmawindowmanagement.h --- a/src/client/plasmawindowmanagement.h +++ b/src/client/plasmawindowmanagement.h @@ -37,6 +37,7 @@ class PlasmaWindow; class PlasmaWindowModel; class Surface; +class PlasmaVirtualDesktop; /** * @short Wrapper for the org_kde_plasma_window_management interface. @@ -272,6 +273,8 @@ **/ QString appId() const; /** + * @deprecated: use plasmaVirtualDesktops instead + * @see plasmaVirtualDesktops * @returns the id of the virtual desktop this PlasmaWindow is on * @see virtualDesktopChanged **/ @@ -409,6 +412,7 @@ */ void requestResize(); /** + * @deprecated: use requestEnterVirtualDesktop instead * Requests to send the window to virtual @p desktop. **/ void requestVirtualDesktop(quint32 desktop); @@ -480,6 +484,40 @@ **/ QRect geometry() const; + /** + * Ask the server to make the window enter a virtual desktop. + * The server may or may not consent. + * A window can enter more than one virtual desktop. + * + * @since 5.46 + */ + void requestEnterVirtualDesktop(const QString &id); + + /** + * RFC: do this with an empty id to request_enter_virtual_desktop? + * Make the window enter a new virtual desktop. If the server consents the request, + * it will create a new virtual desktop and assign the window to it. + */ + void requestEnterNewVirtualDesktop(); + + /** + * Ask the server to make the window the window exit a virtual desktop. + * The server may or may not consent. + * If it exits all desktops it will be considered on all of them. + * + * @since 5.46 + */ + void requestLeaveVirtualDesktop(const QString &id); + + /** + * Return all the virtual desktop ids this window is associated to. + * When a desktop gets deleted, it will be automatically removed from this list. + * If this list is empty, assume it's on all desktops. + * + * @since 5.46 + */ + QStringList plasmaVirtualDesktops() const; + Q_SIGNALS: /** * The window title changed. @@ -492,8 +530,8 @@ **/ void appIdChanged(); /** + * @deprecated use plasmaVirtualDesktopEntered and plasmaVirtualDesktopLeft instead * The virtual desktop changed. - * @see virtualDesktop **/ void virtualDesktopChanged(); /** @@ -620,6 +658,21 @@ **/ void geometryChanged(); + /** + * This signal is emitted when the window has entered a new virtual desktop. + * The window can be on more than one desktop, or none: then is considered on all of them. + * @since 5.46 + */ + void plasmaVirtualDesktopEntered(const QString &id); + + /** + * This signal is emitted when the window left a virtual desktop. + * If the window leaves all desktops, it can be considered on all. + * + * @since 5.46 + */ + void plasmaVirtualDesktopLeft(const QString &id); + private: friend class PlasmaWindowManagement; explicit PlasmaWindow(PlasmaWindowManagement *parent, org_kde_plasma_window *dataOffer, quint32 internalId); diff --git a/src/client/plasmawindowmanagement.cpp b/src/client/plasmawindowmanagement.cpp --- a/src/client/plasmawindowmanagement.cpp +++ b/src/client/plasmawindowmanagement.cpp @@ -19,6 +19,7 @@ *********************************************************************/ #include "plasmawindowmanagement.h" #include "plasmawindowmodel.h" +#include "plasmavirtualdesktop.h" #include "event_queue.h" #include "output.h" #include "surface.h" @@ -93,6 +94,7 @@ bool unmapped = false; QPointer parentWindow; QMetaObject::Connection parentWindowUnmappedConnection; + QStringList plasmaVirtualDesktops; QRect geometry; quint32 pid = 0; @@ -108,6 +110,8 @@ static void parentWindowCallback(void *data, org_kde_plasma_window *window, org_kde_plasma_window *parent); static void windowGeometryCallback(void *data, org_kde_plasma_window *window, int32_t x, int32_t y, uint32_t width, uint32_t height); static void iconChangedCallback(void *data, org_kde_plasma_window *org_kde_plasma_window); + static void virtualDesktopEnteredCallback(void *data, org_kde_plasma_window *org_kde_plasma_window, const char *id); + static void virtualDesktopLeftCallback(void *data, org_kde_plasma_window *org_kde_plasma_window, const char *id); void setActive(bool set); void setMinimized(bool set); void setMaximized(bool set); @@ -347,7 +351,9 @@ parentWindowCallback, windowGeometryCallback, iconChangedCallback, - pidChangedCallback + pidChangedCallback, + virtualDesktopEnteredCallback, + virtualDesktopLeftCallback }; void PlasmaWindow::Private::parentWindowCallback(void *data, org_kde_plasma_window *window, org_kde_plasma_window *parent) @@ -458,6 +464,24 @@ p->q->deleteLater(); } +void PlasmaWindow::Private::virtualDesktopEnteredCallback(void *data, org_kde_plasma_window *window, const char *id) +{ + auto p = cast(data); + Q_UNUSED(window); + const QString stringId(QString::fromUtf8(id)); + p->plasmaVirtualDesktops << stringId; + emit p->q->plasmaVirtualDesktopEntered(stringId); +} + +void PlasmaWindow::Private::virtualDesktopLeftCallback(void *data, org_kde_plasma_window *window, const char *id) +{ + auto p = cast(data); + Q_UNUSED(window); + const QString stringId(QString::fromUtf8(id)); + p->plasmaVirtualDesktops.removeAll(stringId); + emit p->q->plasmaVirtualDesktopLeft(stringId); +} + void PlasmaWindow::Private::stateChangedCallback(void *data, org_kde_plasma_window *window, uint32_t state) { auto p = cast(data); @@ -824,7 +848,13 @@ bool PlasmaWindow::isOnAllDesktops() const { - return d->onAllDesktops; + //from protocol version 8 virtual desktops are managed by plasmaVirtualDesktops + if (org_kde_plasma_window_get_version(d->window) < 8) { + return d->onAllDesktops; + } else { + return d->plasmaVirtualDesktops.isEmpty(); + } + } bool PlasmaWindow::isDemandingAttention() const @@ -1009,5 +1039,25 @@ return d->geometry; } +void PlasmaWindow::requestEnterVirtualDesktop(const QString &id) +{ + org_kde_plasma_window_request_enter_virtual_desktop(d->window, id.toUtf8()); +} + +void PlasmaWindow::requestEnterNewVirtualDesktop() +{ + org_kde_plasma_window_request_enter_new_virtual_desktop(d->window); +} + +void PlasmaWindow::requestLeaveVirtualDesktop(const QString &id) +{ + org_kde_plasma_window_request_leave_virtual_desktop(d->window, id.toUtf8()); +} + +QStringList PlasmaWindow::plasmaVirtualDesktops() const +{ + return d->plasmaVirtualDesktops; +} + } } diff --git a/src/client/plasmawindowmodel.h b/src/client/plasmawindowmodel.h --- a/src/client/plasmawindowmodel.h +++ b/src/client/plasmawindowmodel.h @@ -105,22 +105,26 @@ /** * @since 5.35 */ - Pid + Pid, + /** + * @since 5.47 + */ + SkipSwitcher, }; Q_ENUM(AdditionalRoles) explicit PlasmaWindowModel(PlasmaWindowManagement *parent); virtual ~PlasmaWindowModel(); - QHash roleNames() const Q_DECL_OVERRIDE; + QHash roleNames() const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; - int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; /** * Returns an index with internalPointer() pointing to a PlasmaWindow instance. **/ - QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override; /** diff --git a/src/client/plasmawindowmodel.cpp b/src/client/plasmawindowmodel.cpp --- a/src/client/plasmawindowmodel.cpp +++ b/src/client/plasmawindowmodel.cpp @@ -133,6 +133,10 @@ [window, this] { this->dataChanged(window, SkipTaskbar); } ); + QObject::connect(window, &PlasmaWindow::skipSwitcherChanged, q, + [window, this] { this->dataChanged(window, SkipSwitcher); } + ); + QObject::connect(window, &PlasmaWindow::shadeableChanged, q, [window, this] { this->dataChanged(window, IsShadeable); } ); @@ -253,6 +257,8 @@ return window->isDemandingAttention(); } else if (role == SkipTaskbar) { return window->skipTaskbar(); + } else if (role == SkipSwitcher) { + return window->skipSwitcher(); } else if (role == IsShadeable) { return window->isShadeable(); } else if (role == IsShaded) { diff --git a/src/client/protocols/fullscreen-shell.xml b/src/client/protocols/fullscreen-shell.xml --- a/src/client/protocols/fullscreen-shell.xml +++ b/src/client/protocols/fullscreen-shell.xml @@ -62,7 +62,7 @@ its own cursor and set wl_pointer.cursor(NULL). - + @@ -74,7 +74,7 @@ the wl_fullscreen_shell.capability enum. If clients want to take advantage of any of these capabilities, they sould use a wl_display.sync request immediatly after binding to ensure that they - recieve all the capability events. + receive all the capability events. diff --git a/src/client/protocols/outputdevice.xml b/src/client/protocols/outputdevice.xml --- a/src/client/protocols/outputdevice.xml +++ b/src/client/protocols/outputdevice.xml @@ -223,7 +223,7 @@ The uuid can be used to identify the output. It's controlled by the server entirely. The server should make sure the uuid is - persistant across restarts. An empty uuid is considered invalid. + persistent across restarts. An empty uuid is considered invalid. diff --git a/src/client/protocols/plasma-shell.xml b/src/client/protocols/plasma-shell.xml --- a/src/client/protocols/plasma-shell.xml +++ b/src/client/protocols/plasma-shell.xml @@ -17,7 +17,7 @@ along with this program. If not, see . ]]> - + This interface is used by KF5 powered Wayland shells to communicate with the compositor and can only be bound one time. @@ -130,7 +130,7 @@ --> - + An interface that may be implemented by a wl_surface, for implementations that provide the shell user interface. @@ -332,7 +332,7 @@ Setting this bit to the window, will make it say it prefers to not be listed in the taskbar. Taskbar implementations may or may not follow this hint. - + @@ -384,5 +384,14 @@ An auto-hiding panel got shown by the compositor. + + + + + Setting this bit will indicate that the window prefers not to be listed in a switcher. + + + + diff --git a/src/client/protocols/plasma-virtual-desktop.xml b/src/client/protocols/plasma-virtual-desktop.xml new file mode 100644 --- /dev/null +++ b/src/client/protocols/plasma-virtual-desktop.xml @@ -0,0 +1,117 @@ + + + . + ]]> + + + + + Given the id of a particular virtual desktop, get the corresponding org_kde_plasma_virtual_desktop which represents only the desktop with that id; + + + + + + + + Ask the server to create a new virtual desktop, and position it at a specified position. If the position is zero or less, it will be positioned at the beginning, if the cosition is the count or more, it will be positioned at the end. + + + + + + + + Ask the server to get rid of a virtual desktop, the server may or may not acconsent to the request. + + + + + + + + + + + + + + + + + + + This event is sent after all other properties has been + sent after binding to the desktop manager object and after any + other property changes done after that. This allows + changes to the org_kde_plasma_virtual_desktop_management properties to be seen as + atomic, even if they happen via multiple events. + + + + + + + + + Request the server to set the status of this desktop to active: The server is free to consent or deny the request. This will be the new "current" virtual desktop of the system. + + + + + + The format of the id is decided by the compositor implementation. A desktop id univocally identifies a virtual desktop and must be guaranteed to never exist two desktops with the same id. The format of the string id is up to the server implementation. + + + + + + + + + + + The desktop will be the new "current" desktop of the system. The server may support either one virtual desktop active at a time, or other combinations such as one virtual desktop active per screen. + Windows associated to this virtual desktop will be shown. + + + + + + Windows that were associated only to this desktop will be hidden. + + + + + + This event is sent after all other properties has been + sent after binding to the desktop object and after any + other property changes done after that. This allows + changes to the org_kde_plasma_virtual_desktop properties to be seen as + atomic, even if they happen via multiple events. + + + + + + This virtual desktop has just been removed by the server: + All windows will lose the association to this desktop. + + + + + diff --git a/src/client/protocols/plasma-window-management.xml b/src/client/protocols/plasma-window-management.xml --- a/src/client/protocols/plasma-window-management.xml +++ b/src/client/protocols/plasma-window-management.xml @@ -84,7 +84,7 @@ - + Manages and control an application window. @@ -105,6 +105,7 @@ + Deprecated: use enter_virtual_desktop Maps the window to a different virtual desktop. To show the window on all virtual desktops, call the @@ -199,6 +200,7 @@ + DEPRECATED: use virtual_desktop_entered and virtual_desktop_left instead This event will be sent when a window is moved to another virtual desktop. @@ -267,5 +269,45 @@ + + + + + + Make the window enter a virtual desktop. A window can enter more + than one virtual desktop. if the id is empty or invalid, no action will be performed. + + + + + + RFC: do this with an empty id to request_enter_virtual_desktop? + Make the window enter a new virtual desktop. If the server consents the request, + it will create a new virtual desktop and assign the window to it. + + + + + + Make the window exit a virtual desktop. If it exits all desktops it will be considered on all of them. + + + + + + + This event will be sent when the window has entered a new virtual desktop. The window can be on more than one desktop, or none: then is considered on all of them. + + + + + + + This event will be sent when the window left a virtual desktop. If the window leaves all desktops, it can be considered on all. + If the window gets manually added on all desktops, the server has to send virtual_desktop_left for every previous desktop it was in for the window to be really considered on all desktops. + + + + diff --git a/src/client/protocols/xdg-output-unstable-v1.xml b/src/client/protocols/xdg-output-unstable-v1.xml new file mode 100644 --- /dev/null +++ b/src/client/protocols/xdg-output-unstable-v1.xml @@ -0,0 +1,161 @@ + + + + + Copyright © 2017 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol aims at describing outputs in a way which is more in line + with the concept of an output on desktop oriented systems. + + Some information are more specific to the concept of an output for + a desktop oriented system and may not make sense in other applications, + such as IVI systems for example. + + Typically, the global compositor space on a desktop system is made of + a contiguous or overlapping set of rectangular regions. + + Some of the information provided in this protocol might be identical + to their counterparts already available from wl_output, in which case + the information provided by this protocol should be preferred to their + equivalent in wl_output. The goal is to move the desktop specific + concepts (such as output location within the global compositor space, + the connector name and types, etc.) out of the core wl_output protocol. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible + changes may be added together with the corresponding interface + version bump. + Backward incompatible changes are done by bumping the version + number in the protocol and interface names and resetting the + interface version. Once the protocol is to be declared stable, + the 'z' prefix and the version number in the protocol and + interface names are removed and the interface version number is + reset. + + + + + A global factory interface for xdg_output objects. + + + + + Using this request a client can tell the server that it is not + going to use the xdg_output_manager object anymore. + + Any objects already created through this instance are not affected. + + + + + + This creates a new xdg_output object for the given wl_output. + + + + + + + + + An xdg_output describes part of the compositor geometry. + + This typically corresponds to a monitor that displays part of the + compositor space. + + + + + Using this request a client can tell the server that it is not + going to use the xdg_output object anymore. + + + + + + The position event describes the location of the wl_output within + the global compositor space. + + The logical_position event is sent after creating an xdg_output + (see xdg_output_manager.get_xdg_output) and whenever the location + of the output changes within the global compositor space. + + + + + + + + The logical_size event describes the size of the output in the + global compositor space. + + For example, a surface without any buffer scale, transformation + nor rotation set, with the size matching the logical_size will + have the same size as the corresponding output when displayed. + + Most regular Wayland clients should not pay attention to the + logical size and would rather rely on xdg_shell interfaces. + + Some clients such as Xwayland, however, need this to configure + their surfaces in the global compositor space as the compositor + may apply a different scale from what is advertised by the output + scaling property (to achieve fractional scaling, for example). + + For example, for a wl_output mode 3840×2160 and a scale factor 2: + + - A compositor not scaling the surface buffers will advertise a + logical size of 3840×2160, + + - A compositor automatically scaling the surface buffers will + advertise a logical size of 1920×1080, + + - A compositor using a fractional scale of 1.5 will advertise a + logical size to 2560×1620. + + The logical_size event is sent after creating an xdg_output + (see xdg_output_manager.get_xdg_output) and whenever the logical + size of the output changes, either as a result of a change in the + applied scale or because of a change in the corresponding output + mode(see wl_output.mode) or transform (see wl_output.transform). + + + + + + + + This event is sent after all other properties of an xdg_output + have been sent. + + This allows changes to the xdg_output properties to be seen as + atomic, even if they happen via multiple events. + + + + + diff --git a/src/client/registry.h b/src/client/registry.h --- a/src/client/registry.h +++ b/src/client/registry.h @@ -49,6 +49,7 @@ struct org_kde_kwin_contrast_manager; struct org_kde_kwin_slide_manager; struct org_kde_plasma_shell; +struct org_kde_plasma_virtual_desktop_management; struct org_kde_plasma_window_management; struct org_kde_kwin_server_decoration_manager; struct org_kde_kwin_server_decoration_palette_manager; @@ -83,6 +84,7 @@ class RemoteAccessManager; class Output; class PlasmaShell; +class PlasmaVirtualDesktopManagement; class PlasmaWindowManagement; class PointerConstraints; class PointerGestures; @@ -176,6 +178,7 @@ AppMenu, ///Refers to org_kde_kwin_appmenu @since 5.42 ServerSideDecorationPalette, ///Refers to org_kde_kwin_server_decoration_palette_manager @since 5.42 RemoteAccessManager, ///< Refers to org_kde_kwin_remote_access_manager interface, @since 5.45 + PlasmaVirtualDesktopManagement, ///< Refers to org_kde_plasma_virtual_desktop_management interface @since 5.47 XdgOutputUnstableV1, ///refers to zxdg_output_v1, @since 5.47 XdgShellStable ///refers to xdg_wm_base @since 5.48 }; @@ -393,13 +396,23 @@ **/ org_kde_plasma_shell *bindPlasmaShell(uint32_t name, uint32_t version) const; /** + * Binds the org_kde_plasma_virtual_desktop_management with @p name and @p version. + * If the @p name does not exist or is not for the Plasma Virtual desktop interface, + * @c null will be returned. + * + * Prefer using createPlasmaShell instead. + * @see createPlasmaShell + * @since 5.46 + **/ + org_kde_plasma_virtual_desktop_management *bindPlasmaVirtualDesktopManagement(uint32_t name, uint32_t version) const; + /** * Binds the org_kde_plasma_window_management with @p name and @p version. * If the @p name does not exist or is not for the Plasma window management interface, * @c null will be returned. * * Prefer using createPlasmaWindowManagement instead. * @see createPlasmaWindowManagement - * @since 5.4 + * @since 5.46 **/ org_kde_plasma_window_management *bindPlasmaWindowManagement(uint32_t name, uint32_t version) const; /** @@ -814,6 +827,22 @@ **/ PlasmaShell *createPlasmaShell(quint32 name, quint32 version, QObject *parent = nullptr); /** + * Creates a PlasmaVirtualDesktopManagement and sets it up to manage the interface identified by + * @p name and @p version. + * + * Note: in case @p name is invalid or isn't for the org_kde_plasma_virtual_desktop_management interface, + * the returned VirtualDesktop will not be valid. Therefore it's recommended to call + * isValid on the created instance. + * + * @param name The name of the org_kde_plasma_virtual_desktop_management interface to bind + * @param version The version or the org_kde_plasma_virtual_desktop_management interface to use + * @param parent The parent for PlasmaShell + * + * @returns The created PlasmaShell. + * @since 5.46 + **/ + PlasmaVirtualDesktopManagement *createPlasmaVirtualDesktopManagement(quint32 name, quint32 version, QObject *parent = nullptr); + /** * Creates a PlasmaWindowManagement and sets it up to manage the interface identified by * @p name and @p version. * @@ -1247,6 +1276,13 @@ **/ void plasmaShellAnnounced(quint32 name, quint32 version); /** + * Emitted whenever a org_kde_plasma_virtual_desktop_management interface gets announced. + * @param name The name for the announced interface + * @param version The maximum supported version of the announced interface + * @since 5.46 + **/ + void plasmaVirtualDesktopManagementAnnounced(quint32 name, quint32 version); + /** * Emitted whenever a org_kde_plasma_window_management interface gets announced. * @param name The name for the announced interface * @param version The maximum supported version of the announced interface @@ -1489,6 +1525,12 @@ **/ void plasmaShellRemoved(quint32 name); /** + * Emitted whenever a org_kde_plasma_virtual_desktop_management interface gets removed. + * @param name The name for the removed interface + * @since 5.46 + **/ + void plasmaVirtualDesktopManagementRemoved(quint32 name); + /** * Emitted whenever a org_kde_plasma_window_management interface gets removed. * @param name The name for the removed interface * @since 5.4 diff --git a/src/client/registry.cpp b/src/client/registry.cpp --- a/src/client/registry.cpp +++ b/src/client/registry.cpp @@ -34,6 +34,7 @@ #include "outputdevice.h" #include "output.h" #include "plasmashell.h" +#include "plasmavirtualdesktop.h" #include "plasmawindowmanagement.h" #include "pointerconstraints.h" #include "pointergestures.h" @@ -61,6 +62,7 @@ #include #include #include +#include #include #include #include @@ -170,6 +172,13 @@ &Registry::plasmaShellAnnounced, &Registry::plasmaShellRemoved }}, + {Registry::Interface::PlasmaVirtualDesktopManagement, { + 1, + QByteArrayLiteral("org_kde_plasma_virtual_desktop_management"), + &org_kde_plasma_virtual_desktop_management_interface, + &Registry::plasmaVirtualDesktopManagementAnnounced, + &Registry::plasmaVirtualDesktopManagementRemoved + }}, {Registry::Interface::PlasmaWindowManagement, { 9, QByteArrayLiteral("org_kde_plasma_window_management"), @@ -646,6 +655,7 @@ BIND(FullscreenShell, _wl_fullscreen_shell) BIND(DataDeviceManager, wl_data_device_manager) BIND(PlasmaShell, org_kde_plasma_shell) +BIND(PlasmaVirtualDesktopManagement, org_kde_plasma_virtual_desktop_management) BIND(PlasmaWindowManagement, org_kde_plasma_window_management) BIND(Idle, org_kde_kwin_idle) BIND(RemoteAccessManager, org_kde_kwin_remote_access_manager) @@ -709,6 +719,7 @@ CREATE(Output) CREATE(DataDeviceManager) CREATE(PlasmaShell) +CREATE(PlasmaVirtualDesktopManagement) CREATE(PlasmaWindowManagement) CREATE(Idle) CREATE(RemoteAccessManager) diff --git a/src/client/textinput.h b/src/client/textinput.h --- a/src/client/textinput.h +++ b/src/client/textinput.h @@ -342,7 +342,7 @@ quint32 afterLength; }; /** - * @returns The lenght in bytes which should be deleted around the cursor position + * @returns The length in bytes which should be deleted around the cursor position * @see committed **/ DeleteSurroundingText deleteSurroundingText() const; diff --git a/src/client/textinput_v0.cpp b/src/client/textinput_v0.cpp --- a/src/client/textinput_v0.cpp +++ b/src/client/textinput_v0.cpp @@ -437,6 +437,7 @@ bool isValid() override; void setupV0(wl_text_input_manager *ti) override; TextInput *createTextInput(Seat *seat, QObject *parent = nullptr) override; + using TextInputManager::Private::operator zwp_text_input_manager_v2*; //overriding only one overload results in a compiler warning. This tells GCC we're doing it deliberately operator wl_text_input_manager*() override { return textinputmanagerunstablev0; } diff --git a/src/client/textinput_v2.cpp b/src/client/textinput_v2.cpp --- a/src/client/textinput_v2.cpp +++ b/src/client/textinput_v2.cpp @@ -461,6 +461,7 @@ bool isValid() override; void setupV2(zwp_text_input_manager_v2 *ti) override; TextInput *createTextInput(Seat *seat, QObject *parent = nullptr) override; + using TextInputManager::Private::operator wl_text_input_manager*; operator zwp_text_input_manager_v2*() override { return textinputmanagerunstablev2; } diff --git a/src/client/xdgoutput.h b/src/client/xdgoutput.h new file mode 100644 --- /dev/null +++ b/src/client/xdgoutput.h @@ -0,0 +1,229 @@ +/**************************************************************************** +Copyright 2018 David Edmundson + +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 . +****************************************************************************/ +#ifndef KWAYLAND_CLIENT_XDGOUTPUT_H +#define KWAYLAND_CLIENT_XDGOUTPUT_H + +#include +#include +#include + +#include + +struct zxdg_output_manager_v1; +struct zxdg_output_v1; + +namespace KWayland +{ +namespace Client +{ + +class EventQueue; +class XdgOutput; +class Output; + +/** + * @short Wrapper for the zxdg_output_manager_v1 interface. + * + * This class provides a convenient wrapper for the zxdg_output_manager_v1 interface. + * + * This provides the logical size of the output. This is useful in case it doesn't match the + * pixelSize / outputScale. + * + * To use this class one needs to interact with the Registry. There are two + * possible ways to create the XdgOutputManager interface: + * @code + * XdgOutputManager *c = registry->createXdgOutputManager(name, version); + * @endcode + * + * This creates the XdgOutputManager and sets it up directly. As an alternative this + * can also be done in a more low level way: + * @code + * XdgOutputManager *c = new XdgOutputManager; + * c->setup(registry->bindXdgOutputManager(name, version)); + * @endcode + * + * The XdgOutputManager can be used as a drop-in replacement for any zxdg_output_manager_v1 + * pointer as it provides matching cast operators. + * + * @since 5.47 + * + * @see Registry + **/ +class KWAYLANDCLIENT_EXPORT XdgOutputManager : public QObject +{ + Q_OBJECT +public: + /** + * Creates a new XdgOutputManager. + * Note: after constructing the XdgOutputManager it is not yet valid and one needs + * to call setup. In order to get a ready to use XdgOutputManager prefer using + * Registry::createXdgOutputManager. + **/ + explicit XdgOutputManager(QObject *parent = nullptr); + virtual ~XdgOutputManager(); + + /** + * Setup this XdgOutputManager to manage the @p xdgoutputmanager. + * When using Registry::createXdgOutputManager there is no need to call this + * method. + **/ + void setup(zxdg_output_manager_v1 *xdgoutputmanager); + /** + * @returns @c true if managing a zxdg_output_manager_v1. + **/ + bool isValid() const; + /** + * Releases the zxdg_output_manager_v1 interface. + * After the interface has been released the XdgOutputManager instance is no + * longer valid and can be setup with another zxdg_output_manager_v1 interface. + **/ + void release(); + /** + * Destroys the data held by this XdgOutputManager. + * This method is supposed to be used when the connection to the Wayland + * server goes away. If the connection is not valid anymore, it's not + * possible to call release anymore as that calls into the Wayland + * connection and the call would fail. This method cleans up the data, so + * that the instance can be deleted or set up to a new zxdg_output_manager_v1 interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, xdgoutputmanager, &XdgOutputManager::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + /** + * Sets the @p queue to use for creating objects with this XdgOutputManager. + **/ + void setEventQueue(EventQueue *queue); + /** + * @returns The event queue to use for creating objects with this XdgOutputManager. + **/ + EventQueue *eventQueue(); + + XdgOutput *getXdgOutput(Output *output, QObject *parent = nullptr); + + operator zxdg_output_manager_v1*(); + operator zxdg_output_manager_v1*() const; + +Q_SIGNALS: + /** + * The corresponding global for this interface on the Registry got removed. + * + * This signal gets only emitted if the XdgOutputManager got created by + * Registry::createXdgOutputManager + **/ + void removed(); + +private: + class Private; + QScopedPointer d; +}; + +/** + * @short Wrapper for the zxdg_output_v1 interface. + * + * This class provides a convenient wrapper for the zxdg_output_v1 interface. + * + * The XdgOutputManager can be used as a drop-in replacement for any zxdg_output_v1 + * pointer as it provides matching cast operators. + * + * This protocol provides a potentially more correct size and position of the screen + * than Output with respect to scaling. + * + * @see Registry + **/ + +class KWAYLANDCLIENT_EXPORT XdgOutput : public QObject +{ + Q_OBJECT +public: + virtual ~XdgOutput(); + + /** + * Setup this XdgOutput to manage the @p xdgoutput. + * When using XdgOutputManager::createXdgOutput there is no need to call this + * method. + **/ + void setup(zxdg_output_v1 *xdgoutput); + /** + * @returns @c true if managing a zxdg_output_v1. + **/ + bool isValid() const; + /** + * Releases the zxdg_output_v1 interface. + * After the interface has been released the XdgOutput instance is no + * longer valid and can be setup with another zxdg_output_v1 interface. + **/ + void release(); + /** + * Destroys the data held by this XdgOutput. + * This method is supposed to be used when the connection to the Wayland + * server goes away. If the connection is not valid anymore, it's not + * possible to call release anymore as that calls into the Wayland + * connection and the call would fail. This method cleans up the data, so + * that the instance can be deleted or set up to a new zxdg_output_v1 interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, xdgoutput, &XdgOutput::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + operator zxdg_output_v1*(); + operator zxdg_output_v1*() const; + + /** + * The top left position of the output in compositor co-ordinates + */ + QPoint logicalPosition() const; + + /** + * The size of the output in compositor co-ordinates + * (i.e pixel size / output scale) + */ + QSize logicalSize() const; + +Q_SIGNALS: + /** + * Emitted when the logical position or size changes + */ + void changed(); + +private: + friend class XdgOutputManager; + explicit XdgOutput(QObject *parent = nullptr); + class Private; + QScopedPointer d; +}; + + +} +} + +#endif diff --git a/src/client/xdgoutput.cpp b/src/client/xdgoutput.cpp new file mode 100644 --- /dev/null +++ b/src/client/xdgoutput.cpp @@ -0,0 +1,240 @@ +/**************************************************************************** +Copyright 2018 David Edmundson + +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 "xdgoutput.h" +#include "event_queue.h" +#include "wayland_pointer_p.h" +#include "output.h" + +#include +#include + +#include + +namespace KWayland +{ +namespace Client +{ + +class XdgOutputManager::Private +{ +public: + Private() = default; + + void setup(zxdg_output_manager_v1 *arg); + + WaylandPointer xdgoutputmanager; + EventQueue *queue = nullptr; +}; + +XdgOutputManager::XdgOutputManager(QObject *parent) + : QObject(parent) + , d(new Private) +{ +} + +void XdgOutputManager::Private::setup(zxdg_output_manager_v1 *arg) +{ + Q_ASSERT(arg); + Q_ASSERT(!xdgoutputmanager); + xdgoutputmanager.setup(arg); +} + +XdgOutputManager::~XdgOutputManager() +{ + release(); +} + +void XdgOutputManager::setup(zxdg_output_manager_v1 *xdgoutputmanager) +{ + d->setup(xdgoutputmanager); +} + +void XdgOutputManager::release() +{ + d->xdgoutputmanager.release(); +} + +void XdgOutputManager::destroy() +{ + d->xdgoutputmanager.destroy(); +} + +XdgOutputManager::operator zxdg_output_manager_v1*() { + return d->xdgoutputmanager; +} + +XdgOutputManager::operator zxdg_output_manager_v1*() const { + return d->xdgoutputmanager; +} + +bool XdgOutputManager::isValid() const +{ + return d->xdgoutputmanager.isValid(); +} + +void XdgOutputManager::setEventQueue(EventQueue *queue) +{ + d->queue = queue; +} + +EventQueue *XdgOutputManager::eventQueue() +{ + return d->queue; +} + +XdgOutput *XdgOutputManager::getXdgOutput(Output *output, QObject *parent) +{ + Q_ASSERT(isValid()); + auto p = new XdgOutput(parent); + auto w = zxdg_output_manager_v1_get_xdg_output(d->xdgoutputmanager, *output); + if (d->queue) { + d->queue->addProxy(w); + } + p->setup(w); + return p; +} + +struct XdgOutputBuffer +{ + QPoint logicalPosition; + QSize logicalSize; +}; + +class XdgOutput::Private +{ +public: + Private(XdgOutput *q); + + void setup(zxdg_output_v1 *arg); + + WaylandPointer xdgoutput; + + XdgOutputBuffer current; + XdgOutputBuffer pending; + +private: + XdgOutput *q; + +private: + static void logical_positionCallback(void *data, zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y); + static void logical_sizeCallback(void *data, zxdg_output_v1 *zxdg_output_v1, int32_t width, int32_t height); + static void doneCallback(void *data, zxdg_output_v1 *zxdg_output_v1); + + static const zxdg_output_v1_listener s_listener; +}; + +const zxdg_output_v1_listener XdgOutput::Private::s_listener = { + logical_positionCallback, + logical_sizeCallback, + doneCallback +}; + +void XdgOutput::Private::logical_positionCallback(void *data, zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->xdgoutput == zxdg_output_v1); + p->pending.logicalPosition = QPoint(x,y); +} + +void XdgOutput::Private::logical_sizeCallback(void *data, zxdg_output_v1 *zxdg_output_v1, int32_t width, int32_t height) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->xdgoutput == zxdg_output_v1); + p->pending.logicalSize = QSize(width,height); +} + +void XdgOutput::Private::doneCallback(void *data, zxdg_output_v1 *zxdg_output_v1) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->xdgoutput == zxdg_output_v1); + std::swap(p->current, p->pending); + + if (p->current.logicalSize != p->pending.logicalSize || + p->current.logicalPosition != p->pending.logicalPosition) { + emit p->q->changed(); + } +} + +XdgOutput::Private::Private(XdgOutput *q) + : q(q) +{ +} + +XdgOutput::XdgOutput(QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ +} + +void XdgOutput::Private::setup(zxdg_output_v1 *arg) +{ + Q_ASSERT(arg); + Q_ASSERT(!xdgoutput); + xdgoutput.setup(arg); + zxdg_output_v1_add_listener(xdgoutput, &s_listener, this); +} + +XdgOutput::~XdgOutput() +{ + release(); +} + +void XdgOutput::setup(zxdg_output_v1 *xdgoutput) +{ + d->setup(xdgoutput); +} + +void XdgOutput::release() +{ + d->xdgoutput.release(); +} + +void XdgOutput::destroy() +{ + d->xdgoutput.destroy(); +} + +QSize XdgOutput::logicalSize() const +{ + return d->current.logicalSize; +} + +QPoint XdgOutput::logicalPosition() const +{ + return d->current.logicalPosition; +} + +XdgOutput::operator zxdg_output_v1*() { + return d->xdgoutput; +} + +XdgOutput::operator zxdg_output_v1*() const { + return d->xdgoutput; +} + +bool XdgOutput::isValid() const +{ + return d->xdgoutput.isValid(); +} + + +} +} + diff --git a/src/client/xdgshell_v6.cpp b/src/client/xdgshell_v6.cpp --- a/src/client/xdgshell_v6.cpp +++ b/src/client/xdgshell_v6.cpp @@ -160,24 +160,24 @@ } uint32_t gravity = 0; - if (positioner.anchorEdge().testFlag(Qt::LeftEdge)) { + if (positioner.gravity().testFlag(Qt::LeftEdge)) { gravity |= ZXDG_POSITIONER_V6_GRAVITY_LEFT; } - if (positioner.anchorEdge().testFlag(Qt::TopEdge)) { + if (positioner.gravity().testFlag(Qt::TopEdge)) { gravity |= ZXDG_POSITIONER_V6_GRAVITY_TOP; } - if (positioner.anchorEdge().testFlag(Qt::RightEdge)) { + if (positioner.gravity().testFlag(Qt::RightEdge)) { gravity |= ZXDG_POSITIONER_V6_GRAVITY_RIGHT; } - if (positioner.anchorEdge().testFlag(Qt::BottomEdge)) { + if (positioner.gravity().testFlag(Qt::BottomEdge)) { gravity |= ZXDG_POSITIONER_V6_GRAVITY_BOTTOM; } - zxdg_positioner_v6_set_anchor(p, anchor); if (gravity != 0) { zxdg_positioner_v6_set_gravity(p, gravity); } uint32_t constraint = 0; + if (positioner.constraints().testFlag(XdgPositioner::Constraint::SlideX)) { constraint |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X; } @@ -191,7 +191,7 @@ constraint |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y; } if (positioner.constraints().testFlag(XdgPositioner::Constraint::ResizeX)) { - constraint |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y; + constraint |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_X; } if (positioner.constraints().testFlag(XdgPositioner::Constraint::ResizeY)) { constraint |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y; diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -25,6 +25,7 @@ output_interface.cpp pointer_interface.cpp plasmashell_interface.cpp + plasmavirtualdesktop_interface.cpp plasmawindowmanagement_interface.cpp pointerconstraints_interface.cpp pointerconstraints_interface_v1.cpp @@ -76,6 +77,11 @@ ) ecm_add_wayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/plasma-virtual-desktop.xml + BASENAME plasma-virtual-desktop +) + +ecm_add_wayland_server_protocol(SERVER_LIB_SRCS PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/plasma-window-management.xml BASENAME plasma-window-management ) @@ -196,6 +202,9 @@ ${CMAKE_CURRENT_BINARY_DIR}/wayland-org_kde_kwin_outputdevice-server-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-plasma-shell-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-plasma-shell-server-protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/wayland-plasma-shell-client-protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/wayland-plasma-virtual-desktop-server-protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/wayland-plasma-virtual-desktop-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-plasma-window-management-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-plasma-window-management-server-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-qt-surface-extension-client-protocol.h @@ -300,6 +309,7 @@ pointerconstraints_interface.h pointergestures_interface.h plasmashell_interface.h + plasmavirtualdesktop_interface.h plasmawindowmanagement_interface.h qtsurfaceextension_interface.h region_interface.h diff --git a/src/server/datadevice_interface.cpp b/src/server/datadevice_interface.cpp --- a/src/server/datadevice_interface.cpp +++ b/src/server/datadevice_interface.cpp @@ -130,6 +130,9 @@ wl_resource_post_error(dataSource->resource(), WL_DATA_SOURCE_ERROR_INVALID_SOURCE, "Data source is for drag and drop"); return; } + if (selection == dataSource) { + return; + } Q_Q(DataDeviceInterface); QObject::disconnect(selectionUnboundConnection); QObject::disconnect(selectionDestroyedConnection); diff --git a/src/server/display.h b/src/server/display.h --- a/src/server/display.h +++ b/src/server/display.h @@ -86,6 +86,7 @@ class XdgForeignInterface; class AppMenuManagerInterface; class ServerSideDecorationPaletteManagerInterface; +class PlasmaVirtualDesktopManagementInterface; class XdgOutputManagerInterface; /** @@ -269,6 +270,14 @@ /** + * Creates the PlasmaVirtualDesktopManagementInterface in interface @p version. + * + * @returns The created manager object + * @since 5.46 + **/ + PlasmaVirtualDesktopManagementInterface *createPlasmaVirtualDesktopManagement(QObject *parent = nullptr); + + /** * Gets the ClientConnection for the given @p client. * If there is no ClientConnection yet for the given @p client, it will be created. * @param client The native client for which the ClientConnection is retrieved diff --git a/src/server/display.cpp b/src/server/display.cpp --- a/src/server/display.cpp +++ b/src/server/display.cpp @@ -51,6 +51,7 @@ #include "xdgshell_stable_interface_p.h" #include "appmenu_interface.h" #include "server_decoration_palette_interface.h" +#include "plasmavirtualdesktop_interface.h" #include "xdgoutput_interface.h" #include @@ -462,6 +463,14 @@ return b; } + +PlasmaVirtualDesktopManagementInterface *Display::createPlasmaVirtualDesktopManagement(QObject *parent) +{ + auto b = new PlasmaVirtualDesktopManagementInterface(this, parent); + connect(this, &Display::aboutToTerminate, b, [this, b] { delete b; }); + return b; +} + XdgOutputManagerInterface *Display::createXdgOutputManager(QObject *parent) { auto b = new XdgOutputManagerInterface(this, parent); diff --git a/src/server/plasmashell_interface.h b/src/server/plasmashell_interface.h --- a/src/server/plasmashell_interface.h +++ b/src/server/plasmashell_interface.h @@ -135,10 +135,17 @@ * @returns true if this window doesn't want to be listed * in the taskbar * @since 5.5 - */ + **/ bool skipTaskbar() const; /** + * @returns true if this window doesn't want to be listed + * in a window switcher + * @since 5.47 + **/ + bool skipSwitcher() const; + + /** * Informs the PlasmaShellSurfaceInterface that the auto-hiding panel got hidden. * Once it is shown again the method {@link showAutoHidingPanel} should be used. * @@ -192,6 +199,10 @@ * A change in the skip taskbar property has been requested */ void skipTaskbarChanged(); + /** + * A change in the skip switcher property has been requested + **/ + void skipSwitcherChanged(); /** * A surface with Role Panel and PanelBehavior AutoHide requested to be hidden. diff --git a/src/server/plasmashell_interface.cpp b/src/server/plasmashell_interface.cpp --- a/src/server/plasmashell_interface.cpp +++ b/src/server/plasmashell_interface.cpp @@ -50,7 +50,7 @@ static const quint32 s_version; }; -const quint32 PlasmaShellInterface::Private::s_version = 4; +const quint32 PlasmaShellInterface::Private::s_version = 5; PlasmaShellInterface::Private::Private(PlasmaShellInterface *q, Display *d) : Global::Private(d, &org_kde_plasma_shell_interface, s_version) @@ -76,6 +76,7 @@ bool m_positionSet = false; PanelBehavior m_panelBehavior = PanelBehavior::AlwaysVisible; bool m_skipTaskbar = false; + bool m_skipSwitcher = false; bool panelTakesFocus = false; private: @@ -85,6 +86,7 @@ static void setRoleCallback(wl_client *client, wl_resource *resource, uint32_t role); static void setPanelBehaviorCallback(wl_client *client, wl_resource *resource, uint32_t flag); static void setSkipTaskbarCallback(wl_client *client, wl_resource *resource, uint32_t skip); + static void setSkipSwitcherCallback(wl_client *client, wl_resource *resource, uint32_t skip); static void panelAutoHideHideCallback(wl_client *client, wl_resource *resource); static void panelAutoHideShowCallback(wl_client *client, wl_resource *resource); static void panelTakesFocusCallback(wl_client *client, wl_resource *resource, uint32_t takesFocus); @@ -165,7 +167,8 @@ setSkipTaskbarCallback, panelAutoHideHideCallback, panelAutoHideShowCallback, - panelTakesFocusCallback + panelTakesFocusCallback, + setSkipSwitcherCallback }; #endif @@ -277,6 +280,14 @@ emit s->q_func()->skipTaskbarChanged(); } +void PlasmaShellSurfaceInterface::Private::setSkipSwitcherCallback(wl_client *client, wl_resource *resource, uint32_t skip) +{ + auto s = cast(resource); + Q_ASSERT(client == *s->client); + s->m_skipSwitcher = (bool)skip; + emit s->q_func()->skipSwitcherChanged(); +} + void PlasmaShellSurfaceInterface::Private::panelAutoHideHideCallback(wl_client *client, wl_resource *resource) { auto s = cast(resource); @@ -361,6 +372,12 @@ return d->m_skipTaskbar; } +bool PlasmaShellSurfaceInterface::skipSwitcher() const +{ + Q_D(); + return d->m_skipSwitcher; +} + void PlasmaShellSurfaceInterface::hideAutoHidingPanel() { Q_D(); diff --git a/src/server/plasmavirtualdesktop_interface.h b/src/server/plasmavirtualdesktop_interface.h new file mode 100644 --- /dev/null +++ b/src/server/plasmavirtualdesktop_interface.h @@ -0,0 +1,171 @@ +/**************************************************************************** +Copyright 2018 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 . +****************************************************************************/ +#ifndef KWAYLAND_SERVER_PLASMAVIRTUALDESKTOP_H +#define KWAYLAND_SERVER_PLASMAVIRTUALDESKTOP_H + +#include "global.h" +#include "resource.h" + +#include + +namespace KWayland +{ +namespace Server +{ + +class Display; +class PlasmaVirtualDesktopInterface; + +/** + * @short Wrapper for the org_kde_plasma_virtual_desktop_management interface. + * + * This class provides a convenient wrapper for the org_kde_plasma_virtual_desktop_management interface. + * @since 5.46 + */ +class KWAYLANDSERVER_EXPORT PlasmaVirtualDesktopManagementInterface : public Global +{ + Q_OBJECT +public: + virtual ~PlasmaVirtualDesktopManagementInterface(); + + /** + * Sets a new layout for this desktop grid. + */ + void setLayout(quint32 rows, quint32 columns); + + /** + * @returns A desktop identified uniquely by this id. + * If not found, nullptr will be returned. + * @see createDesktop + */ + PlasmaVirtualDesktopInterface *desktop(const QString &id); + + /** + * @returns A desktop identified uniquely by this id, if not found + * a new desktop will be created for this id at a given position. + * @param id the id for the desktop + * @param position the position the desktop will be in, if not provided, + * it will be appended at the end. If the desktop was already + * existing, position is ignored. + */ + PlasmaVirtualDesktopInterface *createDesktop(const QString &id, quint32 position = std::numeric_limits::max()); + + /** + * Removed and destroys the desktop identified by id, if present + */ + void removeDesktop(const QString &id); + + /** + * @returns All tghe desktops present. + */ + QList desktops() const; + + /** + * Inform the clients that all the properties have been sent, and + * their client-side representation is complete. + */ + void sendDone(); + +Q_SIGNALS: + /** + * A desktop has been activated + */ + void desktopActivated(const QString &id); + + /** + * The client asked to remove a desktop, It's responsibility of the server + * deciding whether to remove it or not. + */ + void desktopRemoveRequested(const QString &id); + + /** + * The client asked to create a desktop, It's responsibility of the server + * deciding whether to create it or not. + * @param name The desired user readable name + * @param position The desired position. Normalized, guaranteed to be in the range 0-count + */ + void desktopCreateRequested(const QString &name, quint32 position); + +private: + explicit PlasmaVirtualDesktopManagementInterface(Display *display, QObject *parent = nullptr); + friend class Display; + class Private; + Private *d_func() const; +}; + +class KWAYLANDSERVER_EXPORT PlasmaVirtualDesktopInterface : public QObject +{ + Q_OBJECT +public: + virtual ~PlasmaVirtualDesktopInterface(); + + /** + * @returns the unique id for this desktop. + * ids are set at creation time by PlasmaVirtualDesktopManagementInterface::createDesktop + * and can't be changed at runtime. + */ + QString id() const; + + /** + * Sets a new name for this desktop + */ + void setName(const QString &name); + + /** + * @returns the name for this desktop + */ + QString name() const; + + /** + * Set this desktop as active or not. + * It's the compositor responsibility to manage the mutual exclusivity of active desktops. + */ + void setActive(bool active); + + /** + * @returns true if this desktop is active. Only one at a time will be. + */ + bool isActive() const; + + /** + * Inform the clients that all the properties have been sent, and + * their client-side representation is complete. + */ + void sendDone(); + +Q_SIGNALS: + /** + * Emitted when the client asked to activate this desktop: + * it's the decision of the server whether to perform the activation or not. + */ + void activateRequested(); + +private: + explicit PlasmaVirtualDesktopInterface(PlasmaVirtualDesktopManagementInterface *parent); + friend class PlasmaVirtualDesktopManagementInterface; + + class Private; + const QScopedPointer d; +}; + +} +} + +#endif diff --git a/src/server/plasmavirtualdesktop_interface.cpp b/src/server/plasmavirtualdesktop_interface.cpp new file mode 100644 --- /dev/null +++ b/src/server/plasmavirtualdesktop_interface.cpp @@ -0,0 +1,402 @@ +/**************************************************************************** +Copyright 2018 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 "plasmavirtualdesktop_interface.h" +#include "display.h" +#include "global_p.h" +#include "resource_p.h" + +#include +#include + +#include +#include + +namespace KWayland +{ +namespace Server +{ + +class Q_DECL_HIDDEN PlasmaVirtualDesktopInterface::Private +{ +public: + Private(PlasmaVirtualDesktopInterface *q, PlasmaVirtualDesktopManagementInterface *c); + ~Private(); + void createResource(wl_resource *parent, quint32 serial); + + PlasmaVirtualDesktopInterface *q; + PlasmaVirtualDesktopManagementInterface *vdm; + + QVector resources; + QString id; + QString name; + bool active = false; + +private: + static void unbind(wl_resource *resource); + static void requestActivateCallback(wl_client *client, wl_resource *resource); + + static Private *cast(wl_resource *resource) { + return reinterpret_cast(wl_resource_get_user_data(resource)); + } + + wl_listener listener; + static const struct org_kde_plasma_virtual_desktop_interface s_interface; +}; + + +class Q_DECL_HIDDEN PlasmaVirtualDesktopManagementInterface::Private : public Global::Private +{ +public: + Private(PlasmaVirtualDesktopManagementInterface *q, Display *d); + + QVector resources; + QList desktops; + quint32 rows = 0; + quint32 columns = 0; + + inline QList::const_iterator constFindDesktop(const QString &id); + inline QList::iterator findDesktop(const QString &id); +private: + void bind(wl_client *client, uint32_t version, uint32_t id) override; + + static void unbind(wl_resource *resource); + static Private *cast(wl_resource *r) { + return reinterpret_cast(wl_resource_get_user_data(r)); + } + + static void getVirtualDesktopCallback(wl_client *client, wl_resource *resource, uint32_t serial, const char *id); + static void requestCreateVirtualDesktopCallback(wl_client *client, wl_resource *resource, const char *name, uint32_t position); + static void requestRemoveVirtualDesktopCallback(wl_client *client, wl_resource *resource, const char *id); + + PlasmaVirtualDesktopManagementInterface *q; + + static const struct org_kde_plasma_virtual_desktop_management_interface s_interface; + static const quint32 s_version; +}; + +const quint32 PlasmaVirtualDesktopManagementInterface::Private::s_version = 1; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct org_kde_plasma_virtual_desktop_management_interface PlasmaVirtualDesktopManagementInterface::Private::s_interface = { + getVirtualDesktopCallback, + requestCreateVirtualDesktopCallback, + requestRemoveVirtualDesktopCallback +}; +#endif + +inline QList::const_iterator PlasmaVirtualDesktopManagementInterface::Private::constFindDesktop(const QString &id) +{ + return std::find_if( desktops.constBegin(), + desktops.constEnd(), + [id]( const PlasmaVirtualDesktopInterface *desk ){ return desk->id() == id; } ); +} + +inline QList::iterator PlasmaVirtualDesktopManagementInterface::Private::findDesktop(const QString &id) +{ + return std::find_if( desktops.begin(), + desktops.end(), + [id]( const PlasmaVirtualDesktopInterface *desk ){ return desk->id() == id; } ); +} + +void PlasmaVirtualDesktopManagementInterface::Private::getVirtualDesktopCallback(wl_client *client, wl_resource *resource, uint32_t serial, const char *id) +{ + Q_UNUSED(client) + auto s = cast(resource); + + auto i = s->constFindDesktop(QString::fromUtf8(id)); + if (i == s->desktops.constEnd()) { + return; + } + + (*i)->d->createResource(resource, serial); +} + +void PlasmaVirtualDesktopManagementInterface::Private::requestCreateVirtualDesktopCallback(wl_client *client, wl_resource *resource, const char *name, uint32_t position) +{ + Q_UNUSED(client) + auto s = cast(resource); + emit s->q->desktopCreateRequested(QString::fromUtf8(name), qBound(0, position, (quint32)s->desktops.count())); +} + +void PlasmaVirtualDesktopManagementInterface::Private::requestRemoveVirtualDesktopCallback(wl_client *client, wl_resource *resource, const char *id) +{ + Q_UNUSED(client) + auto s = cast(resource); + emit s->q->desktopRemoveRequested(QString::fromUtf8(id)); +} + +PlasmaVirtualDesktopManagementInterface::Private::Private(PlasmaVirtualDesktopManagementInterface *q, Display *d) + : Global::Private(d, &org_kde_plasma_virtual_desktop_management_interface, s_version) + , q(q) +{ +} + +void PlasmaVirtualDesktopManagementInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id) +{ + auto c = display->getConnection(client); + wl_resource *resource = c->createResource(&org_kde_plasma_virtual_desktop_management_interface, qMin(version, s_version), id); + + if (!resource) { + wl_client_post_no_memory(client); + return; + } + resources << resource; + + wl_resource_set_implementation(resource, &s_interface, this, unbind); + + quint32 i = 0; + for (auto it = desktops.constBegin(); it != desktops.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_management_send_desktop_created(resource, (*it)->id().toUtf8().constData(), i++); + } + org_kde_plasma_virtual_desktop_management_send_done(resource); +} + +void PlasmaVirtualDesktopManagementInterface::Private::unbind(wl_resource *resource) +{ + auto dm = reinterpret_cast(wl_resource_get_user_data(resource)); + dm->resources.removeAll(resource); +} + +PlasmaVirtualDesktopManagementInterface::PlasmaVirtualDesktopManagementInterface(Display *display, QObject *parent) + : Global(new Private(this, display), parent) +{ +} + +PlasmaVirtualDesktopManagementInterface::~PlasmaVirtualDesktopManagementInterface() +{} + +PlasmaVirtualDesktopManagementInterface::Private *PlasmaVirtualDesktopManagementInterface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +PlasmaVirtualDesktopInterface *PlasmaVirtualDesktopManagementInterface::desktop(const QString &id) +{ + Q_D(); + auto i = d->constFindDesktop(id); + if (i != d->desktops.constEnd()) { + return *i; + } + return nullptr; +} + +PlasmaVirtualDesktopInterface *PlasmaVirtualDesktopManagementInterface::createDesktop(const QString &id, quint32 position) +{ + Q_D(); + auto i = d->constFindDesktop(id); + if (i != d->desktops.constEnd()) { + return *i; + } + + const quint32 actualPosition = qMin(position, (quint32)d->desktops.count()); + + PlasmaVirtualDesktopInterface *desktop = new PlasmaVirtualDesktopInterface(this); + desktop->d->id = id; + for (auto it = desktop->d->resources.constBegin(); it != desktop->d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_send_desktop_id(*it, id.toUtf8().constData()); + } + + //activate the first desktop TODO: to be done here? + if (d->desktops.isEmpty()) { + desktop->d->active = true; + } + + d->desktops.insert(actualPosition, desktop); + //NOTE: this in case the desktop has been deleted but not trough removedesktop + connect(desktop, &QObject::destroyed, this, + [this, id] { + Q_D(); + auto i = d->findDesktop(id); + if (i != d->desktops.end()) { + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_management_send_desktop_removed(*it, id.toUtf8().constData()); + } + + d->desktops.erase(i); + } + } + ); + + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_management_send_desktop_created(*it, id.toUtf8().constData(), actualPosition); + } + + return desktop; +} + +void PlasmaVirtualDesktopManagementInterface::removeDesktop(const QString &id) +{ + Q_D(); + auto deskIt = d->findDesktop(id); + if (deskIt == d->desktops.end()) { + return; + } + + for (auto it = (*deskIt)->d->resources.constBegin(); it != (*deskIt)->d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_send_removed(*it); + } + + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_management_send_desktop_removed(*it, id.toUtf8().constData()); + } + + d->desktops.erase(deskIt); + (*deskIt)->deleteLater(); +} + +QList PlasmaVirtualDesktopManagementInterface::desktops() const +{ + Q_D(); + return d->desktops; +} + +void PlasmaVirtualDesktopManagementInterface::sendDone() +{ + Q_D(); + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_management_send_done(*it); + } +} + +//// PlasmaVirtualDesktopInterface + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct org_kde_plasma_virtual_desktop_interface PlasmaVirtualDesktopInterface::Private::s_interface = { + requestActivateCallback +}; +#endif + +void PlasmaVirtualDesktopInterface::Private::requestActivateCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + auto s = cast(resource); + emit s->q->activateRequested(); +} + +PlasmaVirtualDesktopInterface::Private::Private(PlasmaVirtualDesktopInterface *q, PlasmaVirtualDesktopManagementInterface *c) + : q(q), + vdm(c) +{ +} + +PlasmaVirtualDesktopInterface::Private::~Private() +{ + // need to copy, as destroy goes through the destroy listener and modifies the list as we iterate + const auto c = resources; + for (const auto &r : c) { + auto client = wl_resource_get_client(r); + org_kde_plasma_virtual_desktop_send_removed(r); + wl_resource_destroy(r); + wl_client_flush(client); + } +} + +void PlasmaVirtualDesktopInterface::Private::unbind(wl_resource *resource) +{ + Private *p = reinterpret_cast(wl_resource_get_user_data(resource)); + p->resources.removeAll(resource); +} + +void PlasmaVirtualDesktopInterface::Private::createResource(wl_resource *parent, quint32 serial) +{ + ClientConnection *c = vdm->display()->getConnection(wl_resource_get_client(parent)); + wl_resource *resource = c->createResource(&org_kde_plasma_virtual_desktop_interface, wl_resource_get_version(parent), serial); + if (!resource) { + return; + } + wl_resource_set_implementation(resource, &s_interface, this, unbind); + resources << resource; + + org_kde_plasma_virtual_desktop_send_desktop_id(resource, id.toUtf8().constData()); + if (!name.isEmpty()) { + org_kde_plasma_virtual_desktop_send_name(resource, name.toUtf8().constData()); + } + + if (active) { + org_kde_plasma_virtual_desktop_send_activated(resource); + } + + c->flush(); +} + +PlasmaVirtualDesktopInterface::PlasmaVirtualDesktopInterface(PlasmaVirtualDesktopManagementInterface *parent) + : QObject(parent), + d(new Private(this, parent)) +{ +} + +PlasmaVirtualDesktopInterface::~PlasmaVirtualDesktopInterface() +{} + +QString PlasmaVirtualDesktopInterface::id() const +{ + return d->id; +} + +void PlasmaVirtualDesktopInterface::setName(const QString &name) +{ + if (d->name == name) { + return; + } + + d->name = name; + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_send_name(*it, name.toUtf8().constData()); + } +} + +QString PlasmaVirtualDesktopInterface::name() const +{ + return d->name; +} + +void PlasmaVirtualDesktopInterface::setActive(bool active) +{ + if (d->active == active) { + return; + } + + d->active = active; + if (active) { + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_send_activated(*it); + } + } else { + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_send_deactivated(*it); + } + } +} + +bool PlasmaVirtualDesktopInterface::isActive() const +{ + return d->active; +} + +void PlasmaVirtualDesktopInterface::sendDone() +{ + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_send_done(*it); + } +} + +} +} + diff --git a/src/server/plasmavirtualdesktop_interface_unstable_v1.h b/src/server/plasmavirtualdesktop_interface_unstable_v1.h new file mode 100644 --- /dev/null +++ b/src/server/plasmavirtualdesktop_interface_unstable_v1.h @@ -0,0 +1,171 @@ +/**************************************************************************** +Copyright 2018 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 . +****************************************************************************/ +#ifndef KWAYLAND_SERVER_PLASMAVIRTUALDESKTOP_UNSTABLE_V1_H +#define KWAYLAND_SERVER_PLASMAVIRTUALDESKTOP_UNSTABLE_V1_H + +#include "global.h" +#include "resource.h" + +#include + +namespace KWayland +{ +namespace Server +{ + +class Display; +class PlasmaVirtualDesktopV1Interface; + +/** + * @short Wrapper for the org_kde_plasma_virtual_desktop_management interface. + * + * This class provides a convenient wrapper for the org_kde_plasma_virtual_desktop_management interface. + * @since 5.46 + */ +class KWAYLANDSERVER_EXPORT PlasmaVirtualDesktopManagementV1Interface : public Global +{ + Q_OBJECT +public: + virtual ~PlasmaVirtualDesktopManagementV1Interface(); + + /** + * Sets a new layout for this desktop grid. + */ + void setLayout(quint32 rows, quint32 columns); + + /** + * @returns A desktop identified uniquely by this id. + * If not found, nullptr will be returned. + * @see createDesktop + */ + PlasmaVirtualDesktopV1Interface *desktop(const QString &id); + + /** + * @returns A desktop identified uniquely by this id, if not found + * a new desktop will be created for this id at a given position. + * @param id the id for the desktop + * @param position the position the desktop will be in, if not provided, + * it will be appended at the end. If the desktop was already + * existing, position is ignored. + */ + PlasmaVirtualDesktopV1Interface *createDesktop(const QString &id, quint32 position = std::numeric_limits::max()); + + /** + * Removed and destroys the desktop identified by id, if present + */ + void removeDesktop(const QString &id); + + /** + * @returns All tghe desktops present. + */ + QList desktops() const; + + /** + * Inform the clients that all the properties have been sent, and + * their client-side representation is complete. + */ + void sendDone(); + +Q_SIGNALS: + /** + * A desktop has been activated + */ + void desktopActivated(const QString &id); + + /** + * The client asked to remove a desktop, It's responsibility of the server + * deciding whether to remove it or not. + */ + void desktopRemoveRequested(const QString &id); + + /** + * The client asked to create a desktop, It's responsibility of the server + * deciding whether to create it or not. + * @param name The desired user readable name + * @param position The desired position. Normalized, guaranteed to be in the range 0-count + */ + void desktopCreateRequested(const QString &name, quint32 position); + +private: + explicit PlasmaVirtualDesktopManagementV1Interface(Display *display, QObject *parent = nullptr); + friend class Display; + class Private; + Private *d_func() const; +}; + +class KWAYLANDSERVER_EXPORT PlasmaVirtualDesktopV1Interface : public QObject +{ + Q_OBJECT +public: + virtual ~PlasmaVirtualDesktopV1Interface(); + + /** + * @returns the unique id for this desktop. + * ids are set at creation time by PlasmaVirtualDesktopManagementInterfaceUnstableV1::createDesktop + * and can't be changed at runtime. + */ + QString id() const; + + /** + * Sets a new name for this desktop + */ + void setName(const QString &name); + + /** + * @returns the name for this desktop + */ + QString name() const; + + /** + * Set this desktop as active or not. + * It's the compositor responsibility to manage the mutual exclusivity of active desktops. + */ + void setActive(bool active); + + /** + * @returns true if this desktop is active. Only one at a time will be. + */ + bool isActive() const; + + /** + * Inform the clients that all the properties have been sent, and + * their client-side representation is complete. + */ + void sendDone(); + +Q_SIGNALS: + /** + * Emitted when the client asked to activate this desktop: + * it's the decision of the server whether to perform the activation or not. + */ + void activateRequested(); + +private: + explicit PlasmaVirtualDesktopV1Interface(PlasmaVirtualDesktopManagementV1Interface *parent); + friend class PlasmaVirtualDesktopManagementV1Interface; + + class Private; + const QScopedPointer d; +}; + +} +} + +#endif diff --git a/src/server/plasmavirtualdesktop_interface_unstable_v1.cpp b/src/server/plasmavirtualdesktop_interface_unstable_v1.cpp new file mode 100644 --- /dev/null +++ b/src/server/plasmavirtualdesktop_interface_unstable_v1.cpp @@ -0,0 +1,402 @@ +/**************************************************************************** +Copyright 2018 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 "plasmavirtualdesktop_interface_unstable_v1.h" +#include "display.h" +#include "global_p.h" +#include "resource_p.h" + +#include +#include + +#include +#include + +namespace KWayland +{ +namespace Server +{ + +class Q_DECL_HIDDEN PlasmaVirtualDesktopV1Interface::Private +{ +public: + Private(PlasmaVirtualDesktopV1Interface *q, PlasmaVirtualDesktopManagementV1Interface *c); + ~Private(); + void createResource(wl_resource *parent, quint32 serial); + + PlasmaVirtualDesktopV1Interface *q; + PlasmaVirtualDesktopManagementV1Interface *vdm; + + QVector resources; + QString id; + QString name; + bool active = false; + +private: + static void unbind(wl_resource *resource); + static void requestActivateCallback(wl_client *client, wl_resource *resource); + + static Private *cast(wl_resource *resource) { + return reinterpret_cast(wl_resource_get_user_data(resource)); + } + + wl_listener listener; + static const struct org_kde_plasma_virtual_desktop_v1_interface s_interface; +}; + + +class Q_DECL_HIDDEN PlasmaVirtualDesktopManagementV1Interface::Private : public Global::Private +{ +public: + Private(PlasmaVirtualDesktopManagementV1Interface *q, Display *d); + + QVector resources; + QList desktops; + quint32 rows = 0; + quint32 columns = 0; + + inline QList::const_iterator constFindDesktop(const QString &id); + inline QList::iterator findDesktop(const QString &id); +private: + void bind(wl_client *client, uint32_t version, uint32_t id) override; + + static void unbind(wl_resource *resource); + static Private *cast(wl_resource *r) { + return reinterpret_cast(wl_resource_get_user_data(r)); + } + + static void getVirtualDesktopCallback(wl_client *client, wl_resource *resource, uint32_t serial, const char *id); + static void requestCreateVirtualDesktopCallback(wl_client *client, wl_resource *resource, const char *name, uint32_t position); + static void requestRemoveVirtualDesktopCallback(wl_client *client, wl_resource *resource, const char *id); + + PlasmaVirtualDesktopManagementV1Interface *q; + + static const struct org_kde_plasma_virtual_desktop_management_v1_interface s_interface; + static const quint32 s_version; +}; + +const quint32 PlasmaVirtualDesktopManagementV1Interface::Private::s_version = 1; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct org_kde_plasma_virtual_desktop_management_v1_interface PlasmaVirtualDesktopManagementV1Interface::Private::s_interface = { + getVirtualDesktopCallback, + requestCreateVirtualDesktopCallback, + requestRemoveVirtualDesktopCallback +}; +#endif + +inline QList::const_iterator PlasmaVirtualDesktopManagementV1Interface::Private::constFindDesktop(const QString &id) +{ + return std::find_if( desktops.constBegin(), + desktops.constEnd(), + [id]( const PlasmaVirtualDesktopV1Interface *desk ){ return desk->id() == id; } ); +} + +inline QList::iterator PlasmaVirtualDesktopManagementV1Interface::Private::findDesktop(const QString &id) +{ + return std::find_if( desktops.begin(), + desktops.end(), + [id]( const PlasmaVirtualDesktopV1Interface *desk ){ return desk->id() == id; } ); +} + +void PlasmaVirtualDesktopManagementV1Interface::Private::getVirtualDesktopCallback(wl_client *client, wl_resource *resource, uint32_t serial, const char *id) +{ + Q_UNUSED(client) + auto s = cast(resource); + + auto i = s->constFindDesktop(QString::fromUtf8(id)); + if (i == s->desktops.constEnd()) { + return; + } + + (*i)->d->createResource(resource, serial); +} + +void PlasmaVirtualDesktopManagementV1Interface::Private::requestCreateVirtualDesktopCallback(wl_client *client, wl_resource *resource, const char *name, uint32_t position) +{ + Q_UNUSED(client) + auto s = cast(resource); + emit s->q->desktopCreateRequested(QString::fromUtf8(name), qBound(0, position, (quint32)s->desktops.count())); +} + +void PlasmaVirtualDesktopManagementV1Interface::Private::requestRemoveVirtualDesktopCallback(wl_client *client, wl_resource *resource, const char *id) +{ + Q_UNUSED(client) + auto s = cast(resource); + emit s->q->desktopRemoveRequested(QString::fromUtf8(id)); +} + +PlasmaVirtualDesktopManagementV1Interface::Private::Private(PlasmaVirtualDesktopManagementV1Interface *q, Display *d) + : Global::Private(d, &org_kde_plasma_virtual_desktop_management_v1_interface, s_version) + , q(q) +{ +} + +void PlasmaVirtualDesktopManagementV1Interface::Private::bind(wl_client *client, uint32_t version, uint32_t id) +{ + auto c = display->getConnection(client); + wl_resource *resource = c->createResource(&org_kde_plasma_virtual_desktop_management_v1_interface, qMin(version, s_version), id); + + if (!resource) { + wl_client_post_no_memory(client); + return; + } + resources << resource; + + wl_resource_set_implementation(resource, &s_interface, this, unbind); + + quint32 i = 0; + for (auto it = desktops.constBegin(); it != desktops.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_management_v1_send_desktop_created(resource, (*it)->id().toUtf8().constData(), i++); + } + org_kde_plasma_virtual_desktop_management_v1_send_done(resource); +} + +void PlasmaVirtualDesktopManagementV1Interface::Private::unbind(wl_resource *resource) +{ + auto dm = reinterpret_cast(wl_resource_get_user_data(resource)); + dm->resources.removeAll(resource); +} + +PlasmaVirtualDesktopManagementV1Interface::PlasmaVirtualDesktopManagementV1Interface(Display *display, QObject *parent) + : Global(new Private(this, display), parent) +{ +} + +PlasmaVirtualDesktopManagementV1Interface::~PlasmaVirtualDesktopManagementV1Interface() +{} + +PlasmaVirtualDesktopManagementV1Interface::Private *PlasmaVirtualDesktopManagementV1Interface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +PlasmaVirtualDesktopV1Interface *PlasmaVirtualDesktopManagementV1Interface::desktop(const QString &id) +{ + Q_D(); + auto i = d->constFindDesktop(id); + if (i != d->desktops.constEnd()) { + return *i; + } + return nullptr; +} + +PlasmaVirtualDesktopV1Interface *PlasmaVirtualDesktopManagementV1Interface::createDesktop(const QString &id, quint32 position) +{ + Q_D(); + auto i = d->constFindDesktop(id); + if (i != d->desktops.constEnd()) { + return *i; + } + + const quint32 actualPosition = qMin(position, (quint32)d->desktops.count()); + + PlasmaVirtualDesktopV1Interface *desktop = new PlasmaVirtualDesktopV1Interface(this); + desktop->d->id = id; + for (auto it = desktop->d->resources.constBegin(); it != desktop->d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_v1_send_desktop_id(*it, id.toUtf8().constData()); + } + + //activate the first desktop TODO: to be done here? + if (d->desktops.isEmpty()) { + desktop->d->active = true; + } + + d->desktops.insert(actualPosition, desktop); + //NOTE: this in case the desktop has been deleted but not trough removedesktop + connect(desktop, &QObject::destroyed, this, + [this, id] { + Q_D(); + auto i = d->findDesktop(id); + if (i != d->desktops.end()) { + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_management_v1_send_desktop_removed(*it, id.toUtf8().constData()); + } + + d->desktops.erase(i); + } + } + ); + + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_management_v1_send_desktop_created(*it, id.toUtf8().constData(), actualPosition); + } + + return desktop; +} + +void PlasmaVirtualDesktopManagementV1Interface::removeDesktop(const QString &id) +{ + Q_D(); + auto deskIt = d->findDesktop(id); + if (deskIt == d->desktops.end()) { + return; + } + + for (auto it = (*deskIt)->d->resources.constBegin(); it != (*deskIt)->d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_v1_send_removed(*it); + } + + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_management_v1_send_desktop_removed(*it, id.toUtf8().constData()); + } + + d->desktops.erase(deskIt); + (*deskIt)->deleteLater(); +} + +QList PlasmaVirtualDesktopManagementV1Interface::desktops() const +{ + Q_D(); + return d->desktops; +} + +void PlasmaVirtualDesktopManagementV1Interface::sendDone() +{ + Q_D(); + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_management_v1_send_done(*it); + } +} + +//// PlasmaVirtualDesktopInterfaceUnstableV1 + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct org_kde_plasma_virtual_desktop_v1_interface PlasmaVirtualDesktopV1Interface::Private::s_interface = { + requestActivateCallback +}; +#endif + +void PlasmaVirtualDesktopV1Interface::Private::requestActivateCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + auto s = cast(resource); + emit s->q->activateRequested(); +} + +PlasmaVirtualDesktopV1Interface::Private::Private(PlasmaVirtualDesktopV1Interface *q, PlasmaVirtualDesktopManagementV1Interface *c) + : q(q), + vdm(c) +{ +} + +PlasmaVirtualDesktopV1Interface::Private::~Private() +{ + // need to copy, as destroy goes through the destroy listener and modifies the list as we iterate + const auto c = resources; + for (const auto &r : c) { + auto client = wl_resource_get_client(r); + org_kde_plasma_virtual_desktop_v1_send_removed(r); + wl_resource_destroy(r); + wl_client_flush(client); + } +} + +void PlasmaVirtualDesktopV1Interface::Private::unbind(wl_resource *resource) +{ + Private *p = reinterpret_cast(wl_resource_get_user_data(resource)); + p->resources.removeAll(resource); +} + +void PlasmaVirtualDesktopV1Interface::Private::createResource(wl_resource *parent, quint32 serial) +{ + ClientConnection *c = vdm->display()->getConnection(wl_resource_get_client(parent)); + wl_resource *resource = c->createResource(&org_kde_plasma_virtual_desktop_v1_interface, wl_resource_get_version(parent), serial); + if (!resource) { + return; + } + wl_resource_set_implementation(resource, &s_interface, this, unbind); + resources << resource; + + org_kde_plasma_virtual_desktop_v1_send_desktop_id(resource, id.toUtf8().constData()); + if (!name.isEmpty()) { + org_kde_plasma_virtual_desktop_v1_send_name(resource, name.toUtf8().constData()); + } + + if (active) { + org_kde_plasma_virtual_desktop_v1_send_activated(resource); + } + + c->flush(); +} + +PlasmaVirtualDesktopV1Interface::PlasmaVirtualDesktopV1Interface(PlasmaVirtualDesktopManagementV1Interface *parent) + : QObject(parent), + d(new Private(this, parent)) +{ +} + +PlasmaVirtualDesktopV1Interface::~PlasmaVirtualDesktopV1Interface() +{} + +QString PlasmaVirtualDesktopV1Interface::id() const +{ + return d->id; +} + +void PlasmaVirtualDesktopV1Interface::setName(const QString &name) +{ + if (d->name == name) { + return; + } + + d->name = name; + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_v1_send_name(*it, name.toUtf8().constData()); + } +} + +QString PlasmaVirtualDesktopV1Interface::name() const +{ + return d->name; +} + +void PlasmaVirtualDesktopV1Interface::setActive(bool active) +{ + if (d->active == active) { + return; + } + + d->active = active; + if (active) { + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_v1_send_activated(*it); + } + } else { + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_v1_send_deactivated(*it); + } + } +} + +bool PlasmaVirtualDesktopV1Interface::isActive() const +{ + return d->active; +} + +void PlasmaVirtualDesktopV1Interface::sendDone() +{ + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_virtual_desktop_v1_send_done(*it); + } +} + +} +} + diff --git a/src/server/plasmawindowmanagement_interface.h b/src/server/plasmawindowmanagement_interface.h --- a/src/server/plasmawindowmanagement_interface.h +++ b/src/server/plasmawindowmanagement_interface.h @@ -37,6 +37,7 @@ class Display; class PlasmaWindowInterface; class SurfaceInterface; +class PlasmaVirtualDesktopManagementInterface; /** * @todo Add documentation @@ -72,6 +73,20 @@ **/ void unmapWindow(PlasmaWindowInterface *window); + /** + * Associate a PlasmaVirtualDesktopManagementInterface to this window management. + * It's necessary to associate one in orderto use the plasma virtual desktop features + * of PlasmaWindowInterface, as a window must know what are the deasktops available + * @since 5.48 + */ + void setPlasmaVirtualDesktopManagementInterface(PlasmaVirtualDesktopManagementInterface *manager); + + /** + * @returns the PlasmaVirtualDesktopManagementInterface associated to this PlasmaWindowManagementInterface + * @since 5.48 + */ + PlasmaVirtualDesktopManagementInterface *plasmaVirtualDesktopManagementInterface() const; + Q_SIGNALS: void requestChangeShowingDesktop(ShowingDesktopState requestedState); @@ -94,7 +109,12 @@ void setTitle(const QString &title); void setAppId(const QString &appId); void setPid(quint32 pid); - void setVirtualDesktop(quint32 desktop); +#ifndef KWAYLANDSERVER_NO_DEPRECATED + /** + * @deprecated use addPlasmaVirtualDesktop and removePlasmaVirtualDesktop + */ + void KWAYLANDSERVER_DEPRECATED setVirtualDesktop(quint32 desktop); +#endif void setActive(bool set); void setMinimized(bool set); void setMaximized(bool set); @@ -133,6 +153,7 @@ */ void setResizable(bool set); /** + * FIXME: still relevant with new desktops? * @since 5.22 */ void setVirtualDesktopChangeable(bool set); @@ -182,6 +203,30 @@ **/ void setIcon(const QIcon &icon); + /** + * Adds a new desktop to this window: a window can be on + * an arbitrary subset of virtual desktops. + * If it's on none it will be considered on all desktops. + * + * @since 5.48 + */ + void addPlasmaVirtualDesktop(const QString &id); + + /** + * Removes a visrtual desktop from a window + * + * @since 5.48 + */ + void removePlasmaVirtualDesktop(const QString &id); + + /** + * The ids of all the desktops currently associated with this window. + * When a desktop is deleted it will be automatically removed from this list + * + * @since 5.48 + */ + QStringList plasmaVirtualDesktops() const; + Q_SIGNALS: void closeRequested(); /** @@ -192,7 +237,12 @@ * @since 5.22 */ void resizeRequested(); - void virtualDesktopRequested(quint32 desktop); +#ifndef KWAYLANDSERVER_NO_DEPRECATED + /** + * @deprecated use enterPlasmaVirtualDesktopRequested and leavePlasmaVirtualDesktopRequested instead + */ + void KWAYLANDSERVER_DEPRECATED virtualDesktopRequested(quint32 desktop); +#endif void activeRequested(bool set); void minimizedRequested(bool set); void maximizedRequested(bool set); @@ -224,10 +274,33 @@ */ void resizableRequested(bool set); /** + * FIXME: still relevant with new virtual desktops? * @since 5.22 */ void virtualDesktopChangeableRequested(bool set); + /** + * Emitted when the client wishes this window to enter in a new virtual desktop. + * The server will decide whether to consent this request + * @since 5.48 + */ + void enterPlasmaVirtualDesktopRequested(const QString &desktop); + + /** + * Emitted when the client wishes this window to enter in + * a new virtual desktop to be created for it. + * The server will decide whether to consent this request + * @since 5.48 + */ + void enterNewPlasmaVirtualDesktopRequested(); + + /** + * Emitted when the client wishes to remove this window from a virtual desktop. + * The server will decide whether to consent this request + * @since 5.48 + */ + void leavePlasmaVirtualDesktopRequested(const QString &desktop); + private: friend class PlasmaWindowManagementInterface; explicit PlasmaWindowInterface(PlasmaWindowManagementInterface *wm, QObject *parent); diff --git a/src/server/plasmawindowmanagement_interface.cpp b/src/server/plasmawindowmanagement_interface.cpp --- a/src/server/plasmawindowmanagement_interface.cpp +++ b/src/server/plasmawindowmanagement_interface.cpp @@ -22,6 +22,7 @@ #include "resource_p.h" #include "display.h" #include "surface_interface.h" +#include "plasmavirtualdesktop_interface.h" #include #include @@ -48,6 +49,7 @@ ShowingDesktopState state = ShowingDesktopState::Disabled; QVector resources; QList windows; + QPointer plasmaVirtualDesktopManagementInterface = nullptr; quint32 windowIdCounter = 0; private: @@ -90,6 +92,7 @@ bool unmapped = false; PlasmaWindowInterface *parentWindow = nullptr; QMetaObject::Connection parentWindowDestroyConnection; + QStringList plasmaVirtualDesktops; QRect geometry; private: @@ -103,6 +106,9 @@ static void unsetMinimizedGeometryCallback(wl_client *client, wl_resource *resource, wl_resource *panel); static void destroyCallback(wl_client *client, wl_resource *resource); static void getIconCallback(wl_client *client, wl_resource *resource, int32_t fd); + static void requestEnterVirtualDesktopCallback(wl_client *client, wl_resource *resource, const char *id); + static void requestEnterNewVirtualDesktopCallback(wl_client *client, wl_resource *resource); + static void requestLeaveVirtualDesktopCallback(wl_client *client, wl_resource *resource, const char *id); static Private *cast(wl_resource *resource) { return reinterpret_cast(wl_resource_get_user_data(resource)); } @@ -255,6 +261,12 @@ return window; } +QList PlasmaWindowManagementInterface::windows() const +{ + Q_D(); + return d->windows; +} + void PlasmaWindowManagementInterface::unmapWindow(PlasmaWindowInterface *window) { if (!window) { @@ -266,6 +278,21 @@ window->d->unmap(); } +void PlasmaWindowManagementInterface::setPlasmaVirtualDesktopManagementInterface(PlasmaVirtualDesktopManagementInterface *manager) +{ + Q_D(); + if (d->plasmaVirtualDesktopManagementInterface == manager) { + return; + } + d->plasmaVirtualDesktopManagementInterface = manager; +} + +PlasmaVirtualDesktopManagementInterface *PlasmaWindowManagementInterface::plasmaVirtualDesktopManagementInterface() const +{ + Q_D(); + return d->plasmaVirtualDesktopManagementInterface; +} + #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct org_kde_plasma_window_interface PlasmaWindowInterface::Private::s_interface = { setStateCallback, @@ -276,7 +303,10 @@ requestMoveCallback, requestResizeCallback, destroyCallback, - getIconCallback + getIconCallback, + requestEnterVirtualDesktopCallback, + requestEnterNewVirtualDesktopCallback, + requestLeaveVirtualDesktopCallback }; #endif @@ -328,6 +358,9 @@ resources << resource; org_kde_plasma_window_send_virtual_desktop_changed(resource, m_virtualDesktop); + for (const auto &desk : plasmaVirtualDesktops) { + org_kde_plasma_window_send_virtual_desktop_entered(resource, desk.toUtf8().constData()); + } if (!m_appId.isEmpty()) { org_kde_plasma_window_send_app_id_changed(resource, m_appId.toUtf8().constData()); } @@ -425,6 +458,27 @@ ); } +void PlasmaWindowInterface::Private::requestEnterVirtualDesktopCallback(wl_client *client, wl_resource *resource, const char *id) +{ + Q_UNUSED(client) + Private *p = cast(resource); + emit p->q->enterPlasmaVirtualDesktopRequested(QString::fromUtf8(id)); +} + +void PlasmaWindowInterface::Private::requestEnterNewVirtualDesktopCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + Private *p = cast(resource); + emit p->q->enterNewPlasmaVirtualDesktopRequested(); +} + +void PlasmaWindowInterface::Private::requestLeaveVirtualDesktopCallback(wl_client *client, wl_resource *resource, const char *id) +{ + Q_UNUSED(client) + Private *p = cast(resource); + emit p->q->leavePlasmaVirtualDesktopRequested(QString::fromUtf8(id)); +} + void PlasmaWindowInterface::Private::setTitle(const QString &title) { if (m_title == title) { @@ -684,10 +738,12 @@ d->setTitle(title); } +#ifndef KWAYLANDSERVER_NO_DEPRECATED void PlasmaWindowInterface::setVirtualDesktop(quint32 desktop) { d->setVirtualDesktop(desktop); } +#endif void PlasmaWindowInterface::unmap() { @@ -731,7 +787,39 @@ void PlasmaWindowInterface::setOnAllDesktops(bool set) { + //the deprecated vd management d->setState(ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_ON_ALL_DESKTOPS, set); + + if (!d->wm->plasmaVirtualDesktopManagementInterface()) { + return; + } + + //the current vd management + if (set) { + if (d->plasmaVirtualDesktops.isEmpty()) { + return; + } + //leaving everything means on all desktops + for (auto desk : plasmaVirtualDesktops()) { + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_window_send_virtual_desktop_left(*it, desk.toUtf8().constData()); + } + } + d->plasmaVirtualDesktops.clear(); + } else { + if (!d->plasmaVirtualDesktops.isEmpty()) { + return; + } + //enters the desktops which are active (usually only one but not a given) + for (auto desk : d->wm->plasmaVirtualDesktopManagementInterface()->desktops()) { + if (desk->isActive() && !d->plasmaVirtualDesktops.contains(desk->id())) { + d->plasmaVirtualDesktops << desk->id(); + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_window_send_virtual_desktop_entered(*it, desk->id().toUtf8().constData()); + } + } + } + } } void PlasmaWindowInterface::setDemandsAttention(bool set) @@ -781,6 +869,58 @@ d->setIcon(icon); } +void PlasmaWindowInterface::addPlasmaVirtualDesktop(const QString &id) +{ + //don't add a desktop we're not sure it exists + if (!d->wm->plasmaVirtualDesktopManagementInterface() || d->plasmaVirtualDesktops.contains(id)) { + return; + } + + PlasmaVirtualDesktopInterface *desktop = d->wm->plasmaVirtualDesktopManagementInterface()->desktop(id); + + if (!desktop) { + return; + } + + //full? lets set it on all desktops, the plasmaVirtualDesktops list will get empty, which means it's on all desktops + if (d->wm->plasmaVirtualDesktopManagementInterface()->desktops().count() == d->plasmaVirtualDesktops.count() + 1) { + setOnAllDesktops(true); + return; + } + + d->plasmaVirtualDesktops << id; + + //if the desktop dies, remove it from or list + connect(desktop, &QObject::destroyed, + this, [this, id](){removePlasmaVirtualDesktop(id);}); + + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_window_send_virtual_desktop_entered(*it, id.toUtf8().constData()); + } +} + +void PlasmaWindowInterface::removePlasmaVirtualDesktop(const QString &id) +{ + if (!d->plasmaVirtualDesktops.contains(id)) { + return; + } + + d->plasmaVirtualDesktops.removeAll(id); + for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { + org_kde_plasma_window_send_virtual_desktop_left(*it, id.toUtf8().constData()); + } + + //we went on all desktops + if (d->plasmaVirtualDesktops.isEmpty()) { + setOnAllDesktops(true); + } +} + +QStringList PlasmaWindowInterface::plasmaVirtualDesktops() const +{ + return d->plasmaVirtualDesktops; +} + void PlasmaWindowInterface::setShadeable(bool set) { d->setState(ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_SHADEABLE, set); diff --git a/src/server/surface_interface.cpp b/src/server/surface_interface.cpp --- a/src/server/surface_interface.cpp +++ b/src/server/surface_interface.cpp @@ -181,93 +181,89 @@ Q_ASSERT(lockedPointer.isNull()); Q_ASSERT(confinedPointer.isNull()); lockedPointer = QPointer(lock); + + auto cleanUp = [this]() { + lockedPointer.clear(); + disconnect(constrainsOneShotConnection); + constrainsOneShotConnection = QMetaObject::Connection(); + disconnect(constrainsUnboundConnection); + constrainsUnboundConnection = QMetaObject::Connection(); + emit q_func()->pointerConstraintsChanged(); + }; + if (lock->lifeTime() == LockedPointerInterface::LifeTime::OneShot) { constrainsOneShotConnection = QObject::connect(lock, &LockedPointerInterface::lockedChanged, q_func(), - [this] { - if (lockedPointer.isNull()) { + [this, cleanUp] { + if (lockedPointer.isNull() || lockedPointer->isLocked()) { return; } - if (!lockedPointer->isLocked()) { - lockedPointer.clear(); - disconnect(constrainsOneShotConnection); - constrainsOneShotConnection = QMetaObject::Connection(); - disconnect(constrainsUnboundConnection); - constrainsUnboundConnection = QMetaObject::Connection(); - emit q_func()->pointerConstraintsChanged(); - } + cleanUp(); } ); } constrainsUnboundConnection = QObject::connect(lock, &LockedPointerInterface::unbound, q_func(), - [this] { + [this, cleanUp] { if (lockedPointer.isNull()) { return; } - lockedPointer.clear(); - disconnect(constrainsOneShotConnection); - constrainsOneShotConnection = QMetaObject::Connection(); - disconnect(constrainsUnboundConnection); - constrainsUnboundConnection = QMetaObject::Connection(); - emit q_func()->pointerConstraintsChanged(); + cleanUp(); } ); emit q_func()->pointerConstraintsChanged(); } -void SurfaceInterface::Private::installIdleInhibitor(IdleInhibitorInterface *inhibitor) -{ - idleInhibitors << inhibitor; - QObject::connect(inhibitor, &IdleInhibitorInterface::aboutToBeUnbound, q, - [this, inhibitor] { - idleInhibitors.removeOne(inhibitor); - if (idleInhibitors.isEmpty()) { - emit q_func()->inhibitsIdleChanged(); - } - } - ); - if (idleInhibitors.count() == 1) { - emit q_func()->inhibitsIdleChanged(); - } -} - void SurfaceInterface::Private::installPointerConstraint(ConfinedPointerInterface *confinement) { Q_ASSERT(lockedPointer.isNull()); Q_ASSERT(confinedPointer.isNull()); confinedPointer = QPointer(confinement); + + auto cleanUp = [this]() { + confinedPointer.clear(); + disconnect(constrainsOneShotConnection); + constrainsOneShotConnection = QMetaObject::Connection(); + disconnect(constrainsUnboundConnection); + constrainsUnboundConnection = QMetaObject::Connection(); + emit q_func()->pointerConstraintsChanged(); + }; + if (confinement->lifeTime() == ConfinedPointerInterface::LifeTime::OneShot) { constrainsOneShotConnection = QObject::connect(confinement, &ConfinedPointerInterface::confinedChanged, q_func(), - [this] { - if (confinedPointer.isNull()) { + [this, cleanUp] { + if (confinedPointer.isNull() || confinedPointer->isConfined()) { return; } - if (!confinedPointer->isConfined()) { - confinedPointer.clear(); - disconnect(constrainsOneShotConnection); - constrainsOneShotConnection = QMetaObject::Connection(); - disconnect(constrainsUnboundConnection); - constrainsUnboundConnection = QMetaObject::Connection(); - emit q_func()->pointerConstraintsChanged(); - } + cleanUp(); } ); } constrainsUnboundConnection = QObject::connect(confinement, &ConfinedPointerInterface::unbound, q_func(), - [this] { + [this, cleanUp] { if (confinedPointer.isNull()) { return; } - confinedPointer.clear(); - disconnect(constrainsOneShotConnection); - constrainsOneShotConnection = QMetaObject::Connection(); - disconnect(constrainsUnboundConnection); - constrainsUnboundConnection = QMetaObject::Connection(); - emit q_func()->pointerConstraintsChanged(); + cleanUp(); } ); emit q_func()->pointerConstraintsChanged(); } +void SurfaceInterface::Private::installIdleInhibitor(IdleInhibitorInterface *inhibitor) +{ + idleInhibitors << inhibitor; + QObject::connect(inhibitor, &IdleInhibitorInterface::aboutToBeUnbound, q, + [this, inhibitor] { + idleInhibitors.removeOne(inhibitor); + if (idleInhibitors.isEmpty()) { + emit q_func()->inhibitsIdleChanged(); + } + } + ); + if (idleInhibitors.count() == 1) { + emit q_func()->inhibitsIdleChanged(); + } +} + #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_surface_interface SurfaceInterface::Private::s_interface = { resourceDestroyedCallback, diff --git a/src/server/xdgforeign_v2_interface.cpp b/src/server/xdgforeign_v2_interface.cpp --- a/src/server/xdgforeign_v2_interface.cpp +++ b/src/server/xdgforeign_v2_interface.cpp @@ -95,6 +95,7 @@ void XdgExporterUnstableV2Interface::Private::destroyCallback(wl_client *client, wl_resource *resource) { Q_UNUSED(client) + Q_UNUSED(resource) } void XdgExporterUnstableV2Interface::Private::exportCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * surface) @@ -237,6 +238,7 @@ void XdgImporterUnstableV2Interface::Private::destroyCallback(wl_client *client, wl_resource *resource) { Q_UNUSED(client) + Q_UNUSED(resource) } void XdgImporterUnstableV2Interface::Private::importCallback(wl_client *client, wl_resource *resource, uint32_t id, const char *h) @@ -438,6 +440,8 @@ void XdgImportedUnstableV2Interface::Private::setParentOfCallback(wl_client *client, wl_resource *resource, wl_resource * surface) { + Q_UNUSED(client) + auto s = cast(resource); SurfaceInterface *surf = SurfaceInterface::get(surface); diff --git a/src/server/xdgoutput_interface.h b/src/server/xdgoutput_interface.h new file mode 100644 --- /dev/null +++ b/src/server/xdgoutput_interface.h @@ -0,0 +1,121 @@ +/**************************************************************************** +Copyright 2018 David Edmundson + +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 . +****************************************************************************/ +#ifndef KWAYLAND_SERVER_XDGOUTPUT_H +#define KWAYLAND_SERVER_XDGOUTPUT_H + +#include "global.h" +#include "resource.h" + + +#include + + +/* + * In terms of protocol XdgOutputInterface are a resource + * but for the sake of sanity, we should treat XdgOutputs as globals like Output is + * Hence this doesn't match most of kwayland API paradigms. + */ + +namespace KWayland +{ +namespace Server +{ + +class Display; +class OutputInterface; +class XdgOutputInterface; + +/** + * Global manager for XdgOutputs + * @since 5.47 + */ +class KWAYLANDSERVER_EXPORT XdgOutputManagerInterface : public Global +{ + Q_OBJECT +public: + virtual ~XdgOutputManagerInterface(); + /** + * Creates an XdgOutputInterface object for an existing Output + * which exposes XDG specific properties of outputs + * + * @arg output the wl_output interface this XDG output is for + * @parent the parent of the newly created object + */ + XdgOutputInterface* createXdgOutput(OutputInterface *output, QObject *parent); +private: + explicit XdgOutputManagerInterface(Display *display, QObject *parent = nullptr); + friend class Display; + class Private; + Private *d_func() const; +}; + +/** + * Extension to Output + * Users should set all relevant values on creation and on future changes. + * done() should be explicitly called after change batches including initial setting. + * @since 5.47 + */ +class KWAYLANDSERVER_EXPORT XdgOutputInterface : public QObject +{ + Q_OBJECT +public: + virtual ~XdgOutputInterface(); + + /** + * Sets the size of this output in logical co-ordinates. + * Users should call done() after setting all values + */ + void setLogicalSize(const QSize &size); + + /** + * Returns the last set logical size on this output + */ + QSize logicalSize() const; + + /** + * Sets the topleft position of this output in logical co-ordinates. + * Users should call done() after setting all values + * @see OutputInterface::setPosition + */ + void setLogicalPosition(const QPoint &pos); + + /** + * Returns the last set logical position on this output + */ + QPoint logicalPosition() const; + + /** + * Submit changes to all clients + */ + void done(); + +private: + explicit XdgOutputInterface(QObject *parent); + friend class XdgOutputManagerInterface; + + class Private; + QScopedPointer d; +}; + + +} +} + +#endif diff --git a/src/server/xdgoutput_interface.cpp b/src/server/xdgoutput_interface.cpp new file mode 100644 --- /dev/null +++ b/src/server/xdgoutput_interface.cpp @@ -0,0 +1,307 @@ +/**************************************************************************** +Copyright 2018 David Edmundson + +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 "xdgoutput_interface.h" +#include "display.h" +#include "global_p.h" +#include "resource_p.h" +#include "output_interface.h" + +#include + +namespace KWayland +{ +namespace Server +{ + +class XdgOutputManagerInterface::Private : public Global::Private +{ +public: + Private(XdgOutputManagerInterface *q, Display *d); + QHash outputs; + +private: + void bind(wl_client *client, uint32_t version, uint32_t id) override; + + static void unbind(wl_resource *resource); + static Private *cast(wl_resource *r) { + return reinterpret_cast(wl_resource_get_user_data(r)); + } + + static void destroyCallback(wl_client *client, wl_resource *resource); + static void getXdgOutputCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * output); + + XdgOutputManagerInterface *q; + static const struct zxdg_output_manager_v1_interface s_interface; + static const quint32 s_version; +}; + +const quint32 XdgOutputManagerInterface::Private::s_version = 1; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zxdg_output_manager_v1_interface XdgOutputManagerInterface::Private::s_interface = { + destroyCallback, + getXdgOutputCallback +}; +#endif + +class XdgOutputV1Interface: public Resource +{ +public: + XdgOutputV1Interface(XdgOutputManagerInterface *parent, wl_resource *parentResource); + ~XdgOutputV1Interface(); + void setLogicalSize(const QSize &size); + void setLogicalPosition(const QPoint &pos); + void done(); +private: + class Private; +}; + +class XdgOutputInterface::Private +{ +public: + void resourceConnected(XdgOutputV1Interface *resource); + void resourceDisconnected(XdgOutputV1Interface *resource); + QPoint pos; + QSize size; + bool doneOnce = false; + QList resources; +}; + + +XdgOutputManagerInterface::XdgOutputManagerInterface(Display *display, QObject *parent) + : Global(new XdgOutputManagerInterface::Private(this, display), parent) +{ +} + +XdgOutputManagerInterface::~XdgOutputManagerInterface() +{} + +XdgOutputInterface* XdgOutputManagerInterface::createXdgOutput(OutputInterface *output, QObject *parent) +{ + Q_D(); + if (!d->outputs.contains(output)) { + auto xdgOutput = new XdgOutputInterface(parent); + d->outputs[output] = xdgOutput; + //as XdgOutput lifespan is managed by user, delete our mapping when either + //it or the relevant Output gets deleted + connect(output, &QObject::destroyed, this, [this, output]() { + Q_D(); + d->outputs.remove(output); + }); + connect(xdgOutput, &QObject::destroyed, this, [this, output]() { + Q_D(); + d->outputs.remove(output); + }); + + } + return d->outputs[output]; +} + +XdgOutputManagerInterface::Private* XdgOutputManagerInterface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +void XdgOutputManagerInterface::Private::destroyCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + wl_resource_destroy(resource); +} + +void XdgOutputManagerInterface::Private::getXdgOutputCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * outputResource) +{ + auto d = cast(resource); + auto output = OutputInterface::get(outputResource); + if (!output) { // output client is requesting XdgOutput for an Output that doesn't exist + return; + } + if (!d->outputs.contains(output)) { + return; //server hasn't created an XdgOutput for this output yet, give the client nothing + } + auto iface = new XdgOutputV1Interface(d->q, resource); + iface->create(d->display->getConnection(client), wl_resource_get_version(resource), id); + if (!iface->resource()) { + wl_resource_post_no_memory(resource); + delete iface; + return; + } + + auto xdgOutput = d->outputs[output]; + xdgOutput->d->resourceConnected(iface); + connect(iface, &XdgOutputV1Interface::unbound, xdgOutput, [xdgOutput, iface]() { + xdgOutput->d->resourceDisconnected(iface); + }); +} + +XdgOutputManagerInterface::Private::Private(XdgOutputManagerInterface *q, Display *d) + : Global::Private(d, &zxdg_output_manager_v1_interface, s_version) + , q(q) +{ +} + +void XdgOutputManagerInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id) +{ + auto c = display->getConnection(client); + wl_resource *resource = c->createResource(&zxdg_output_manager_v1_interface, qMin(version, s_version), id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &s_interface, this, unbind); +} + +void XdgOutputManagerInterface::Private::unbind(wl_resource *resource) +{ + Q_UNUSED(resource) +} + +XdgOutputInterface::XdgOutputInterface(QObject *parent): + QObject(parent), + d(new XdgOutputInterface::Private) +{ +} + +XdgOutputInterface::~XdgOutputInterface() +{} + +void XdgOutputInterface::setLogicalSize(const QSize &size) +{ + if (size == d->size) { + return; + } + d->size = size; + for(auto resource: d->resources) { + resource->setLogicalSize(size); + } +} + +QSize XdgOutputInterface::logicalSize() const +{ + return d->size; +} + +void XdgOutputInterface::setLogicalPosition(const QPoint &pos) +{ + if (pos == d->pos) { + return; + } + d->pos = pos; + for(auto resource: d->resources) { + resource->setLogicalPosition(pos); + } +} + +QPoint XdgOutputInterface::logicalPosition() const +{ + return d->pos; +} + +void XdgOutputInterface::done() +{ + d->doneOnce = true; + for(auto resource: d->resources) { + resource->done(); + } +} + +void XdgOutputInterface::Private::resourceConnected(XdgOutputV1Interface *resource) +{ + resource->setLogicalPosition(pos); + resource->setLogicalSize(size); + if (doneOnce) { + resource->done(); + } + resources << resource; +} + +void XdgOutputInterface::Private::resourceDisconnected(XdgOutputV1Interface *resource) +{ + resources.removeOne(resource); +} + + +class XdgOutputV1Interface::Private : public Resource::Private +{ +public: + Private(XdgOutputV1Interface *q, XdgOutputManagerInterface *c, wl_resource *parentResource); + ~Private(); + +private: + + XdgOutputV1Interface *q_func() { + return reinterpret_cast(q); + } + + static const struct zxdg_output_v1_interface s_interface; +}; + +XdgOutputV1Interface::XdgOutputV1Interface(XdgOutputManagerInterface *parent, wl_resource *parentResource) + :Resource(new XdgOutputV1Interface::Private(this, parent, parentResource)) +{} + +XdgOutputV1Interface::~XdgOutputV1Interface() +{} + +void XdgOutputV1Interface::setLogicalSize(const QSize &size) +{ + if (!d->resource) { + return; + } + zxdg_output_v1_send_logical_size(d->resource, size.width(), size.height()); +} + +void XdgOutputV1Interface::setLogicalPosition(const QPoint &pos) +{ + if (!d->resource) { + return; + } + zxdg_output_v1_send_logical_position(d->resource, pos.x(), pos.y()); +} + +void XdgOutputV1Interface::done() +{ + if (!d->resource) { + return; + } + zxdg_output_v1_send_done(d->resource); +} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zxdg_output_v1_interface XdgOutputV1Interface::Private::s_interface = { + resourceDestroyedCallback +}; +#endif + +XdgOutputV1Interface::Private::Private(XdgOutputV1Interface *q, XdgOutputManagerInterface *c, wl_resource *parentResource) + : Resource::Private(q, c, parentResource, &zxdg_output_v1_interface, &s_interface) +{ +} + +XdgOutputV1Interface::Private::~Private() +{ + if (resource) { + wl_resource_destroy(resource); + resource = nullptr; + } +} + +} +} + diff --git a/src/tools/mapping.txt b/src/tools/mapping.txt --- a/src/tools/mapping.txt +++ b/src/tools/mapping.txt @@ -36,6 +36,8 @@ org_kde_kwin_shadow_manager;ShadowManager org_kde_plasma_shell;PlasmaShell org_kde_plasma_surface;PlasmaShellSurface +org_kde_plasma_virtual_desktop_management;PlasmaVirtualDesktopManagement +org_kde_plasma_virtual_desktop;PlasmaVirtualDesktop org_kde_plasma_window_management;PlasmaWindowManagement org_kde_plasma_window;PlasmaWindow org_kde_kwin_server_decoration_manager;ServerSideDecorationManager diff --git a/tests/plasmasurfacetest.cpp b/tests/plasmasurfacetest.cpp --- a/tests/plasmasurfacetest.cpp +++ b/tests/plasmasurfacetest.cpp @@ -49,6 +49,10 @@ m_skipTaskbar = set; } + void setSkipSwitcher(bool set) { + m_skipSwitcher = set; + } + private: void setupRegistry(Registry *registry); void render(); @@ -64,6 +68,7 @@ PlasmaShellSurface *m_plasmaShellSurface = nullptr; PlasmaShellSurface::Role m_role = PlasmaShellSurface::Role::Normal; bool m_skipTaskbar = false; + bool m_skipSwitcher = false; }; PlasmaSurfaceTest::PlasmaSurfaceTest(QObject *parent) @@ -136,6 +141,7 @@ m_plasmaShellSurface = m_plasmaShell->createSurface(m_surface, this); Q_ASSERT(m_plasmaShellSurface); m_plasmaShellSurface->setSkipTaskbar(m_skipTaskbar); + m_plasmaShellSurface->setSkipSwitcher(m_skipSwitcher); m_plasmaShellSurface->setRole(m_role); render(); } @@ -177,6 +183,9 @@ QCommandLineOption skipTaskbarOption(QStringLiteral("skipTaskbar")); parser.addOption(skipTaskbarOption); parser.process(app); + QCommandLineOption skipSwitcherOption(QStringLiteral("skipSwitcher")); + parser.addOption(skipSwitcherOption); + parser.process(app); PlasmaSurfaceTest client; @@ -192,6 +201,7 @@ client.setRole(PlasmaShellSurface::Role::ToolTip); } client.setSkipTaskbar(parser.isSet(skipTaskbarOption)); + client.setSkipSwitcher(parser.isSet(skipSwitcherOption)); client.init();