diff --git a/src/wayland/autotests/client/CMakeLists.txt b/src/wayland/autotests/client/CMakeLists.txt index 0049e215a1..40fe528692 100644 --- a/src/wayland/autotests/client/CMakeLists.txt +++ b/src/wayland/autotests/client/CMakeLists.txt @@ -353,6 +353,17 @@ target_link_libraries( testPlasmaVirtualDesktop Qt::Test Qt::Gui KF5::WaylandCli add_test(NAME kwayland-testPlasmaVirtualDesktop COMMAND testPlasmaVirtualDesktop) ecm_mark_as_test(testPlasmaVirtualDesktop) +######################################################## +# Test Activities +######################################################## +set( testPlasmaActivities_SRCS + test_plasma_activities.cpp + ) +add_executable(testPlasmaActivities ${testPlasmaActivities_SRCS}) +target_link_libraries( testPlasmaActivities Qt::Test Qt::Gui KF5::WaylandClient Plasma::KWaylandServer) +add_test(NAME kwayland-testPlasmaActivities COMMAND testPlasmaActivities) +ecm_mark_as_test(testPlasmaActivities) + ######################################################## # Test XDG Output ######################################################## diff --git a/src/wayland/autotests/client/test_plasma_activities.cpp b/src/wayland/autotests/client/test_plasma_activities.cpp new file mode 100644 index 0000000000..46c5966e5d --- /dev/null +++ b/src/wayland/autotests/client/test_plasma_activities.cpp @@ -0,0 +1,199 @@ +/* + SPDX-FileCopyrightText: 2021 Kevin Ottens + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ +// Qt +#include +// KWin +#include "KWayland/Client/compositor.h" +#include "KWayland/Client/connection_thread.h" +#include "KWayland/Client/event_queue.h" +#include "KWayland/Client/region.h" +#include "KWayland/Client/registry.h" +#include "KWayland/Client/surface.h" +#include "../../src/server/display.h" +#include "../../src/server/compositor_interface.h" +#include "../../src/server/plasmawindowmanagement_interface.h" +#include "KWayland/Client/plasmawindowmanagement.h" + +using namespace KWayland::Client; + +class TestActivities : public QObject +{ + Q_OBJECT +public: + explicit TestActivities(QObject *parent = nullptr); +private Q_SLOTS: + void init(); + void cleanup(); + + void testEnterLeaveActivity(); + +private: + KWaylandServer::Display *m_display; + KWaylandServer::CompositorInterface *m_compositorInterface; + KWaylandServer::PlasmaWindowManagementInterface *m_windowManagementInterface; + KWaylandServer::PlasmaWindowInterface *m_windowInterface; + + KWayland::Client::ConnectionThread *m_connection; + KWayland::Client::Compositor *m_compositor; + 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-activities-0"); + +TestActivities::TestActivities(QObject *parent) + : QObject(parent) + , m_display(nullptr) + , m_compositorInterface(nullptr) + , m_connection(nullptr) + , m_compositor(nullptr) + , m_queue(nullptr) + , m_thread(nullptr) +{ +} + +void TestActivities::init() +{ + using namespace KWaylandServer; + delete m_display; + m_display = new Display(this); + m_display->addSocketName(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 windowManagementSpy(®istry, &Registry::plasmaWindowManagementAnnounced); + 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 = new CompositorInterface(m_display, m_display); + QVERIFY(compositorSpy.wait()); + m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); + + m_windowManagementInterface = new PlasmaWindowManagementInterface(m_display, m_display); + + QVERIFY(windowManagementSpy.wait()); + m_windowManagement = registry.createPlasmaWindowManagement(windowManagementSpy.first().first().value(), windowManagementSpy.first().last().value(), this); + + QSignalSpy windowSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); + QVERIFY(windowSpy.isValid()); + m_windowInterface = m_windowManagementInterface->createWindow(this, QUuid::createUuid()); + m_windowInterface->setPid(1337); + + QVERIFY(windowSpy.wait()); + m_window = windowSpy.first().first().value(); +} + +void TestActivities::cleanup() +{ +#define CLEANUP(variable) \ + if (variable) { \ + delete variable; \ + variable = nullptr; \ + } + CLEANUP(m_compositor) + CLEANUP(m_windowInterface) + 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_windowManagementInterface) + CLEANUP(m_display) +#undef CLEANUP +} + +void TestActivities::testEnterLeaveActivity() +{ + QSignalSpy enterRequestedSpy(m_windowInterface, &KWaylandServer::PlasmaWindowInterface::enterPlasmaActivityRequested); + m_window->requestEnterActivity(QStringLiteral("0-1")); + enterRequestedSpy.wait(); + + QCOMPARE(enterRequestedSpy.takeFirst().at(0).toString(), QStringLiteral("0-1")); + + QSignalSpy activityEnteredSpy(m_window, &KWayland::Client::PlasmaWindow::plasmaActivityEntered); + + //agree to the request + m_windowInterface->addPlasmaActivity(QStringLiteral("0-1")); + QCOMPARE(m_windowInterface->plasmaActivities().length(), 1); + QCOMPARE(m_windowInterface->plasmaActivities().first(), QStringLiteral("0-1")); + + //check if the client received the enter + activityEnteredSpy.wait(); + QCOMPARE(activityEnteredSpy.takeFirst().at(0).toString(), QStringLiteral("0-1")); + QCOMPARE(m_window->plasmaActivities().length(), 1); + QCOMPARE(m_window->plasmaActivities().first(), QStringLiteral("0-1")); + + //add another activity, server side + m_windowInterface->addPlasmaActivity(QStringLiteral("0-3")); + activityEnteredSpy.wait(); + QCOMPARE(activityEnteredSpy.takeFirst().at(0).toString(), QStringLiteral("0-3")); + QCOMPARE(m_windowInterface->plasmaActivities().length(), 2); + QCOMPARE(m_window->plasmaActivities().length(), 2); + QCOMPARE(m_window->plasmaActivities()[1], QStringLiteral("0-3")); + + + //remove an activity + QSignalSpy leaveRequestedSpy(m_windowInterface, &KWaylandServer::PlasmaWindowInterface::leavePlasmaActivityRequested); + m_window->requestLeaveActivity(QStringLiteral("0-1")); + leaveRequestedSpy.wait(); + + QCOMPARE(leaveRequestedSpy.takeFirst().at(0).toString(), QStringLiteral("0-1")); + + QSignalSpy activityLeftSpy(m_window, &KWayland::Client::PlasmaWindow::plasmaActivityLeft); + + //agree to the request + m_windowInterface->removePlasmaActivity(QStringLiteral("0-1")); + QCOMPARE(m_windowInterface->plasmaActivities().length(), 1); + QCOMPARE(m_windowInterface->plasmaActivities().first(), QStringLiteral("0-3")); + + //check if the client received the leave + activityLeftSpy.wait(); + QCOMPARE(activityLeftSpy.takeFirst().at(0).toString(), QStringLiteral("0-1")); + QCOMPARE(m_window->plasmaActivities().length(), 1); + QCOMPARE(m_window->plasmaActivities().first(), QStringLiteral("0-3")); +} + +QTEST_GUILESS_MAIN(TestActivities) +#include "test_plasma_activities.moc" diff --git a/src/wayland/plasmawindowmanagement_interface.cpp b/src/wayland/plasmawindowmanagement_interface.cpp index a12f01b976..c8aa9f3eba 100644 --- a/src/wayland/plasmawindowmanagement_interface.cpp +++ b/src/wayland/plasmawindowmanagement_interface.cpp @@ -23,7 +23,7 @@ namespace KWaylandServer { -static const quint32 s_version = 13; +static const quint32 s_version = 14; class PlasmaWindowManagementInterfacePrivate : public QtWaylandServer::org_kde_plasma_window_management { @@ -78,6 +78,7 @@ public: PlasmaWindowInterface *parentWindow = nullptr; QMetaObject::Connection parentWindowDestroyConnection; QStringList plasmaVirtualDesktops; + QStringList plasmaActivities; QRect geometry; PlasmaWindowInterface *q; QString m_title; @@ -105,6 +106,8 @@ protected: void org_kde_plasma_window_request_enter_virtual_desktop(Resource *resource, const QString &id) override; void org_kde_plasma_window_request_enter_new_virtual_desktop(Resource *resource) override; void org_kde_plasma_window_request_leave_virtual_desktop(Resource *resource, const QString &id) override; + void org_kde_plasma_window_request_enter_activity(Resource *resource, const QString &id) override; + void org_kde_plasma_window_request_leave_activity(Resource *resource, const QString &id) override; }; PlasmaWindowManagementInterfacePrivate::PlasmaWindowManagementInterfacePrivate(PlasmaWindowManagementInterface *_q, Display *display) @@ -343,6 +346,11 @@ void PlasmaWindowInterfacePrivate::org_kde_plasma_window_bind_resource(Resource for (const auto &desk : plasmaVirtualDesktops) { send_virtual_desktop_entered(resource->handle, desk); } + for (const auto &activity : plasmaActivities) { + if (resource->version() >= ORG_KDE_PLASMA_WINDOW_ACTIVITY_ENTERED_SINCE_VERSION) { + send_activity_entered(resource->handle, activity); + } + } if (!m_appId.isEmpty()) { send_app_id_changed(resource->handle, m_appId); } @@ -461,6 +469,18 @@ void PlasmaWindowInterfacePrivate::org_kde_plasma_window_request_leave_virtual_d emit q->leavePlasmaVirtualDesktopRequested(id); } +void PlasmaWindowInterfacePrivate::org_kde_plasma_window_request_enter_activity(Resource *resource, const QString &id) +{ + Q_UNUSED(resource) + emit q->enterPlasmaActivityRequested(id); +} + +void PlasmaWindowInterfacePrivate::org_kde_plasma_window_request_leave_activity(Resource *resource, const QString &id) +{ + Q_UNUSED(resource) + emit q->leavePlasmaActivityRequested(id); +} + void PlasmaWindowInterfacePrivate::setTitle(const QString &title) { if (m_title == title) { @@ -915,6 +935,41 @@ QStringList PlasmaWindowInterface::plasmaVirtualDesktops() const return d->plasmaVirtualDesktops; } +void PlasmaWindowInterface::addPlasmaActivity(const QString &id) +{ + if (d->plasmaActivities.contains(id)) { + return; + } + + d->plasmaActivities << id; + + const auto clientResources = d->resourceMap(); + for (auto resource : clientResources) { + if (resource->version() >= ORG_KDE_PLASMA_WINDOW_ACTIVITY_ENTERED_SINCE_VERSION) { + d->send_activity_entered(resource->handle, id); + } + } +} + +void PlasmaWindowInterface::removePlasmaActivity(const QString &id) +{ + if (!d->plasmaActivities.removeOne(id)) { + return; + } + + const auto clientResources = d->resourceMap(); + for (auto resource : clientResources) { + if (resource->version() >= ORG_KDE_PLASMA_WINDOW_ACTIVITY_LEFT_SINCE_VERSION) { + d->send_activity_left(resource->handle, id); + } + } +} + +QStringList PlasmaWindowInterface::plasmaActivities() const +{ + return d->plasmaActivities; +} + void PlasmaWindowInterface::setShadeable(bool set) { d->setState(ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_SHADEABLE, set); diff --git a/src/wayland/plasmawindowmanagement_interface.h b/src/wayland/plasmawindowmanagement_interface.h index fd77720c7f..201dbe0c7c 100644 --- a/src/wayland/plasmawindowmanagement_interface.h +++ b/src/wayland/plasmawindowmanagement_interface.h @@ -174,6 +174,24 @@ public: */ QStringList plasmaVirtualDesktops() const; + /** + * Adds an activity to this window: a window can be on + * an arbitrary subset of activities. + * If it's on none it will be considered on all activities. + */ + void addPlasmaActivity(const QString &id); + + /** + * Removes an activity from a window + */ + void removePlasmaActivity(const QString &id); + + /** + * The ids of all the activities currently associated with this window. + * When an activity is deleted it will be automatically removed from this list + */ + QStringList plasmaActivities() const; + /** * Set the application menu D-BUS service name and object path for the window. */ @@ -242,6 +260,18 @@ Q_SIGNALS: */ void leavePlasmaVirtualDesktopRequested(const QString &desktop); + /** + * Emitted when the client wishes this window to enter an activity. + * The server will decide whether to consent this request + */ + void enterPlasmaActivityRequested(const QString &activity); + + /** + * Emitted when the client wishes to remove this window from an activity. + * The server will decide whether to consent this request + */ + void leavePlasmaActivityRequested(const QString &activity); + private: friend class PlasmaWindowManagementInterface; friend class PlasmaWindowInterfacePrivate;