// Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "mockcompositor.h" #include #include #include #include #include using namespace MockCompositor; constexpr int xdgDecorationVersion = 1; // protocol VERSION, not the name suffix (_v1) class XdgDecorationManagerV1; class XdgToplevelDecorationV1 : public QObject, public QtWaylandServer::zxdg_toplevel_decoration_v1 { Q_OBJECT public: explicit XdgToplevelDecorationV1(XdgDecorationManagerV1 *manager, XdgToplevel *toplevel, int id, int version) : zxdg_toplevel_decoration_v1(toplevel->resource()->client(), id, version) , m_manager(manager) , m_toplevel(toplevel) { } void sendConfigure(mode mode) { if (!m_configureSent) { // Attaching buffers before the configure is a protocol error QVERIFY(!m_toplevel->surface()->m_pending.buffer); QVERIFY(!m_toplevel->surface()->m_committed.buffer); } send_configure(mode); m_configureSent = true; } void zxdg_toplevel_decoration_v1_destroy(Resource *resource) override { wl_resource_destroy(resource->handle); } void zxdg_toplevel_decoration_v1_destroy_resource(Resource *resource) override; void zxdg_toplevel_decoration_v1_set_mode(Resource *resource, uint32_t mode) override { Q_UNUSED(resource); m_unsetModeRequested = false; m_requestedMode = XdgToplevelDecorationV1::mode(mode); } void zxdg_toplevel_decoration_v1_unset_mode(Resource *resource) override { Q_UNUSED(resource); m_unsetModeRequested = true; m_requestedMode = mode(0); } XdgDecorationManagerV1 *m_manager = nullptr; XdgToplevel *m_toplevel = nullptr; mode m_requestedMode = mode(0); bool m_unsetModeRequested = false; bool m_configureSent = false; }; class XdgDecorationManagerV1 : public Global, public QtWaylandServer::zxdg_decoration_manager_v1 { Q_OBJECT public: explicit XdgDecorationManagerV1(CoreCompositor *compositor, int version = 1) : QtWaylandServer::zxdg_decoration_manager_v1(compositor->m_display, version) , m_version(version) {} bool isClean() override { return m_decorations.empty(); } XdgToplevelDecorationV1 *decorationFor(XdgToplevel *toplevel) { return m_decorations.value(toplevel, nullptr); } int m_version = 1; // TODO: Remove on libwayland upgrade QMap m_decorations; protected: void zxdg_decoration_manager_v1_destroy(Resource *resource) override { //TODO: Should the decorations be destroyed at this point? wl_resource_destroy(resource->handle); } void zxdg_decoration_manager_v1_get_toplevel_decoration(Resource *resource, uint32_t id, ::wl_resource *toplevelResource) override { auto *toplevel = fromResource(toplevelResource); QVERIFY(toplevel); QVERIFY(!decorationFor(toplevel)); // Attaching buffers before the configure is a protocol error QVERIFY(!toplevel->surface()->m_pending.buffer); QVERIFY(!toplevel->surface()->m_committed.buffer); m_decorations[toplevel] = new XdgToplevelDecorationV1(this, toplevel, id, resource->version()); } }; void XdgToplevelDecorationV1::zxdg_toplevel_decoration_v1_destroy_resource(QtWaylandServer::zxdg_toplevel_decoration_v1::Resource *resource) { Q_UNUSED(resource); int removed = m_manager->m_decorations.remove(m_toplevel); Q_ASSERT(removed == 1); delete this; } class XdgDecorationCompositor : public DefaultCompositor { public: explicit XdgDecorationCompositor() { exec([this] { m_config.autoConfigure = true; add(xdgDecorationVersion); }); } XdgToplevelDecorationV1 *toplevelDecoration(int i = 0) { return get()->decorationFor(xdgToplevel(i)); } }; class tst_xdgdecorationv1 : public QObject, private XdgDecorationCompositor { Q_OBJECT private slots: void initTestCase(); void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); } void clientSidePreferredByCompositor(); void initialFramelessWindowHint(); void delayedFramelessWindowHint(); }; void tst_xdgdecorationv1::initTestCase() { if (qEnvironmentVariableIntValue("QT_WAYLAND_DISABLE_WINDOWDECORATION")) QSKIP("This test doesn't make sense when QT_WAYLAND_DISABLE_WINDOWDECORATION is set in the environment"); } void tst_xdgdecorationv1::clientSidePreferredByCompositor() { QRasterWindow window; window.show(); QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().size(), 1); QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().first()->version(), xdgDecorationVersion); QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()); // The client creates a toplevel object // Check that we don't assume decorations before the server has configured them QVERIFY(window.frameMargins().isNull()); QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()->m_unsetModeRequested); QVERIFY(window.frameMargins().isNull()); // We're still waiting for a configure exec([=] { toplevelDecoration()->sendConfigure(XdgToplevelDecorationV1::mode_client_side); xdgToplevel()->sendCompleteConfigure(); }); QTRY_VERIFY(!window.frameMargins().isNull()); } void tst_xdgdecorationv1::initialFramelessWindowHint() { QRasterWindow window; window.setFlag(Qt::FramelessWindowHint, true); window.show(); QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().size(), 1); QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); exec([=]{ xdgToplevel()->sendCompleteConfigure(); }); QCOMPOSITOR_TRY_VERIFY(xdgSurface()->m_committedConfigureSerial); // The client should not have create a decoration object, because that allows the compositor // to override our decision and add server side decorations to our window. QCOMPOSITOR_TRY_VERIFY(!toplevelDecoration()); } void tst_xdgdecorationv1::delayedFramelessWindowHint() { QRasterWindow window; window.show(); QCOMPOSITOR_TRY_COMPARE(get()->resourceMap().size(), 1); QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); exec([=]{ xdgToplevel()->sendCompleteConfigure(); }); QCOMPOSITOR_TRY_VERIFY(xdgSurface()->m_committedConfigureSerial); QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()); window.setFlag(Qt::FramelessWindowHint, true); // The client should now destroy the decoration object, so the compositor is no longer // able to force window decorations QCOMPOSITOR_TRY_VERIFY(!toplevelDecoration()); } QCOMPOSITOR_TEST_MAIN(tst_xdgdecorationv1) #include "tst_xdgdecorationv1.moc"