diff --git a/autotests/client/test_plasmashell.cpp b/autotests/client/test_plasmashell.cpp index 7c6082d..e14d953 100644 --- a/autotests/client/test_plasmashell.cpp +++ b/autotests/client/test_plasmashell.cpp @@ -1,531 +1,538 @@ /******************************************************************** Copyright 2016 Martin Gräßlin 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 // KWayland #include "../../src/client/connection_thread.h" #include "../../src/client/compositor.h" #include "../../src/client/event_queue.h" #include "../../src/client/registry.h" #include "../../src/client/surface.h" #include "../../src/client/plasmashell.h" #include "../../src/server/display.h" #include "../../src/server/compositor_interface.h" #include "../../src/server/plasmashell_interface.h" using namespace KWayland::Client; using namespace KWayland::Server; class TestPlasmaShell : public QObject { Q_OBJECT private Q_SLOTS: void init(); void cleanup(); void testRole_data(); void testRole(); void testPosition(); void testSkipTaskbar(); void testSkipSwitcher(); void testPanelBehavior_data(); void testPanelBehavior(); void testAutoHidePanel(); void testPanelTakesFocus(); void testDisconnect(); void testWhileDestroying(); private: Display *m_display = nullptr; CompositorInterface *m_compositorInterface = nullptr; PlasmaShellInterface *m_plasmaShellInterface = nullptr; ConnectionThread *m_connection = nullptr; Compositor *m_compositor = nullptr; EventQueue *m_queue = nullptr; QThread *m_thread = nullptr; Registry *m_registry = nullptr; PlasmaShell *m_plasmaShell = nullptr; }; static const QString s_socketName = QStringLiteral("kwayland-test-wayland-plasma-shell-0"); void TestPlasmaShell::init() { delete m_display; m_display = new Display(this); m_display->setSocketName(s_socketName); m_display->start(); QVERIFY(m_display->isRunning()); m_compositorInterface = m_display->createCompositor(m_display); m_compositorInterface->create(); m_display->createShm(); m_plasmaShellInterface = m_display->createPlasmaShell(m_display); m_plasmaShellInterface->create(); // 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 EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); m_registry = new Registry(); QSignalSpy interfacesAnnouncedSpy(m_registry, &Registry::interfaceAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); QVERIFY(!m_registry->eventQueue()); m_registry->setEventQueue(m_queue); QCOMPARE(m_registry->eventQueue(), m_queue); m_registry->create(m_connection); QVERIFY(m_registry->isValid()); m_registry->setup(); QVERIFY(interfacesAnnouncedSpy.wait()); #define CREATE(variable, factory, iface) \ variable = m_registry->create##factory(m_registry->interface(Registry::Interface::iface).name, m_registry->interface(Registry::Interface::iface).version, this); \ QVERIFY(variable); CREATE(m_compositor, Compositor, Compositor) CREATE(m_plasmaShell, PlasmaShell, PlasmaShell) #undef CREATE } void TestPlasmaShell::cleanup() { #define DELETE(name) \ if (name) { \ delete name; \ name = nullptr; \ } DELETE(m_plasmaShell) DELETE(m_compositor) DELETE(m_queue) DELETE(m_registry) #undef DELETE if (m_thread) { m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; } delete m_connection; m_connection = nullptr; delete m_display; m_display = nullptr; } void TestPlasmaShell::testRole_data() { QTest::addColumn("clientRole"); QTest::addColumn("serverRole"); QTest::newRow("desktop") << PlasmaShellSurface::Role::Desktop << PlasmaShellSurfaceInterface::Role::Desktop; QTest::newRow("osd") << PlasmaShellSurface::Role::OnScreenDisplay << PlasmaShellSurfaceInterface::Role::OnScreenDisplay; QTest::newRow("panel") << PlasmaShellSurface::Role::Panel << PlasmaShellSurfaceInterface::Role::Panel; QTest::newRow("notification") << PlasmaShellSurface::Role::Notification << PlasmaShellSurfaceInterface::Role::Notification; QTest::newRow("tooltip") << PlasmaShellSurface::Role::ToolTip << PlasmaShellSurfaceInterface::Role::ToolTip; QTest::newRow("criticalnotification") << PlasmaShellSurface::Role::CriticalNotification << PlasmaShellSurfaceInterface::Role::CriticalNotification; } void TestPlasmaShell::testRole() { // this test verifies that setting the role on a plasma shell surface works // first create signal spies QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QSignalSpy plasmaSurfaceCreatedSpy(m_plasmaShellInterface, &PlasmaShellInterface::surfaceCreated); QVERIFY(plasmaSurfaceCreatedSpy.isValid()); // create the surface QScopedPointer s(m_compositor->createSurface()); // no PlasmaShellSurface for the Surface yet yet QVERIFY(!PlasmaShellSurface::get(s.data())); QScopedPointer ps(m_plasmaShell->createSurface(s.data())); QCOMPARE(ps->role(), PlasmaShellSurface::Role::Normal); // now we should have a PlasmaShellSurface for QCOMPARE(PlasmaShellSurface::get(s.data()), ps.data()); // try to create another PlasmaShellSurface for the same Surface, should return from cache QCOMPARE(m_plasmaShell->createSurface(s.data()), ps.data()); // and get them on the server QVERIFY(plasmaSurfaceCreatedSpy.wait()); QCOMPARE(plasmaSurfaceCreatedSpy.count(), 1); QCOMPARE(surfaceCreatedSpy.count(), 1); // verify that we got a plasma shell surface auto sps = plasmaSurfaceCreatedSpy.first().first().value(); QVERIFY(sps); QVERIFY(sps->surface()); QCOMPARE(sps->surface(), surfaceCreatedSpy.first().first().value()); QCOMPARE(sps->shell(), m_plasmaShellInterface); QCOMPARE(PlasmaShellSurfaceInterface::get(sps->resource()), sps); QVERIFY(!PlasmaShellSurfaceInterface::get(nullptr)); // default role should be normal QCOMPARE(sps->role(), PlasmaShellSurfaceInterface::Role::Normal); // now change it QSignalSpy roleChangedSpy(sps, &PlasmaShellSurfaceInterface::roleChanged); QVERIFY(roleChangedSpy.isValid()); QFETCH(PlasmaShellSurface::Role, clientRole); ps->setRole(clientRole); QCOMPARE(ps->role(), clientRole); QVERIFY(roleChangedSpy.wait()); QCOMPARE(roleChangedSpy.count(), 1); QTEST(sps->role(), "serverRole"); // try changing again should not emit the signal ps->setRole(clientRole); QVERIFY(!roleChangedSpy.wait(100)); // set role back to normal ps->setRole(PlasmaShellSurface::Role::Normal); QCOMPARE(ps->role(), PlasmaShellSurface::Role::Normal); QVERIFY(roleChangedSpy.wait()); QCOMPARE(roleChangedSpy.count(), 2); QCOMPARE(sps->role(), PlasmaShellSurfaceInterface::Role::Normal); } void TestPlasmaShell::testPosition() { // this test verifies that updating the position of a PlasmaShellSurface is properly passed to the 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()); // default position should not be set QVERIFY(!sps->isPositionSet()); QCOMPARE(sps->position(), QPoint()); // now let's try to change the position QSignalSpy positionChangedSpy(sps, &PlasmaShellSurfaceInterface::positionChanged); QVERIFY(positionChangedSpy.isValid()); ps->setPosition(QPoint(1, 2)); QVERIFY(positionChangedSpy.wait()); QCOMPARE(positionChangedSpy.count(), 1); QVERIFY(sps->isPositionSet()); QCOMPARE(sps->position(), QPoint(1, 2)); // let's try to set same position, should not trigger an update ps->setPosition(QPoint(1, 2)); QVERIFY(!positionChangedSpy.wait(100)); // different point should work, though ps->setPosition(QPoint(3, 4)); QVERIFY(positionChangedSpy.wait()); QCOMPARE(positionChangedSpy.count(), 2); QCOMPARE(sps->position(), QPoint(3, 4)); } void TestPlasmaShell::testSkipTaskbar() { // this test verifies that sip taskbar 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->skipTaskbar()); // now change QSignalSpy skipTaskbarChangedSpy(sps, &PlasmaShellSurfaceInterface::skipTaskbarChanged); QVERIFY(skipTaskbarChangedSpy.isValid()); ps->setSkipTaskbar(true); QVERIFY(skipTaskbarChangedSpy.wait()); QVERIFY(sps->skipTaskbar()); // setting to same again should not emit the signal ps->setSkipTaskbar(true); QEXPECT_FAIL("", "Should not be emitted if not changed", Continue); QVERIFY(!skipTaskbarChangedSpy.wait(100)); QVERIFY(sps->skipTaskbar()); // setting to false should change again ps->setSkipTaskbar(false); QVERIFY(skipTaskbarChangedSpy.wait()); 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"); QTest::addColumn("server"); QTest::newRow("autohide") << PlasmaShellSurface::PanelBehavior::AutoHide << PlasmaShellSurfaceInterface::PanelBehavior::AutoHide; QTest::newRow("can cover") << PlasmaShellSurface::PanelBehavior::WindowsCanCover << PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover; QTest::newRow("go below") << PlasmaShellSurface::PanelBehavior::WindowsGoBelow << PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow; } void TestPlasmaShell::testPanelBehavior() { // this test verifies that the panel behavior is properly passed to the server QSignalSpy plasmaSurfaceCreatedSpy(m_plasmaShellInterface, &PlasmaShellInterface::surfaceCreated); QVERIFY(plasmaSurfaceCreatedSpy.isValid()); QScopedPointer s(m_compositor->createSurface()); QScopedPointer ps(m_plasmaShell->createSurface(s.data())); ps->setRole(PlasmaShellSurface::Role::Panel); 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()); QCOMPARE(sps->panelBehavior(), PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible); // now change the behavior QSignalSpy behaviorChangedSpy(sps, &PlasmaShellSurfaceInterface::panelBehaviorChanged); QVERIFY(behaviorChangedSpy.isValid()); QFETCH(PlasmaShellSurface::PanelBehavior, client); ps->setPanelBehavior(client); QVERIFY(behaviorChangedSpy.wait()); QTEST(sps->panelBehavior(), "server"); // changing to same should not trigger the signal ps->setPanelBehavior(client); QVERIFY(!behaviorChangedSpy.wait(100)); // but changing back to Always Visible should work ps->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AlwaysVisible); QVERIFY(behaviorChangedSpy.wait()); QCOMPARE(sps->panelBehavior(), PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible); } void TestPlasmaShell::testAutoHidePanel() { // this test verifies that auto-hiding panels work correctly QSignalSpy plasmaSurfaceCreatedSpy(m_plasmaShellInterface, &PlasmaShellInterface::surfaceCreated); QVERIFY(plasmaSurfaceCreatedSpy.isValid()); QScopedPointer s(m_compositor->createSurface()); QScopedPointer ps(m_plasmaShell->createSurface(s.data())); ps->setRole(PlasmaShellSurface::Role::Panel); ps->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AutoHide); QVERIFY(plasmaSurfaceCreatedSpy.wait()); QCOMPARE(plasmaSurfaceCreatedSpy.count(), 1); auto sps = plasmaSurfaceCreatedSpy.first().first().value(); QVERIFY(sps); QCOMPARE(sps->panelBehavior(), PlasmaShellSurfaceInterface::PanelBehavior::AutoHide); QSignalSpy autoHideRequestedSpy(sps, &PlasmaShellSurfaceInterface::panelAutoHideHideRequested); QVERIFY(autoHideRequestedSpy.isValid()); QSignalSpy autoHideShowRequestedSpy(sps, &PlasmaShellSurfaceInterface::panelAutoHideShowRequested); QVERIFY(autoHideShowRequestedSpy.isValid()); ps->requestHideAutoHidingPanel(); QVERIFY(autoHideRequestedSpy.wait()); QCOMPARE(autoHideRequestedSpy.count(), 1); QCOMPARE(autoHideShowRequestedSpy.count(), 0); QSignalSpy panelShownSpy(ps.data(), &PlasmaShellSurface::autoHidePanelShown); QVERIFY(panelShownSpy.isValid()); QSignalSpy panelHiddenSpy(ps.data(), &PlasmaShellSurface::autoHidePanelHidden); QVERIFY(panelHiddenSpy.isValid()); sps->hideAutoHidingPanel(); QVERIFY(panelHiddenSpy.wait()); QCOMPARE(panelHiddenSpy.count(), 1); QCOMPARE(panelShownSpy.count(), 0); ps->requestShowAutoHidingPanel(); QVERIFY(autoHideShowRequestedSpy.wait()); QCOMPARE(autoHideRequestedSpy.count(), 1); QCOMPARE(autoHideShowRequestedSpy.count(), 1); sps->showAutoHidingPanel(); QVERIFY(panelShownSpy.wait()); QCOMPARE(panelHiddenSpy.count(), 1); QCOMPARE(panelShownSpy.count(), 1); // change panel type ps->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AlwaysVisible); // requesting auto hide should raise error QSignalSpy errorSpy(m_connection, &ConnectionThread::errorOccurred); QVERIFY(errorSpy.isValid()); ps->requestHideAutoHidingPanel(); QVERIFY(errorSpy.wait()); } void TestPlasmaShell::testPanelTakesFocus() { // this test verifies that whether a panel wants to take focus is passed through correctly QSignalSpy plasmaSurfaceCreatedSpy(m_plasmaShellInterface, &PlasmaShellInterface::surfaceCreated); + QVERIFY(plasmaSurfaceCreatedSpy.isValid()); QScopedPointer s(m_compositor->createSurface()); QScopedPointer ps(m_plasmaShell->createSurface(s.data())); ps->setRole(PlasmaShellSurface::Role::Panel); QVERIFY(plasmaSurfaceCreatedSpy.wait()); QCOMPARE(plasmaSurfaceCreatedSpy.count(), 1); auto sps = plasmaSurfaceCreatedSpy.first().first().value(); + QSignalSpy plasmaSurfaceTakesFocusSpy(sps, &PlasmaShellSurfaceInterface::panelTakesFocusChanged); + QVERIFY(sps); QCOMPARE(sps->role(), PlasmaShellSurfaceInterface::Role::Panel); QCOMPARE(sps->panelTakesFocus(), false); ps->setPanelTakesFocus(true); m_connection->flush(); - QTRY_COMPARE(sps->panelTakesFocus(), true); + QVERIFY(plasmaSurfaceTakesFocusSpy.wait()); + QCOMPARE(plasmaSurfaceTakesFocusSpy.count(), 1); + QCOMPARE(sps->panelTakesFocus(), true); ps->setPanelTakesFocus(false); m_connection->flush(); - QTRY_COMPARE(sps->panelTakesFocus(), false); + QVERIFY(plasmaSurfaceTakesFocusSpy.wait()); + QCOMPARE(plasmaSurfaceTakesFocusSpy.count(), 2); + QCOMPARE(sps->panelTakesFocus(), false); } void TestPlasmaShell::testDisconnect() { // this test verifies that a disconnect cleans up QSignalSpy plasmaSurfaceCreatedSpy(m_plasmaShellInterface, &PlasmaShellInterface::surfaceCreated); QVERIFY(plasmaSurfaceCreatedSpy.isValid()); // create the surface QScopedPointer s(m_compositor->createSurface()); QScopedPointer ps(m_plasmaShell->createSurface(s.data())); // and get them on the server QVERIFY(plasmaSurfaceCreatedSpy.wait()); QCOMPARE(plasmaSurfaceCreatedSpy.count(), 1); auto sps = plasmaSurfaceCreatedSpy.first().first().value(); QVERIFY(sps); // disconnect QSignalSpy clientDisconnectedSpy(sps->client(), &ClientConnection::disconnected); QVERIFY(clientDisconnectedSpy.isValid()); QSignalSpy surfaceDestroyedSpy(sps, &QObject::destroyed); QVERIFY(surfaceDestroyedSpy.isValid()); if (m_connection) { m_connection->deleteLater(); m_connection = nullptr; } QVERIFY(clientDisconnectedSpy.wait()); QCOMPARE(clientDisconnectedSpy.count(), 1); QCOMPARE(surfaceDestroyedSpy.count(), 0); QVERIFY(surfaceDestroyedSpy.wait()); QCOMPARE(surfaceDestroyedSpy.count(), 1); s->destroy(); ps->destroy(); m_plasmaShell->destroy(); m_compositor->destroy(); m_registry->destroy(); m_queue->destroy(); } void TestPlasmaShell::testWhileDestroying() { // this test tries to hit a condition that a Surface gets created with an ID which was already // used for a previous Surface. For each Surface we try to create a PlasmaShellSurface. // Even if there was a Surface in the past with the same ID, it should create the PlasmaShellSurface QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer s(m_compositor->createSurface()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); // create ShellSurface QSignalSpy shellSurfaceCreatedSpy(m_plasmaShellInterface, &PlasmaShellInterface::surfaceCreated); QVERIFY(shellSurfaceCreatedSpy.isValid()); QScopedPointer ps(m_plasmaShell->createSurface(s.data())); QVERIFY(shellSurfaceCreatedSpy.wait()); // now try to create more surfaces QSignalSpy clientErrorSpy(m_connection, &ConnectionThread::errorOccurred); QVERIFY(clientErrorSpy.isValid()); for (int i = 0; i < 100; i++) { s.reset(); s.reset(m_compositor->createSurface()); m_plasmaShell->createSurface(s.data(), this); QVERIFY(surfaceCreatedSpy.wait()); } QVERIFY(clientErrorSpy.isEmpty()); QVERIFY(!clientErrorSpy.wait(100)); QVERIFY(clientErrorSpy.isEmpty()); } QTEST_GUILESS_MAIN(TestPlasmaShell) #include "test_plasmashell.moc" diff --git a/src/server/plasmashell_interface.cpp b/src/server/plasmashell_interface.cpp index 2a020bd..046c526 100644 --- a/src/server/plasmashell_interface.cpp +++ b/src/server/plasmashell_interface.cpp @@ -1,414 +1,418 @@ /******************************************************************** Copyright 2015 Martin Gräßlin 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 "plasmashell_interface.h" #include "global_p.h" #include "resource_p.h" #include "display.h" #include "surface_interface.h" #include #include #include namespace KWayland { namespace Server { class PlasmaShellInterface::Private : public Global::Private { public: Private(PlasmaShellInterface *q, Display *d); QList surfaces; private: static void createSurfaceCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *surface); void bind(wl_client *client, uint32_t version, uint32_t id) override; void createSurface(wl_client *client, uint32_t version, uint32_t id, SurfaceInterface *surface, wl_resource *parentResource); PlasmaShellInterface *q; static const struct org_kde_plasma_shell_interface s_interface; static const quint32 s_version; }; const quint32 PlasmaShellInterface::Private::s_version = 6; PlasmaShellInterface::Private::Private(PlasmaShellInterface *q, Display *d) : Global::Private(d, &org_kde_plasma_shell_interface, s_version) , q(q) { } #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct org_kde_plasma_shell_interface PlasmaShellInterface::Private::s_interface = { createSurfaceCallback }; #endif class PlasmaShellSurfaceInterface::Private : public Resource::Private { public: Private(PlasmaShellSurfaceInterface *q, PlasmaShellInterface *shell, SurfaceInterface *surface, wl_resource *parentResource); SurfaceInterface *surface; QPoint m_globalPos; Role m_role = Role::Normal; bool m_positionSet = false; PanelBehavior m_panelBehavior = PanelBehavior::AlwaysVisible; bool m_skipTaskbar = false; bool m_skipSwitcher = false; bool panelTakesFocus = false; private: // interface callbacks static void setOutputCallback(wl_client *client, wl_resource *resource, wl_resource *output); static void setPositionCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y); 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); void setPosition(const QPoint &globalPos); void setRole(uint32_t role); void setPanelBehavior(org_kde_plasma_surface_panel_behavior behavior); PlasmaShellSurfaceInterface *q_func() { return reinterpret_cast(q); } static const struct org_kde_plasma_surface_interface s_interface; }; PlasmaShellInterface::PlasmaShellInterface(Display *display, QObject *parent) : Global(new Private(this, display), parent) { } PlasmaShellInterface::~PlasmaShellInterface() = default; void PlasmaShellInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id) { auto c = display->getConnection(client); wl_resource *shell = c->createResource(&org_kde_plasma_shell_interface, qMin(version, s_version), id); if (!shell) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(shell, &s_interface, this, nullptr); } void PlasmaShellInterface::Private::createSurfaceCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *surface) { auto s = reinterpret_cast(wl_resource_get_user_data(resource)); s->createSurface(client, wl_resource_get_version(resource), id, SurfaceInterface::get(surface), resource); } void PlasmaShellInterface::Private::createSurface(wl_client *client, uint32_t version, uint32_t id, SurfaceInterface *surface, wl_resource *parentResource) { auto it = std::find_if(surfaces.constBegin(), surfaces.constEnd(), [surface](PlasmaShellSurfaceInterface *s) { return surface == s->surface(); } ); if (it != surfaces.constEnd()) { wl_resource_post_error(surface->resource(), WL_DISPLAY_ERROR_INVALID_OBJECT, "PlasmaShellSurface already created"); return; } PlasmaShellSurfaceInterface *shellSurface = new PlasmaShellSurfaceInterface(q, surface, parentResource); surfaces << shellSurface; QObject::connect(shellSurface, &PlasmaShellSurfaceInterface::destroyed, q, [this, shellSurface] { surfaces.removeAll(shellSurface); } ); shellSurface->d->create(display->getConnection(client), version, id); emit q->surfaceCreated(shellSurface); } /********************************* * ShellSurfaceInterface *********************************/ PlasmaShellSurfaceInterface::Private::Private(PlasmaShellSurfaceInterface *q, PlasmaShellInterface *shell, SurfaceInterface *surface, wl_resource *parentResource) : Resource::Private(q, shell, parentResource, &org_kde_plasma_surface_interface, &s_interface) , surface(surface) { } #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct org_kde_plasma_surface_interface PlasmaShellSurfaceInterface::Private::s_interface = { resourceDestroyedCallback, setOutputCallback, setPositionCallback, setRoleCallback, setPanelBehaviorCallback, setSkipTaskbarCallback, panelAutoHideHideCallback, panelAutoHideShowCallback, panelTakesFocusCallback, setSkipSwitcherCallback }; #endif PlasmaShellSurfaceInterface::PlasmaShellSurfaceInterface(PlasmaShellInterface *shell, SurfaceInterface *parent, wl_resource *parentResource) : Resource(new Private(this, shell, parent, parentResource)) { auto unsetSurface = [this] { Q_D(); d->surface = nullptr; }; connect(parent, &Resource::unbound, this, unsetSurface); connect(parent, &QObject::destroyed, this, unsetSurface); } PlasmaShellSurfaceInterface::~PlasmaShellSurfaceInterface() = default; SurfaceInterface *PlasmaShellSurfaceInterface::surface() const { Q_D(); return d->surface; } PlasmaShellInterface *PlasmaShellSurfaceInterface::shell() const { Q_D(); return reinterpret_cast(d->global); } PlasmaShellSurfaceInterface::Private *PlasmaShellSurfaceInterface::d_func() const { return reinterpret_cast(d.data()); } void PlasmaShellSurfaceInterface::Private::setOutputCallback(wl_client *client, wl_resource *resource, wl_resource *output) { Q_UNUSED(client) Q_UNUSED(resource) Q_UNUSED(output) // TODO: implement } void PlasmaShellSurfaceInterface::Private::setPositionCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y) { auto s = cast(resource); Q_ASSERT(client == *s->client); s->setPosition(QPoint(x, y)); } void PlasmaShellSurfaceInterface::Private::setPosition(const QPoint &globalPos) { if (m_globalPos == globalPos && m_positionSet) { return; } m_positionSet = true; m_globalPos = globalPos; Q_Q(PlasmaShellSurfaceInterface); emit q->positionChanged(); } void PlasmaShellSurfaceInterface::Private::setRoleCallback(wl_client *client, wl_resource *resource, uint32_t role) { auto s = cast(resource); Q_ASSERT(client == *s->client); s->setRole(role); } void PlasmaShellSurfaceInterface::Private::setRole(uint32_t role) { Role r = Role::Normal; switch (role) { case ORG_KDE_PLASMA_SURFACE_ROLE_DESKTOP: r = Role::Desktop; break; case ORG_KDE_PLASMA_SURFACE_ROLE_PANEL: r = Role::Panel; break; case ORG_KDE_PLASMA_SURFACE_ROLE_ONSCREENDISPLAY: r = Role::OnScreenDisplay; break; case ORG_KDE_PLASMA_SURFACE_ROLE_NOTIFICATION: r = Role::Notification; break; case ORG_KDE_PLASMA_SURFACE_ROLE_TOOLTIP: r = Role::ToolTip; break; case ORG_KDE_PLASMA_SURFACE_ROLE_CRITICALNOTIFICATION: r = Role::CriticalNotification; break; case ORG_KDE_PLASMA_SURFACE_ROLE_NORMAL: default: r = Role::Normal; break; } if (r == m_role) { return; } m_role = r; Q_Q(PlasmaShellSurfaceInterface); emit q->roleChanged(); } void PlasmaShellSurfaceInterface::Private::setPanelBehaviorCallback(wl_client *client, wl_resource *resource, uint32_t flag) { auto s = cast(resource); Q_ASSERT(client == *s->client); s->setPanelBehavior(org_kde_plasma_surface_panel_behavior(flag)); } void PlasmaShellSurfaceInterface::Private::setSkipTaskbarCallback(wl_client *client, wl_resource *resource, uint32_t skip) { auto s = cast(resource); Q_ASSERT(client == *s->client); s->m_skipTaskbar = (bool)skip; 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); Q_ASSERT(client == *s->client); if (s->m_role != Role::Panel || s->m_panelBehavior != PanelBehavior::AutoHide) { wl_resource_post_error(s->resource, ORG_KDE_PLASMA_SURFACE_ERROR_PANEL_NOT_AUTO_HIDE, "Not an auto hide panel"); return; } emit s->q_func()->panelAutoHideHideRequested(); } void PlasmaShellSurfaceInterface::Private::panelAutoHideShowCallback(wl_client *client, wl_resource *resource) { auto s = cast(resource); Q_ASSERT(client == *s->client); if (s->m_role != Role::Panel || s->m_panelBehavior != PanelBehavior::AutoHide) { wl_resource_post_error(s->resource, ORG_KDE_PLASMA_SURFACE_ERROR_PANEL_NOT_AUTO_HIDE, "Not an auto hide panel"); return; } emit s->q_func()->panelAutoHideShowRequested(); } void PlasmaShellSurfaceInterface::Private::panelTakesFocusCallback(wl_client *client, wl_resource *resource, uint32_t takesFocus) { auto s = cast(resource); Q_ASSERT(client == *s->client); + if (s->panelTakesFocus == takesFocus) { + return; + } s->panelTakesFocus = takesFocus; + emit s->q_func()->panelTakesFocusChanged(); } void PlasmaShellSurfaceInterface::Private::setPanelBehavior(org_kde_plasma_surface_panel_behavior behavior) { PanelBehavior newBehavior = PanelBehavior::AlwaysVisible; switch (behavior) { case ORG_KDE_PLASMA_SURFACE_PANEL_BEHAVIOR_AUTO_HIDE: newBehavior = PanelBehavior::AutoHide; break; case ORG_KDE_PLASMA_SURFACE_PANEL_BEHAVIOR_WINDOWS_CAN_COVER: newBehavior = PanelBehavior::WindowsCanCover; break; case ORG_KDE_PLASMA_SURFACE_PANEL_BEHAVIOR_WINDOWS_GO_BELOW: newBehavior = PanelBehavior::WindowsGoBelow; break; case ORG_KDE_PLASMA_SURFACE_PANEL_BEHAVIOR_ALWAYS_VISIBLE: default: break; } if (m_panelBehavior == newBehavior) { return; } m_panelBehavior = newBehavior; Q_Q(PlasmaShellSurfaceInterface); emit q->panelBehaviorChanged(); } QPoint PlasmaShellSurfaceInterface::position() const { Q_D(); return d->m_globalPos; } PlasmaShellSurfaceInterface::Role PlasmaShellSurfaceInterface::role() const { Q_D(); return d->m_role; } bool PlasmaShellSurfaceInterface::isPositionSet() const { Q_D(); return d->m_positionSet; } PlasmaShellSurfaceInterface::PanelBehavior PlasmaShellSurfaceInterface::panelBehavior() const { Q_D(); return d->m_panelBehavior; } bool PlasmaShellSurfaceInterface::skipTaskbar() const { Q_D(); return d->m_skipTaskbar; } bool PlasmaShellSurfaceInterface::skipSwitcher() const { Q_D(); return d->m_skipSwitcher; } void PlasmaShellSurfaceInterface::hideAutoHidingPanel() { Q_D(); if (!d->resource) { return; } org_kde_plasma_surface_send_auto_hidden_panel_hidden(d->resource); } void PlasmaShellSurfaceInterface::showAutoHidingPanel() { Q_D(); if (!d->resource) { return; } org_kde_plasma_surface_send_auto_hidden_panel_shown(d->resource); } bool PlasmaShellSurfaceInterface::panelTakesFocus() const { Q_D(); return d->panelTakesFocus; } PlasmaShellSurfaceInterface *PlasmaShellSurfaceInterface::get(wl_resource *native) { return Private::get(native); } } } diff --git a/src/server/plasmashell_interface.h b/src/server/plasmashell_interface.h index 7e00611..8daf086 100644 --- a/src/server/plasmashell_interface.h +++ b/src/server/plasmashell_interface.h @@ -1,250 +1,257 @@ /******************************************************************** Copyright 2015 Martin Gräßlin 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 WAYLAND_SERVER_PLASMA_SHELL_INTERFACE_H #define WAYLAND_SERVER_PLASMA_SHELL_INTERFACE_H #include #include #include "global.h" #include "resource.h" class QSize; struct wl_resource; namespace KWayland { namespace Server { class Display; class SurfaceInterface; class PlasmaShellSurfaceInterface; /** * @brief Global for the org_kde_plasma_shell interface. * * The PlasmaShellInterface allows to add additional information to a SurfaceInterface. * It goes beyond what a ShellSurfaceInterface provides and is adjusted toward the needs * of the Plasma desktop. * * A server providing this interface should think about how to restrict access to it as * it allows to perform absolute window positioning. * * @since 5.4 **/ class KWAYLANDSERVER_EXPORT PlasmaShellInterface : public Global { Q_OBJECT public: virtual ~PlasmaShellInterface(); Q_SIGNALS: /** * Emitted whenever a PlasmaShellSurfaceInterface got created. **/ void surfaceCreated(KWayland::Server::PlasmaShellSurfaceInterface*); private: friend class Display; explicit PlasmaShellInterface(Display *display, QObject *parent); class Private; }; /** * @brief Resource for the org_kde_plasma_shell_surface interface. * * PlasmaShellSurfaceInterface gets created by PlasmaShellInterface. * * @since 5.4 **/ class KWAYLANDSERVER_EXPORT PlasmaShellSurfaceInterface : public Resource { Q_OBJECT public: virtual ~PlasmaShellSurfaceInterface(); /** * @returns the SurfaceInterface this PlasmaShellSurfaceInterface got created for **/ SurfaceInterface *surface() const; /** * @returns The PlasmaShellInterface which created this PlasmaShellSurfaceInterface. **/ PlasmaShellInterface *shell() const; /** * @returns the requested position in global coordinates. **/ QPoint position() const; /** * @returns Whether a global position has been requested. **/ bool isPositionSet() const; /** * Describes possible roles this PlasmaShellSurfaceInterface can have. * The role can be used by the server to e.g. change the stacking order accordingly. **/ enum class Role { Normal, ///< A normal surface Desktop, ///< The surface represents a desktop, normally stacked below all other surfaces Panel, ///< The surface represents a panel (dock), normally stacked above normal surfaces OnScreenDisplay, ///< The surface represents an on screen display, like a volume changed notification Notification, ///< The surface represents a notification @since 5.24 ToolTip, ///< The surface represents a tooltip @since 5.24 CriticalNotification, ///< The surface represents a critical notification, like battery is running out @since 5.58 }; /** * @returns The requested role, default value is @c Role::Normal. **/ Role role() const; /** * Describes how a PlasmaShellSurfaceInterface with role @c Role::Panel should behave. **/ enum class PanelBehavior { AlwaysVisible, ///< The panel should be always visible AutoHide, ///< The panel auto hides at a screen edge and returns on mouse press against edge WindowsCanCover, ///< Windows are allowed to go above the panel, it raises on mouse press against screen edge WindowsGoBelow ///< Window are allowed to go below the panel }; /** * @returns The PanelBehavior for a PlasmaShellSurfaceInterface with role @c Role::Panel * @see role **/ PanelBehavior panelBehavior() const; /** * @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. * * @see showAutoHidingPanel * @see panelAutoHideHideRequested * @see panelAutoHideShowRequested * @since 5.28 **/ void hideAutoHidingPanel(); /** * Informs the PlasmaShellSurfaceInterface that the auto-hiding panel got shown again. * * @see hideAutoHidingPanel * @see panelAutoHideHideRequested * @see panelAutoHideShowRequested * @see 5.28 **/ void showAutoHidingPanel(); /** * Whether a PlasmaShellSurfaceInterface with Role Panel wants to have focus. * * By default a Panel does not get focus, but the PlasmaShellSurfaceInterface can * request that it wants to have focus. The compositor can use this information to * pass focus to the panel. * @since 5.28 **/ bool panelTakesFocus() const; /** * @returns The PlasmaShellSurfaceInterface for the @p native resource. * @since 5.5 **/ static PlasmaShellSurfaceInterface *get(wl_resource *native); Q_SIGNALS: /** * A change of global position has been requested. **/ void positionChanged(); /** * A change of the role has been requested. **/ void roleChanged(); /** * A change of the panel behavior has been requested. **/ void panelBehaviorChanged(); /** * 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. * * The compositor should inform the PlasmaShellSurfaceInterface about the actual change. * Once the surface is hidden it should invoke {@link hideAutoHidingPanel}. If the compositor * cannot hide the surface (e.g. because it doesn't border a screen edge) it should inform * the surface through invoking {@link showAutoHidingPanel}. This method should also be invoked * whenever the surface gets shown again due to triggering the screen edge. * * @see hideAutoHidingPanel * @see showAutoHidingPanel * @see panelAutoHideShowRequested * @since 5.28 **/ void panelAutoHideHideRequested(); /** * A surface with Role Panel and PanelBehavior AutoHide requested to be shown. * * The compositor should inform the PlasmaShellSurfaceInterface about the actual change. * Once the surface is shown it should invoke {@link showAutoHidingPanel}. * * @see hideAutoHidingPanel * @see showAutoHidingPanel * @see panelAutoHideHideRequested * @since 5.28 **/ void panelAutoHideShowRequested(); + /* + * Emitted when panelTakesFocus changes + * @see panelTakesFocus + * @since 5.66 + */ + void panelTakesFocusChanged(); + private: friend class PlasmaShellInterface; explicit PlasmaShellSurfaceInterface(PlasmaShellInterface *shell, SurfaceInterface *parent, wl_resource *parentResource); class Private; Private *d_func() const; }; } } Q_DECLARE_METATYPE(KWayland::Server::PlasmaShellSurfaceInterface::Role) Q_DECLARE_METATYPE(KWayland::Server::PlasmaShellSurfaceInterface::PanelBehavior) #endif