diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt index 67def6aef9..146ff21ab2 100644 --- a/autotests/integration/CMakeLists.txt +++ b/autotests/integration/CMakeLists.txt @@ -17,6 +17,10 @@ ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework_SOURCES PROTOCOL protocols/wlr-layer-shell-unstable-v1.xml BASENAME wlr-layer-shell-unstable-v1 ) +ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework_SOURCES + PROTOCOL ${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml + BASENAME xdg-shell +) add_library(KWinIntegrationTestFramework STATIC ${KWinIntegrationTestFramework_SOURCES}) target_link_libraries(KWinIntegrationTestFramework kwin Qt5::Test Wayland::Client) diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h index a8e37213f7..31e212eee0 100644 --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -18,6 +18,7 @@ #include #include "qwayland-wlr-layer-shell-unstable-v1.h" +#include "qwayland-xdg-shell.h" namespace KWayland { @@ -107,6 +108,106 @@ Q_SIGNALS: void configureRequested(quint32 serial, const QSize &size); }; +/** + * The XdgShell class represents the @c xdg_wm_base global. + */ +class XdgShell : public QtWayland::xdg_wm_base +{ +public: + ~XdgShell() override; +}; + +/** + * The XdgSurface class represents an xdg_surface object. + */ +class XdgSurface : public QObject, public QtWayland::xdg_surface +{ + Q_OBJECT + +public: + explicit XdgSurface(XdgShell *shell, KWayland::Client::Surface *surface, QObject *parent = nullptr); + ~XdgSurface() override; + + KWayland::Client::Surface *surface() const; + +Q_SIGNALS: + void configureRequested(quint32 serial); + +protected: + void xdg_surface_configure(uint32_t serial) override; + +private: + KWayland::Client::Surface *m_surface; +}; + +/** + * The XdgToplevel class represents an xdg_toplevel surface. Note that the XdgToplevel surface + * takes the ownership of the underlying XdgSurface object. + */ +class XdgToplevel : public QObject, public QtWayland::xdg_toplevel +{ + Q_OBJECT + +public: + enum class State { + Maximized = 1 << 0, + Fullscreen = 1 << 1, + Resizing = 1 << 2, + Activated = 1 << 3 + }; + Q_DECLARE_FLAGS(States, State) + + explicit XdgToplevel(XdgSurface *surface, QObject *parent = nullptr); + ~XdgToplevel() override; + + XdgSurface *xdgSurface() const; + +Q_SIGNALS: + void configureRequested(const QSize &size, KWin::Test::XdgToplevel::States states); + void closeRequested(); + +protected: + void xdg_toplevel_configure(int32_t width, int32_t height, wl_array *states) override; + void xdg_toplevel_close() override; + +private: + QScopedPointer m_xdgSurface; +}; + +/** + * The XdgPositioner class represents an xdg_positioner object. + */ +class XdgPositioner : public QtWayland::xdg_positioner +{ +public: + explicit XdgPositioner(XdgShell *shell); + ~XdgPositioner() override; +}; + +/** + * The XdgPopup class represents an xdg_popup surface. Note that the XdgPopup surface takes + * the ownership of the underlying XdgSurface object. + */ +class XdgPopup : public QObject, public QtWayland::xdg_popup +{ + Q_OBJECT + +public: + XdgPopup(XdgSurface *surface, XdgSurface *parentSurface, XdgPositioner *positioner, QObject *parent = nullptr); + ~XdgPopup() override; + + XdgSurface *xdgSurface() const; + +Q_SIGNALS: + void configureRequested(const QRect &rect); + +protected: + void xdg_popup_configure(int32_t x, int32_t y, int32_t width, int32_t height) override; + +private: + QScopedPointer m_xdgSurface; +}; + enum class AdditionalWaylandInterface { Seat = 1 << 0, Decoration = 1 << 1, @@ -188,6 +289,15 @@ KWayland::Client::XdgShellPopup *createXdgShellStablePopup(KWayland::Client::Sur QObject *parent = nullptr, CreationSetup = CreationSetup::CreateAndConfigure); +XdgToplevel *createXdgToplevelSurface(KWayland::Client::Surface *surface, QObject *parent = nullptr, + CreationSetup configureMode = CreationSetup::CreateAndConfigure); + +XdgPositioner *createXdgPositioner(); + +XdgPopup *createXdgPopupSurface(KWayland::Client::Surface *surface, XdgSurface *parentSurface, + XdgPositioner *positioner, QObject *parent = nullptr, + CreationSetup configureMode = CreationSetup::CreateAndConfigure); + /** * Commits the XdgShellSurface to the given surface, and waits for the configure event from the compositor @@ -240,6 +350,7 @@ bool unlockScreen(); } Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::Test::AdditionalWaylandInterfaces) +Q_DECLARE_METATYPE(KWin::Test::XdgToplevel::States) #define WAYLANDTEST_MAIN_HELPER(TestObject, DPI, OperationMode) \ int main(int argc, char *argv[]) \ diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp index 92d16315f7..8c7d8ff299 100644 --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -74,6 +74,114 @@ void LayerSurfaceV1::zwlr_layer_surface_v1_closed() emit closeRequested(); } +XdgShell::~XdgShell() +{ + destroy(); +} + +XdgSurface::XdgSurface(XdgShell *shell, Surface *surface, QObject *parent) + : QObject(parent) + , QtWayland::xdg_surface(shell->get_xdg_surface(*surface)) + , m_surface(surface) +{ +} + +XdgSurface::~XdgSurface() +{ + destroy(); +} + +Surface *XdgSurface::surface() const +{ + return m_surface; +} + +void XdgSurface::xdg_surface_configure(uint32_t serial) +{ + emit configureRequested(serial); +} + +XdgToplevel::XdgToplevel(XdgSurface *surface, QObject *parent) + : QObject(parent) + , QtWayland::xdg_toplevel(surface->get_toplevel()) + , m_xdgSurface(surface) +{ +} + +XdgToplevel::~XdgToplevel() +{ + destroy(); +} + +XdgSurface *XdgToplevel::xdgSurface() const +{ + return m_xdgSurface.data(); +} + +void XdgToplevel::xdg_toplevel_configure(int32_t width, int32_t height, wl_array *states) +{ + States requestedStates; + + const uint32_t *stateData = static_cast(states->data); + const size_t stateCount = states->size / sizeof(uint32_t); + + for (size_t i = 0; i < stateCount; ++i) { + switch (stateData[i]) { + case QtWayland::xdg_toplevel::state_maximized: + requestedStates |= State::Maximized; + break; + case QtWayland::xdg_toplevel::state_fullscreen: + requestedStates |= State::Fullscreen; + break; + case QtWayland::xdg_toplevel::state_resizing: + requestedStates |= State::Resizing; + break; + case QtWayland::xdg_toplevel::state_activated: + requestedStates |= State::Activated; + break; + } + } + + emit configureRequested(QSize(width, height), requestedStates); +} + +void XdgToplevel::xdg_toplevel_close() +{ + emit closeRequested(); +} + +XdgPositioner::XdgPositioner(XdgShell *shell) + : QtWayland::xdg_positioner(shell->create_positioner()) +{ +} + +XdgPositioner::~XdgPositioner() +{ + destroy(); +} + +XdgPopup::XdgPopup(XdgSurface *surface, XdgSurface *parentSurface, XdgPositioner *positioner, QObject *parent) + : QObject(parent) + , QtWayland::xdg_popup(surface->get_popup(parentSurface->object(), positioner->object())) + , m_xdgSurface(surface) +{ +} + +XdgPopup::~XdgPopup() +{ + destroy(); +} + +XdgSurface *XdgPopup::xdgSurface() const +{ + return m_xdgSurface.data(); +} + +void XdgPopup::xdg_popup_configure(int32_t x, int32_t y, int32_t width, int32_t height) +{ + emit configureRequested(QRect(x, y, width, height)); +} + static struct { ConnectionThread *connection = nullptr; EventQueue *queue = nullptr; @@ -81,7 +189,8 @@ static struct { SubCompositor *subCompositor = nullptr; ServerSideDecorationManager *decoration = nullptr; ShadowManager *shadowManager = nullptr; - XdgShell *xdgShellStable = nullptr; + KWayland::Client::XdgShell *xdgShellStable = nullptr; + XdgShell *xdgShell = nullptr; ShmPool *shm = nullptr; Seat *seat = nullptr; PlasmaShell *plasmaShell = nullptr; @@ -213,6 +322,10 @@ bool setupWaylandConnection(AdditionalWaylandInterfaces flags) s_waylandConnection.layerShellV1->init(*registry, name, version); } } + if (interface == QByteArrayLiteral("xdg_wm_base")) { + s_waylandConnection.xdgShell = new XdgShell(); + s_waylandConnection.xdgShell->init(*registry, name, version); + } }); QSignalSpy allAnnounced(registry, &Registry::interfacesAnnounced); @@ -341,6 +454,8 @@ void destroyWaylandConnection() s_waylandConnection.pointerConstraints = nullptr; delete s_waylandConnection.xdgShellStable; s_waylandConnection.xdgShellStable = nullptr; + delete s_waylandConnection.xdgShell; + s_waylandConnection.xdgShell = nullptr; delete s_waylandConnection.shadowManager; s_waylandConnection.shadowManager = nullptr; delete s_waylandConnection.idleInhibit; @@ -617,7 +732,7 @@ QtWayland::zwp_input_panel_surface_v1 *createInputPanelSurfaceV1(Surface *surfac return s; } -XdgShellPopup *createXdgShellStablePopup(Surface *surface, XdgShellSurface *parentSurface, const XdgPositioner &positioner, QObject *parent, CreationSetup creationSetup) +XdgShellPopup *createXdgShellStablePopup(Surface *surface, XdgShellSurface *parentSurface, const KWayland::Client::XdgPositioner &positioner, QObject *parent, CreationSetup creationSetup) { if (!s_waylandConnection.xdgShellStable) { return nullptr; @@ -653,6 +768,68 @@ void initXdgShellPopup(KWayland::Client::Surface *surface, KWayland::Client::Xdg shellPopup->ackConfigure(configureRequestedSpy.last()[1].toInt()); } +static void waitForConfigured(XdgSurface *shellSurface) +{ + QSignalSpy surfaceConfigureRequestedSpy(shellSurface, &XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.isValid()); + + shellSurface->surface()->commit(Surface::CommitFlag::None); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + shellSurface->ack_configure(surfaceConfigureRequestedSpy.last().first().toUInt()); +} + +XdgToplevel *createXdgToplevelSurface(Surface *surface, QObject *parent, CreationSetup configureMode) +{ + XdgShell *shell = s_waylandConnection.xdgShell; + + if (!shell) { + qWarning() << "Could not create an xdg_toplevel surface because xdg_wm_base global is not bound"; + return nullptr; + } + + XdgSurface *xdgSurface = new XdgSurface(shell, surface, parent); + XdgToplevel *xdgToplevel = new XdgToplevel(xdgSurface, parent); + + if (configureMode == CreationSetup::CreateAndConfigure) { + waitForConfigured(xdgSurface); + } + + return xdgToplevel; +} + +XdgPositioner *createXdgPositioner() +{ + XdgShell *shell = s_waylandConnection.xdgShell; + + if (!shell) { + qWarning() << "Could not create an xdg_positioner object because xdg_wm_base global is not bound"; + return nullptr; + } + + return new XdgPositioner(shell); +} + +XdgPopup *createXdgPopupSurface(Surface *surface, XdgSurface *parentSurface, XdgPositioner *positioner, + QObject *parent, CreationSetup configureMode) +{ + XdgShell *shell = s_waylandConnection.xdgShell; + + if (!shell) { + qWarning() << "Could not create an xdg_popup surface because xdg_wm_base global is not bound"; + return nullptr; + } + + XdgSurface *xdgSurface = new XdgSurface(shell, surface, parent); + XdgPopup *xdgPopup = new XdgPopup(xdgSurface, parentSurface, positioner, parent); + + if (configureMode == CreationSetup::CreateAndConfigure) { + waitForConfigured(xdgSurface); + } + + return xdgPopup; +} + bool waitForWindowDestroyed(AbstractClient *client) { QSignalSpy destroyedSpy(client, &QObject::destroyed);