Support XDG v6

Summary:

The main clever part that's not just boring boiler plate is how we
handle the structure change
A surface now has an XDGSurface which then has a an Xdg TopLevel or a
Xdg Popup

We need to fit this into the public API which assumes a surface has a
Surface or a Popup.
The old Surface is similar to the new TopLevel.

The shoehorning works by relying on the fact that a surface without a
role is pretty useless.

Clients create the surface implicitly with the toplevel or implicitly
with the popup.
The server only announced it has a new "XdgSurface" when it gets a new
zxdg_surface_get_toplevel.

----

Popup decisions:
- On popup creation the server should copy the current info from the
positioner and then it gets deleted. Given kwaylands job is to keep
state, we expose all these parameter via popup.

- Due to this positioner is not exposed as a resource anywhere.

- Server API is 100% backwards compatiable.
i.e new code will work identically with v5 clients.

- Client API is not.  Grabs are called separately from the constructor,
and the parent surface changed to an xdgsurface, not a raw surface.
V5 code still works as-is, just not with the new constructors.

It seemed better to match the v6 (and what will be the stable v7) than
to try and do hacks and lose functionality.
Given the client needs to change the code to opt into V6 anyway. I don't
think this is a huge problem.

Test Plan: Current test still passes.

Reviewers: #plasma, graesslin

Reviewed By: #plasma, graesslin

Subscribers: graesslin, mart, plasma-devel, #frameworks

Tags: #frameworks, #plasma_on_wayland

Differential Revision: https://phabricator.kde.org/D6047
master
David Edmundson 7 years ago
parent f1491e009e
commit 3c67cc1399

@ -45,6 +45,7 @@ set(SERVER_LIB_SRCS
textinput_interface_v2.cpp
xdgshell_interface.cpp
xdgshell_v5_interface.cpp
xdgshell_v6_interface.cpp
)
ecm_add_wayland_server_protocol(SERVER_LIB_SRCS
@ -131,6 +132,11 @@ ecm_add_wayland_server_protocol(SERVER_LIB_SRCS
BASENAME xdg-shell-v5
)
ecm_add_wayland_server_protocol(SERVER_LIB_SRCS
PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/xdg-shell-unstable-v6.xml
BASENAME xdg-shell-v6
)
ecm_add_wayland_server_protocol(SERVER_LIB_SRCS
PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/pointer-gestures-unstable-v1.xml
BASENAME pointer-gestures-unstable-v1

@ -342,12 +342,25 @@ ecm_mark_as_test(testSelection)
########################################################
set( testXdgShellV5_SRCS
test_xdg_shell.cpp
test_xdg_shell_v5.cpp
)
add_executable(testXdgShellV5 ${testXdgShellV5_SRCS})
target_link_libraries( testXdgShellV5 Qt5::Test Qt5::Gui KF5::WaylandServer KF5::WaylandClient Wayland::Client)
add_test(NAME kwayland-testXdgShellV5 COMMAND testXdgShellV5)
ecm_mark_as_test(testXdgShellV5)
########################################################
# Test XdgShellV6
########################################################
set( testXdgShellV6_SRCS
test_xdg_shell.cpp
test_xdg_shell_v6.cpp
)
add_executable(testXdgShellV6 ${testXdgShellV6_SRCS})
target_link_libraries( testXdgShellV6 Qt5::Test Qt5::Gui KF5::WaylandServer KF5::WaylandClient Wayland::Client)
add_test(kwayland-testXdgShellV6 testXdgShellV6)
ecm_mark_as_test(testXdgShellV6)
########################################################
# Test Pointer Constraints
########################################################

@ -17,72 +17,12 @@ 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 <http://www.gnu.org/licenses/>.
*********************************************************************/
// Qt
#include <QtTest/QtTest>
// client
#include "../../src/client/xdgshell.h"
#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/output.h"
#include "../../src/client/seat.h"
#include "../../src/client/shm_pool.h"
#include "../../src/client/surface.h"
// server
#include "../../src/server/display.h"
#include "../../src/server/compositor_interface.h"
#include "../../src/server/output_interface.h"
#include "../../src/server/seat_interface.h"
#include "../../src/server/surface_interface.h"
#include "../../src/server/xdgshell_interface.h"
using namespace KWayland::Client;
using namespace KWayland::Server;
Q_DECLARE_METATYPE(Qt::MouseButton)
class XdgShellTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void init();
void cleanup();
void testCreateSurface();
void testTitle();
void testWindowClass();
void testMaximize();
void testMinimize();
void testFullscreen();
void testShowWindowMenu();
void testMove();
void testResize_data();
void testResize();
void testTransient();
void testClose();
void testConfigureStates_data();
void testConfigureStates();
void testConfigureMultipleAcks();
void testPopup();
private:
Display *m_display = nullptr;
CompositorInterface *m_compositorInterface = nullptr;
OutputInterface *m_o1Interface = nullptr;
OutputInterface *m_o2Interface = nullptr;
SeatInterface *m_seatInterface = nullptr;
XdgShellInterface *m_xdgShellInterface = nullptr;
ConnectionThread *m_connection = nullptr;
QThread *m_thread = nullptr;
EventQueue *m_queue = nullptr;
Compositor *m_compositor = nullptr;
ShmPool *m_shmPool = nullptr;
XdgShell *m_xdgShell = nullptr;
Output *m_output1 = nullptr;
Output *m_output2 = nullptr;
Seat *m_seat = nullptr;
};
#include "test_xdg_shell.h"
XdgShellTest::XdgShellTest(XdgShellInterfaceVersion version):
m_version(version)
{}
static const QString s_socketName = QStringLiteral("kwayland-test-xdg_shell-0");
@ -107,8 +47,8 @@ void XdgShellTest::init()
m_seatInterface->create();
m_compositorInterface = m_display->createCompositor(m_display);
m_compositorInterface->create();
m_xdgShellInterface = m_display->createXdgShell(XdgShellInterfaceVersion::UnstableV5, m_display);
QCOMPARE(m_xdgShellInterface->interfaceVersion(), XdgShellInterfaceVersion::UnstableV5);
m_xdgShellInterface = m_display->createXdgShell(m_version, m_display);
QCOMPARE(m_xdgShellInterface->interfaceVersion(), m_version);
m_xdgShellInterface->create();
// setup connection
@ -134,7 +74,11 @@ void XdgShellTest::init()
QVERIFY(interfaceAnnouncedSpy.isValid());
QSignalSpy outputAnnouncedSpy(&registry, &Registry::outputAnnounced);
QVERIFY(outputAnnouncedSpy.isValid());
QSignalSpy xdgShellAnnouncedSpy(&registry, &Registry::xdgShellUnstableV5Announced);
auto shellAnnouncedSignal = m_version == XdgShellInterfaceVersion::UnstableV5 ?
&Registry::xdgShellUnstableV5Announced : &Registry::xdgShellUnstableV6Announced;
QSignalSpy xdgShellAnnouncedSpy(&registry, shellAnnouncedSignal);
QVERIFY(xdgShellAnnouncedSpy.isValid());
registry.setEventQueue(m_queue);
registry.create(m_connection);
@ -160,8 +104,10 @@ void XdgShellTest::init()
QCOMPARE(xdgShellAnnouncedSpy.count(), 1);
m_xdgShell = registry.createXdgShell(registry.interface(Registry::Interface::XdgShellUnstableV5).name,
registry.interface(Registry::Interface::XdgShellUnstableV5).version,
Registry::Interface iface = m_version == XdgShellInterfaceVersion::UnstableV5 ? Registry::Interface::XdgShellUnstableV5 : Registry::Interface::XdgShellUnstableV6;
m_xdgShell = registry.createXdgShell(registry.interface(iface).name,
registry.interface(iface).version,
this);
QVERIFY(m_xdgShell);
QVERIFY(m_xdgShell->isValid());
@ -238,16 +184,6 @@ void XdgShellTest::testCreateSurface()
QVERIFY(destroyedSpy.wait());
}
#define SURFACE \
QSignalSpy xdgSurfaceCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated); \
QVERIFY(xdgSurfaceCreatedSpy.isValid()); \
QScopedPointer<Surface> surface(m_compositor->createSurface()); \
QScopedPointer<XdgShellSurface> xdgSurface(m_xdgShell->createSurface(surface.data())); \
QCOMPARE(xdgSurface->size(), QSize()); \
QVERIFY(xdgSurfaceCreatedSpy.wait()); \
auto serverXdgSurface = xdgSurfaceCreatedSpy.first().first().value<XdgShellSurfaceInterface*>(); \
QVERIFY(serverXdgSurface);
void XdgShellTest::testTitle()
{
// this test verifies that we can change the title of a shell surface
@ -464,6 +400,30 @@ void XdgShellTest::testTransient()
QVERIFY(!serverXdgSurface->isTransient());
}
void XdgShellTest::testPing()
{
// this test verifies that a ping request is sent to the client
SURFACE
QSignalSpy pingSpy(m_xdgShellInterface, &XdgShellInterface::pongReceived);
QVERIFY(pingSpy.isValid());
quint32 serial = m_xdgShellInterface->ping(serverXdgSurface);
QVERIFY(pingSpy.wait());
QCOMPARE(pingSpy.count(), 1);
QCOMPARE(pingSpy.takeFirst().at(0).value<quint32>(), serial);
// test of a ping failure
// disconnecting the connection thread to the queue will break the connection and pings will do a timeout
disconnect(m_connection, &ConnectionThread::eventsRead, m_queue, &EventQueue::dispatch);
m_xdgShellInterface->ping(serverXdgSurface);
QSignalSpy pingDelayedSpy(m_xdgShellInterface, &XdgShellInterface::pingDelayed);
QVERIFY(pingDelayedSpy.wait());
QSignalSpy pingTimeoutSpy(m_xdgShellInterface, &XdgShellInterface::pingTimeout);
QVERIFY(pingTimeoutSpy.wait());
}
void XdgShellTest::testClose()
{
// this test verifies that a close request is sent to the client
@ -599,51 +559,4 @@ void XdgShellTest::testConfigureMultipleAcks()
QCOMPARE(xdgSurface->size(), QSize(30, 40));
}
void XdgShellTest::testPopup()
{
// this test verifies that the creation of popups works correctly
SURFACE
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
QVERIFY(surfaceCreatedSpy.isValid());
QSignalSpy xdgPopupSpy(m_xdgShellInterface, &XdgShellInterface::popupCreated);
QVERIFY(xdgPopupSpy.isValid());
QScopedPointer<Surface> popupSurface(m_compositor->createSurface());
QVERIFY(surfaceCreatedSpy.wait());
// TODO: proper serial
QScopedPointer<XdgShellPopup> xdgPopup(m_xdgShell->createPopup(popupSurface.data(), surface.data(), m_seat, 120, QPoint(10, 20)));
QVERIFY(xdgPopupSpy.wait());
QCOMPARE(xdgPopupSpy.count(), 1);
QCOMPARE(xdgPopupSpy.first().at(1).value<SeatInterface*>(), m_seatInterface);
QCOMPARE(xdgPopupSpy.first().at(2).value<quint32>(), 120u);
auto serverXdgPopup = xdgPopupSpy.first().first().value<XdgShellPopupInterface*>();
QVERIFY(serverXdgPopup);
QCOMPARE(serverXdgPopup->surface(), surfaceCreatedSpy.first().first().value<SurfaceInterface*>());
QCOMPARE(serverXdgPopup->transientFor().data(), serverXdgSurface->surface());
QCOMPARE(serverXdgPopup->transientOffset(), QPoint(10, 20));
// now also a popup for the popup
QScopedPointer<Surface> popup2Surface(m_compositor->createSurface());
QScopedPointer<XdgShellPopup> xdgPopup2(m_xdgShell->createPopup(popup2Surface.data(), popupSurface.data(), m_seat, 121, QPoint(5, 7)));
QVERIFY(xdgPopupSpy.wait());
QCOMPARE(xdgPopupSpy.count(), 2);
QCOMPARE(xdgPopupSpy.last().at(1).value<SeatInterface*>(), m_seatInterface);
QCOMPARE(xdgPopupSpy.last().at(2).value<quint32>(), 121u);
auto serverXdgPopup2 = xdgPopupSpy.last().first().value<XdgShellPopupInterface*>();
QVERIFY(serverXdgPopup2);
QCOMPARE(serverXdgPopup2->surface(), surfaceCreatedSpy.last().first().value<SurfaceInterface*>());
QCOMPARE(serverXdgPopup2->transientFor().data(), serverXdgPopup->surface());
QCOMPARE(serverXdgPopup2->transientOffset(), QPoint(5, 7));
QSignalSpy popup2DoneSpy(xdgPopup2.data(), &XdgShellPopup::popupDone);
QVERIFY(popup2DoneSpy.isValid());
serverXdgPopup2->popupDone();
QVERIFY(popup2DoneSpy.wait());
// TODO: test that this sends also the done to all parents
}
QTEST_GUILESS_MAIN(XdgShellTest)
#include "test_xdg_shell.moc"

@ -0,0 +1,101 @@
/********************************************************************
Copyright 2016 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
// Qt
#include <QtTest/QtTest>
// client
#include "../../src/client/xdgshell.h"
#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/output.h"
#include "../../src/client/seat.h"
#include "../../src/client/shm_pool.h"
#include "../../src/client/surface.h"
// server
#include "../../src/server/display.h"
#include "../../src/server/compositor_interface.h"
#include "../../src/server/output_interface.h"
#include "../../src/server/seat_interface.h"
#include "../../src/server/surface_interface.h"
#include "../../src/server/xdgshell_interface.h"
using namespace KWayland::Client;
using namespace KWayland::Server;
Q_DECLARE_METATYPE(Qt::MouseButton)
class XdgShellTest : public QObject
{
Q_OBJECT
protected:
XdgShellTest(XdgShellInterfaceVersion version);
private Q_SLOTS:
void init();
void cleanup();
void testCreateSurface();
void testTitle();
void testWindowClass();
void testMaximize();
void testMinimize();
void testFullscreen();
void testShowWindowMenu();
void testMove();
void testResize_data();
void testResize();
void testTransient();
void testPing();
void testClose();
void testConfigureStates_data();
void testConfigureStates();
void testConfigureMultipleAcks();
protected:
XdgShellInterface *m_xdgShellInterface = nullptr;
Compositor *m_compositor = nullptr;
XdgShell *m_xdgShell = nullptr;
Display *m_display = nullptr;
CompositorInterface *m_compositorInterface = nullptr;
OutputInterface *m_o1Interface = nullptr;
OutputInterface *m_o2Interface = nullptr;
SeatInterface *m_seatInterface = nullptr;
ConnectionThread *m_connection = nullptr;
QThread *m_thread = nullptr;
EventQueue *m_queue = nullptr;
ShmPool *m_shmPool = nullptr;
Output *m_output1 = nullptr;
Output *m_output2 = nullptr;
Seat *m_seat = nullptr;
private:
XdgShellInterfaceVersion m_version;
};
#define SURFACE \
QSignalSpy xdgSurfaceCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated); \
QVERIFY(xdgSurfaceCreatedSpy.isValid()); \
QScopedPointer<Surface> surface(m_compositor->createSurface()); \
QScopedPointer<XdgShellSurface> xdgSurface(m_xdgShell->createSurface(surface.data())); \
QCOMPARE(xdgSurface->size(), QSize()); \
QVERIFY(xdgSurfaceCreatedSpy.wait()); \
auto serverXdgSurface = xdgSurfaceCreatedSpy.first().first().value<XdgShellSurfaceInterface*>(); \
QVERIFY(serverXdgSurface);

@ -0,0 +1,72 @@
#include "test_xdg_shell.h"
class XdgShellTestV5 : public XdgShellTest {
Q_OBJECT
public:
XdgShellTestV5() :
XdgShellTest(KWayland::Server::XdgShellInterfaceVersion::UnstableV5) {}
private Q_SLOTS:
void testPopup();
};
void XdgShellTestV5::testPopup()
{
// this test verifies that the creation of popups works correctly
SURFACE
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
QVERIFY(surfaceCreatedSpy.isValid());
QSignalSpy xdgPopupSpy(m_xdgShellInterface, &XdgShellInterface::popupCreated);
//check as well as the compat signal, the new signal is also fired
QSignalSpy xdgPopupSpyNew(m_xdgShellInterface, &XdgShellInterface::xdgPopupCreated);
QVERIFY(xdgPopupSpy.isValid());
QScopedPointer<Surface> popupSurface(m_compositor->createSurface());
QVERIFY(surfaceCreatedSpy.wait());
// TODO: proper serial
QScopedPointer<XdgShellPopup> xdgPopup(m_xdgShell->createPopup(popupSurface.data(), surface.data(), m_seat, 120, QPoint(10, 20)));
QVERIFY(xdgPopupSpy.wait());
QCOMPARE(xdgPopupSpy.count(), 1);
QCOMPARE(xdgPopupSpyNew.count(), 1);
QCOMPARE(xdgPopupSpy.first().at(1).value<SeatInterface*>(), m_seatInterface);
QCOMPARE(xdgPopupSpy.first().at(2).value<quint32>(), 120u);
auto serverXdgPopup = xdgPopupSpy.first().first().value<XdgShellPopupInterface*>();
QVERIFY(serverXdgPopup);
QCOMPARE(serverXdgPopup->surface(), surfaceCreatedSpy.first().first().value<SurfaceInterface*>());
QCOMPARE(serverXdgPopup->transientFor().data(), serverXdgSurface->surface());
QCOMPARE(serverXdgPopup->transientOffset(), QPoint(10, 20));
// now also a popup for the popup
QScopedPointer<Surface> popup2Surface(m_compositor->createSurface());
QScopedPointer<XdgShellPopup> xdgPopup2(m_xdgShell->createPopup(popup2Surface.data(), popupSurface.data(), m_seat, 121, QPoint(5, 7)));
QVERIFY(xdgPopupSpy.wait());
QCOMPARE(xdgPopupSpy.count(), 2);
QCOMPARE(xdgPopupSpy.last().at(1).value<SeatInterface*>(), m_seatInterface);
QCOMPARE(xdgPopupSpy.last().at(2).value<quint32>(), 121u);
auto serverXdgPopup2 = xdgPopupSpy.last().first().value<XdgShellPopupInterface*>();
QVERIFY(serverXdgPopup2);
QCOMPARE(serverXdgPopup2->surface(), surfaceCreatedSpy.last().first().value<SurfaceInterface*>());
QCOMPARE(serverXdgPopup2->transientFor().data(), serverXdgPopup->surface());
QCOMPARE(serverXdgPopup2->transientOffset(), QPoint(5, 7));
QSignalSpy popup2DoneSpy(xdgPopup2.data(), &XdgShellPopup::popupDone);
QVERIFY(popup2DoneSpy.isValid());
serverXdgPopup2->popupDone();
QVERIFY(popup2DoneSpy.wait());
// TODO: test that this sends also the done to all parents
}
QTEST_GUILESS_MAIN(XdgShellTestV5)
#include "test_xdg_shell_v5.moc"

@ -0,0 +1,201 @@
#include "test_xdg_shell.h"
#include <wayland-xdg-shell-v6-client-protocol.h>
class XdgShellTestV6 : public XdgShellTest {
Q_OBJECT
public:
XdgShellTestV6() :
XdgShellTest(KWayland::Server::XdgShellInterfaceVersion::UnstableV6) {}
private Q_SLOTS:
void testMaxSize();
void testMinSize();
void testPopup_data();
void testPopup();
void testMultipleRoles1();
void testMultipleRoles2();
};
void XdgShellTestV6::testMaxSize()
{
qRegisterMetaType<OutputInterface*>();
// this test verifies changing the window maxSize
QSignalSpy xdgSurfaceCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated);
QVERIFY(xdgSurfaceCreatedSpy.isValid());
QScopedPointer<Surface> surface(m_compositor->createSurface());
QScopedPointer<XdgShellSurface> xdgSurface(m_xdgShell->createSurface(surface.data()));
QVERIFY(xdgSurfaceCreatedSpy.wait());
auto serverXdgSurface = xdgSurfaceCreatedSpy.first().first().value<XdgShellSurfaceInterface*>();
QVERIFY(serverXdgSurface);
QSignalSpy maxSizeSpy(serverXdgSurface, &XdgShellSurfaceInterface::maxSizeChanged);
QVERIFY(maxSizeSpy.isValid());
xdgSurface->setMaxSize(QSize(100, 100));
QVERIFY(maxSizeSpy.wait());
QCOMPARE(maxSizeSpy.count(), 1);
QCOMPARE(maxSizeSpy.last().at(0).value<QSize>(), QSize(100,100));
xdgSurface->setMaxSize(QSize(200, 200));
QVERIFY(maxSizeSpy.wait());
QCOMPARE(maxSizeSpy.count(), 2);
QCOMPARE(maxSizeSpy.last().at(0).value<QSize>(), QSize(200,200));
}
void XdgShellTestV6::testPopup_data()
{
QTest::addColumn<XdgPositioner>("positioners");
XdgPositioner positioner(QSize(10,10), QRect(100,100,50,50));
QTest::newRow("default") << positioner;
XdgPositioner positioner2(QSize(20,20), QRect(101,102,51,52));
QTest::newRow("sizeAndAnchorRect") << positioner2;
positioner.setAnchorEdge(Qt::TopEdge | Qt::RightEdge);
QTest::newRow("anchorEdge") << positioner;
positioner.setGravity(Qt::BottomEdge);
QTest::newRow("gravity") << positioner;
positioner.setGravity(Qt::TopEdge | Qt::RightEdge);
QTest::newRow("gravity2") << positioner;
positioner.setConstraints(XdgPositioner::Constraint::SlideX | XdgPositioner::Constraint::FlipY);
QTest::newRow("constraints") << positioner;
positioner.setConstraints(XdgPositioner::Constraint::SlideX | XdgPositioner::Constraint::SlideY | XdgPositioner::Constraint::FlipX | XdgPositioner::Constraint::FlipY | XdgPositioner::Constraint::ResizeX | XdgPositioner::Constraint::ResizeY);
QTest::newRow("constraints2") << positioner;
positioner.setAnchorOffset(QPoint(4,5));
QTest::newRow("offset") << positioner;
}
void XdgShellTestV6::testPopup()
{
QSignalSpy xdgTopLevelCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated);
QSignalSpy xdgPopupCreatedSpy(m_xdgShellInterface, &XdgShellInterface::xdgPopupCreated);
QScopedPointer<Surface> parentSurface(m_compositor->createSurface());
QScopedPointer<XdgShellSurface> xdgParentSurface(m_xdgShell->createSurface(parentSurface.data()));
QVERIFY(xdgTopLevelCreatedSpy.wait());
auto serverXdgTopLevel = xdgTopLevelCreatedSpy.first().first().value<XdgShellSurfaceInterface*>();
XdgPositioner positioner(QSize(10,10), QRect(100,100,50,50));
QScopedPointer<Surface> surface(m_compositor->createSurface());
QScopedPointer<XdgShellPopup> xdgSurface(m_xdgShell->createPopup(surface.data(), xdgParentSurface.data(), positioner));
QVERIFY(xdgPopupCreatedSpy.wait());
auto serverXdgPopup = xdgPopupCreatedSpy.first().first().value<XdgShellPopupInterface*>();
QVERIFY(serverXdgPopup);
QCOMPARE(serverXdgPopup->initialSize(), positioner.initialSize());
QCOMPARE(serverXdgPopup->anchorRect(), positioner.anchorRect());
QCOMPARE(serverXdgPopup->anchorEdge(), positioner.anchorEdge());
QCOMPARE(serverXdgPopup->gravity(), positioner.gravity());
QCOMPARE(serverXdgPopup->anchorOffset(), positioner.anchorOffset());
//we have different enums for client server, but they share the same values
QCOMPARE((int)serverXdgPopup->constraintAdjustments(), (int)positioner.constraints());
QCOMPARE(serverXdgPopup->transientFor().data(), serverXdgTopLevel->surface());
}
void XdgShellTestV6::testMinSize()
{
qRegisterMetaType<OutputInterface*>();
// this test verifies changing the window minSize
QSignalSpy xdgSurfaceCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated);
QVERIFY(xdgSurfaceCreatedSpy.isValid());
QScopedPointer<Surface> surface(m_compositor->createSurface());
QScopedPointer<XdgShellSurface> xdgSurface(m_xdgShell->createSurface(surface.data()));
QVERIFY(xdgSurfaceCreatedSpy.wait());
auto serverXdgSurface = xdgSurfaceCreatedSpy.first().first().value<XdgShellSurfaceInterface*>();
QVERIFY(serverXdgSurface);
QSignalSpy minSizeSpy(serverXdgSurface, &XdgShellSurfaceInterface::minSizeChanged);
QVERIFY(minSizeSpy.isValid());
xdgSurface->setMinSize(QSize(200, 200));
QVERIFY(minSizeSpy.wait());
QCOMPARE(minSizeSpy.count(), 1);
QCOMPARE(minSizeSpy.last().at(0).value<QSize>(), QSize(200,200));
xdgSurface->setMinSize(QSize(100, 100));
QVERIFY(minSizeSpy.wait());
QCOMPARE(minSizeSpy.count(), 2);
QCOMPARE(minSizeSpy.last().at(0).value<QSize>(), QSize(100,100));
}
//top level then toplevel
void XdgShellTestV6::testMultipleRoles1()
{
//setting multiple roles on an xdg surface should fail
QSignalSpy xdgSurfaceCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated);
QSignalSpy xdgPopupCreatedSpy(m_xdgShellInterface, &XdgShellInterface::xdgPopupCreated);
QVERIFY(xdgSurfaceCreatedSpy.isValid());
QVERIFY(xdgPopupCreatedSpy.isValid());
QScopedPointer<Surface> surface(m_compositor->createSurface());
//This is testing we work when a client does something stupid
//we can't use KWayland API here because by design that stops you from doing anything stupid
auto xdgSurface = zxdg_shell_v6_get_xdg_surface(*m_xdgShell, *surface.data());
//create a top level
auto xdgTopLevel1 = zxdg_surface_v6_get_toplevel(xdgSurface);
QVERIFY(xdgSurfaceCreatedSpy.wait());
//now try to create another top level for the same xdg surface. It should fail
auto xdgTopLevel2 = zxdg_surface_v6_get_toplevel(xdgSurface);
QVERIFY(!xdgSurfaceCreatedSpy.wait(10));
zxdg_toplevel_v6_destroy(xdgTopLevel1);
zxdg_toplevel_v6_destroy(xdgTopLevel2);
zxdg_surface_v6_destroy(xdgSurface);
}
//toplevel then popup
void XdgShellTestV6::testMultipleRoles2()
{
QSignalSpy xdgSurfaceCreatedSpy(m_xdgShellInterface, &XdgShellInterface::surfaceCreated);
QSignalSpy xdgPopupCreatedSpy(m_xdgShellInterface, &XdgShellInterface::xdgPopupCreated);
QVERIFY(xdgSurfaceCreatedSpy.isValid());
QVERIFY(xdgPopupCreatedSpy.isValid());
QScopedPointer<Surface> surface(m_compositor->createSurface());
QScopedPointer<Surface> parentSurface(m_compositor->createSurface());
auto parentXdgSurface = zxdg_shell_v6_get_xdg_surface(*m_xdgShell, *parentSurface.data());
auto xdgTopLevelParent = zxdg_surface_v6_get_toplevel(parentXdgSurface);
QVERIFY(xdgSurfaceCreatedSpy.wait());
auto xdgSurface = zxdg_shell_v6_get_xdg_surface(*m_xdgShell, *surface.data());
//create a top level
auto xdgTopLevel1 = zxdg_surface_v6_get_toplevel(xdgSurface);
QVERIFY(xdgSurfaceCreatedSpy.wait());
//now try to create a popup on the same xdg surface. It should fail
auto positioner = zxdg_shell_v6_create_positioner(*m_xdgShell);
zxdg_positioner_v6_set_anchor_rect(positioner,10, 10, 10, 10);
zxdg_positioner_v6_set_size(positioner,10, 100);
auto xdgPopup2 = zxdg_surface_v6_get_popup(xdgSurface, parentXdgSurface, positioner);
QVERIFY(!xdgPopupCreatedSpy.wait(10));
zxdg_positioner_v6_destroy(positioner);
zxdg_toplevel_v6_destroy(xdgTopLevel1);
zxdg_toplevel_v6_destroy(xdgTopLevelParent);
zxdg_popup_v6_destroy(xdgPopup2);
zxdg_surface_v6_destroy(xdgSurface);
}
QTEST_GUILESS_MAIN(XdgShellTestV6)
#include "test_xdg_shell_v6.moc"

@ -44,6 +44,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "subcompositor_interface.h"
#include "textinput_interface_p.h"
#include "xdgshell_v5_interface_p.h"
#include "xdgshell_v6_interface_p.h"
#include <QCoreApplication>
#include <QDebug>
@ -367,6 +368,9 @@ XdgShellInterface *Display::createXdgShell(const XdgShellInterfaceVersion &versi
case XdgShellInterfaceVersion::UnstableV5:
x = new XdgShellV5Interface(this, parent);
break;
case XdgShellInterfaceVersion::UnstableV6:
x = new XdgShellV6Interface(this, parent);
break;
}
connect(this, &Display::aboutToTerminate, x, [x] { delete x; });
return x;

@ -45,12 +45,15 @@ private:
void createSurface(wl_client *client, uint32_t version, uint32_t id, SurfaceInterface *surface, wl_resource *parentResource);
void createPopup(wl_client *client, uint32_t version, uint32_t id, SurfaceInterface *surface, SurfaceInterface *parent, SeatInterface *seat, quint32 serial, const QPoint &pos, wl_resource *parentResource);
void bind(wl_client *client, uint32_t version, uint32_t id) override;
quint32 ping(XdgShellSurfaceInterface * surface) override;
static void unbind(wl_resource *resource);
static Private *cast(wl_resource *r) {
return reinterpret_cast<Private*>(wl_resource_get_user_data(r));
}
QHash<wl_client *, wl_resource*> resources;
static void destroyCallback(wl_client *client, wl_resource *resource);
static void useUnstableVersionCallback(wl_client *client, wl_resource *resource, int32_t version);
static void getXdgSurfaceCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * surface);
@ -145,17 +148,30 @@ void XdgShellV5Interface::Private::createPopup(wl_client *client, uint32_t versi
XdgPopupV5Interface *popupSurface = new XdgPopupV5Interface(q, surface, parentResource);
auto d = popupSurface->d_func();
d->parent = QPointer<SurfaceInterface>(parent);
d->transientOffset = pos;
d->anchorRect = QRect(pos, QSize(0,0));
//default open like a normal popup
d->anchorEdge = Qt::BottomEdge;
d->gravity = Qt::TopEdge;
d->create(display->getConnection(client), version, id);
//compat
emit q->popupCreated(popupSurface, seat, serial);
//new system
emit q->xdgPopupCreated(popupSurface);
emit popupSurface->grabRequested(seat, serial);
}
void XdgShellV5Interface::Private::pongCallback(wl_client *client, wl_resource *resource, uint32_t serial)
{
Q_UNUSED(client)
Q_UNUSED(resource)
Q_UNUSED(serial)
// TODO: implement
auto s = cast(resource);
auto timerIt = s->pingTimers.find(serial);
if (timerIt != s->pingTimers.end() && timerIt.value()->isActive()) {
delete timerIt.value();
s->pingTimers.erase(timerIt);
emit s->q->pongReceived(serial);
}
}
XdgShellV5Interface::Private::Private(XdgShellV5Interface *q, Display *d)
@ -167,19 +183,20 @@ XdgShellV5Interface::Private::Private(XdgShellV5Interface *q, Display *d)
void XdgShellV5Interface::Private::bind(wl_client *client, uint32_t version, uint32_t id)
{
auto c = display->getConnection(client);
wl_resource *resource = c->createResource(&xdg_shell_interface, qMin(version, s_version), id);
auto resource = c->createResource(&xdg_shell_interface, qMin(version, s_version), id);
if (!resource) {
wl_client_post_no_memory(client);
return;
}
resources[client] = resource;
wl_resource_set_implementation(resource, &s_interface, this, unbind);
// TODO: should we track, yes we need to track to be able to ping!
}
void XdgShellV5Interface::Private::unbind(wl_resource *resource)
{
Q_UNUSED(resource)
// TODO: implement?
auto s = cast(resource);
auto client = wl_resource_get_client(resource);
s->resources.remove(client);
}
XdgSurfaceV5Interface *XdgShellV5Interface::getSurface(wl_resource *resource)
@ -199,6 +216,22 @@ XdgSurfaceV5Interface *XdgShellV5Interface::getSurface(wl_resource *resource)
return nullptr;
}
quint32 XdgShellV5Interface::Private::ping(XdgShellSurfaceInterface * surface)
{
auto client = surface->client()->client();
//from here we can get the resource bound to our global.
auto clientXdgShellResource = resources.value(client);
if (!clientXdgShellResource) {
return 0;
}
const quint32 pingSerial = display->nextSerial();
xdg_shell_send_ping(clientXdgShellResource, pingSerial);
setupTimer(pingSerial);
return pingSerial;
}
XdgShellV5Interface::Private *XdgShellV5Interface::d_func() const
{
return reinterpret_cast<Private*>(d.data());

@ -53,6 +53,8 @@ public:
**/
XdgSurfaceV5Interface *getSurface(wl_resource *native);
void ping();
private:
explicit XdgShellV5Interface(Display *display, QObject *parent = nullptr);
friend class Display;

@ -0,0 +1,965 @@
/****************************************************************************
Copyright 2017 David Edmundson <davidedmundson@kde.org>
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 <http://www.gnu.org/licenses/>.
****************************************************************************/
#include "xdgshell_v6_interface_p.h"
#include "xdgshell_interface_p.h"
#include "generic_shell_surface_p.h"
#include "display.h"
#include "global_p.h"
#include "global.h"
#include "resource_p.h"
#include "output_interface.h"
#include "seat_interface.h"
#include "surface_interface.h"
#include <wayland-xdg-shell-v6-server-protocol.h>
namespace KWayland
{
namespace Server
{
class XdgShellV6Interface::Private : public XdgShellInterface::Private
{
public:
Private(XdgShellV6Interface *q, Display *d);
QVector<XdgSurfaceV6Interface*> surfaces;
QVector<XdgPositionerV6Interface*> positioners;
private:
void createSurface(wl_client *client, uint32_t version, uint32_t id, SurfaceInterface *surface, wl_resource *parentResource);
void createPositioner(wl_client *client, uint32_t version, uint32_t id, wl_resource *parentResource);
void bind(wl_client *client, uint32_t version, uint32_t id) override;
quint32 ping(XdgShellSurfaceInterface * surface) override;
static void unbind(wl_resource *resource);
static Private *cast(wl_resource *r) {
return reinterpret_cast<Private*>(wl_resource_get_user_data(r));
}
static void destroyCallback(wl_client *client, wl_resource *resource);
static void createPositionerCallback(wl_client *client, wl_resource *resource, uint32_t id);
static void getXdgSurfaceCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * surface);
static void pongCallback(wl_client *client, wl_resource *resource, uint32_t serial);
XdgShellV6Interface *q;
static const struct zxdg_shell_v6_interface s_interface;
static const quint32 s_version;
QHash<wl_client *, wl_resource*> resources;
};
class XdgPopupV6Interface::Private : public XdgShellPopupInterface::Private
{
public:
Private(XdgPopupV6Interface *q, XdgShellV6Interface *c, SurfaceInterface *surface, wl_resource *parentResource);
~Private();
void ackConfigure(quint32 serial) {
if (!configureSerials.contains(serial)) {
// TODO: send error?
return;
}
while (!configureSerials.isEmpty()) {
quint32 i = configureSerials.takeFirst();
emit q_func()->configureAcknowledged(i);
if (i == serial) {
break;
}
}
}
void popupDone() override;
quint32 configure(const QRect &rect) override;
XdgPopupV6Interface *q_func() {
return reinterpret_cast<XdgPopupV6Interface *>(q);
}
private:
static void grabCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial);
static const struct zxdg_popup_v6_interface s_interface;
};
class XdgSurfaceV6Interface::Private : public KWayland::Server::Resource::Private
{
public:
Private(XdgSurfaceV6Interface* q, XdgShellV6Interface* c, SurfaceInterface* surface, wl_resource* parentResource);
~Private();
XdgSurfaceV6Interface *q_func() {
return reinterpret_cast<XdgSurfaceV6Interface *>(q);
}
void createTopLevel(wl_client *client, uint32_t version, uint32_t id, wl_resource *parentResource);
void createPopup(wl_client *client, uint32_t version, uint32_t id, wl_resource *parentResource, wl_resource *parentWindow, wl_resource *positioner);
XdgShellV6Interface *m_shell;
SurfaceInterface *m_surface;
//effectively a union, only one of these should be populated.
//a surface cannot have two roles
QPointer<XdgTopLevelV6Interface> m_topLevel;
QPointer<XdgPopupV6Interface> m_popup;
private:
static void destroyCallback(wl_client *client, wl_resource *resource);
static void getTopLevelCallback(wl_client *client, wl_resource *resource, uint32_t id);
static void getPopupCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *parent, wl_resource *positioner);
static void ackConfigureCallback(wl_client *client, wl_resource *resource, uint32_t serial);
static void setWindowGeometryCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height);
static const struct zxdg_surface_v6_interface s_interface;
};
class XdgTopLevelV6Interface::Private : public XdgShellSurfaceInterface::Private
{
public:
Private(XdgTopLevelV6Interface* q, XdgShellV6Interface* c, SurfaceInterface* surface, wl_resource* parentResource);
~Private();
void close() override;
void ackConfigure(quint32 serial) {
if (!configureSerials.contains(serial)) {
// TODO: send error?
return;
}
while (!configureSerials.isEmpty()) {
quint32 i = configureSerials.takeFirst();
emit q_func()->configureAcknowledged(i);
if (i == serial) {
break;
}
}
}
quint32 configure(States states, const QSize &size) override {
if (!resource) {
return 0;
}
const quint32 serial = global->display()->nextSerial();
wl_array state;
wl_array_init(&state);
if (states.testFlag(State::Maximized)) {
uint32_t *s = reinterpret_cast<uint32_t*>(wl_array_add(&state, sizeof(uint32_t)));
*s = ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED;
}
if (states.testFlag(State::Fullscreen)) {
uint32_t *s = reinterpret_cast<uint32_t*>(wl_array_add(&state, sizeof(uint32_t)));
*s = ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN;
}
if (states.testFlag(State::Resizing)) {
uint32_t *s = reinterpret_cast<uint32_t*>(wl_array_add(&state, sizeof(uint32_t)));
*s = ZXDG_TOPLEVEL_V6_STATE_RESIZING;
}
if (states.testFlag(State::Activated)) {
uint32_t *s = reinterpret_cast<uint32_t*>(wl_array_add(&state, sizeof(uint32_t)));
*s = ZXDG_TOPLEVEL_V6_STATE_ACTIVATED;
}
configureSerials << serial;
zxdg_toplevel_v6_send_configure(resource, size.width(), size.height(), &state);
zxdg_surface_v6_send_configure(parentResource, serial);
client->flush();
wl_array_release(&state);
return serial;
};
XdgTopLevelV6Interface *q_func() {
return reinterpret_cast<XdgTopLevelV6Interface*>(q);
}
private:
static void destroyCallback(wl_client *client, wl_resource *resource);
static void setParentCallback(struct wl_client *client, struct wl_resource *resource, wl_resource *parent);
static void showWindowMenuCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial, int32_t x, int32_t y);
static void setMaxSizeCallback(wl_client *client, wl_resource *resource, int32_t width, int32_t height);
static void setMinSizeCallback(wl_client *client, wl_resource *resource, int32_t width, int32_t height);
static void setMaximizedCallback(wl_client *client, wl_resource *resource);
static void unsetMaximizedCallback(wl_client *client, wl_resource *resource);
static void setFullscreenCallback(wl_client *client, wl_resource *resource, wl_resource *output);
static void unsetFullscreenCallback(wl_client *client, wl_resource *resource);
static void setMinimizedCallback(wl_client *client, wl_resource *resource);
static const struct zxdg_toplevel_v6_interface s_interface;
};
const quint32 XdgShellV6Interface::Private::s_version = 1;
#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct zxdg_shell_v6_interface XdgShellV6Interface::Private::s_interface = {
destroyCallback,
createPositionerCallback,
getXdgSurfaceCallback,
pongCallback
};
#endif
void XdgShellV6Interface::Private::destroyCallback(wl_client *client, wl_resource *resource)
{
Q_UNUSED(client)
// TODO: send protocol error if there are still surfaces mapped
wl_resource_destroy(resource);
}
void XdgShellV6Interface::Private::createPositionerCallback(wl_client *client, wl_resource *resource, uint32_t id)
{
auto s = cast(resource);
s->createPositioner(client, wl_resource_get_version(resource), id, resource);
}
void XdgShellV6Interface::Private::getXdgSurfaceCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * surface)
{
auto s = cast(resource);
s->createSurface(client, wl_resource_get_version(resource), id, SurfaceInterface::get(surface), resource);
}
void XdgShellV6Interface::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](XdgSurfaceV6Interface *s) {
return false;
return surface == s->surface();
}
);
if (it != surfaces.constEnd()) {
wl_resource_post_error(surface->resource(), ZXDG_SHELL_V6_ERROR_ROLE, "ShellSurface already created");
return;
}
XdgSurfaceV6Interface *shellSurface = new XdgSurfaceV6Interface(q, surface, parentResource);
surfaces << shellSurface;
QObject::connect(shellSurface, &XdgSurfaceV6Interface::destroyed, q,
[this, shellSurface] {
surfaces.removeAll(shellSurface);
}
);
shellSurface->d->create(display->getConnection(client), version, id);
}
void XdgShellV6Interface::Private::createPositioner(wl_client *client, uint32_t version, uint32_t id, wl_resource *parentResource)
{
Q_UNUSED(client)
XdgPositionerV6Interface *positioner = new XdgPositionerV6Interface(q, parentResource);
positioners << positioner;
QObject::connect(positioner, &Resource::destroyed, q,
[this, positioner] {
positioners.removeAll(positioner);
}
);
positioner->d->create(display->getConnection(client), version, id);
}
void XdgShellV6Interface::Private::pongCallback(wl_client *client, wl_resource *resource, uint32_t serial)
{
Q_UNUSED(client)
auto s = cast(resource);
auto timerIt = s->pingTimers.find(serial);
if (timerIt != s->pingTimers.end() && timerIt.value()->isActive()) {
delete timerIt.value();
s->pingTimers.erase(timerIt);
emit s->q->pongReceived(serial);
}
}
XdgShellV6Interface::Private::Private(XdgShellV6Interface *q, Display *d)
: XdgShellInterface::Private(XdgShellInterfaceVersion::UnstableV6, q, d, &zxdg_shell_v6_interface, 1)
, q(q)
{
}
void XdgShellV6Interface::Private::bind(wl_client *client, uint32_t version, uint32_t id)
{
auto c = display->getConnection(client);
auto resource = c->createResource(&zxdg_shell_v6_interface, qMin(version, s_version), id);
if (!resource) {
wl_client_post_no_memory(client);
return;
}
resources[client] = resource;
wl_resource_set_implementation(resource, &s_interface, this, unbind);
}
void XdgShellV6Interface::Private::unbind(wl_resource *resource)
{
auto s = cast(resource);
auto client = wl_resource_get_client(resource);
s->resources.remove(client);
}
XdgTopLevelV6Interface *XdgShellV6Interface::getSurface(wl_resource *resource)
{
if (!resource) {
return nullptr;
}
Q_D();
for (auto it = d->surfaces.constBegin(); it != d->surfaces.constEnd() ; it++) {
auto topLevel = (*it)->topLevel();
if (topLevel && topLevel->resource() == resource) {
return topLevel;
}
}
return nullptr;
}
XdgSurfaceV6Interface *XdgShellV6Interface::realGetSurface(wl_resource *resource)
{
if (!resource) {
return nullptr;
}
Q_D();
for (auto it = d->surfaces.constBegin(); it != d->surfaces.constEnd() ; it++) {
if ((*it)->resource() == resource) {
return (*it);
}
}
return nullptr;
}
XdgPositionerV6Interface *XdgShellV6Interface::getPositioner(wl_resource *resource)
{
if (!resource) {
return nullptr;
}
Q_D();
for (auto it = d->positioners.constBegin(); it != d->positioners.constEnd() ; it++) {
if ((*it)->resource() == resource) {
return *it;
}
}
return nullptr;
}
quint32 XdgShellV6Interface::Private::ping(XdgShellSurfaceInterface *surface)
{
auto client = surface->client()->client();
//from here we can get the resource bound to our global.
auto clientXdgShellResource = resources.value(client);
if (!clientXdgShellResource) {
return 0;
}
const quint32 pingSerial = display->nextSerial();
zxdg_shell_v6_send_ping(clientXdgShellResource, pingSerial);
setupTimer(pingSerial);
return pingSerial;
}
XdgShellV6Interface::Private *XdgShellV6Interface::d_func() const
{
return reinterpret_cast<Private*>(d.data());
}
namespace {
template <>
Qt::Edges edgesToQtEdges(zxdg_toplevel_v6_resize_edge edges)
{
Qt::Edges qtEdges;
switch (edges) {
case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP:
qtEdges = Qt::TopEdge;
break;
case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM:
qtEdges = Qt::BottomEdge;
break;
case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_LEFT:
qtEdges = Qt::LeftEdge;
break;
case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT:
qtEdges = Qt::TopEdge | Qt::LeftEdge;
break;
case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_LEFT:
qtEdges = Qt::BottomEdge | Qt::LeftEdge;
break;
case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_RIGHT:
qtEdges = Qt::RightEdge;
break;
case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_RIGHT:
qtEdges = Qt::TopEdge | Qt::RightEdge;
break;
case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_RIGHT:
qtEdges = Qt::BottomEdge | Qt::RightEdge;
break;
case ZXDG_TOPLEVEL_V6_RESIZE_EDGE_NONE:
break;
default:
Q_UNREACHABLE();
break;
}
return qtEdges;
}
}
#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct zxdg_surface_v6_interface XdgSurfaceV6Interface::Private::s_interface = {
destroyCallback,
getTopLevelCallback,
getPopupCallback,
setWindowGeometryCallback,
ackConfigureCallback
};
#endif
void XdgSurfaceV6Interface::Private::destroyCallback(wl_client *client, wl_resource *resource)
{
Q_UNUSED(client)
//FIXME check if we have attached toplevels first and throw an error
wl_resource_destroy(resource);
}
void XdgSurfaceV6Interface::Private::getTopLevelCallback(wl_client *client, wl_resource *resource, uint32_t id)
{
auto s = cast<XdgSurfaceV6Interface::Private>(resource);
s->createTopLevel(client, wl_resource_get_version(resource), id, resource);
}
void XdgSurfaceV6Interface::Private::createTopLevel(wl_client *client, uint32_t version, uint32_t id, wl_resource *parentResource)
{
if (m_topLevel) {
wl_resource_post_error(parentResource, ZXDG_SHELL_V6_ERROR_ROLE, "Toplevel already created on this surface");
return;
}
if (m_popup) {
wl_resource_post_error(parentResource, ZXDG_SHELL_V6_ERROR_ROLE, "Popup already created on this surface");
return;
}
m_topLevel = new XdgTopLevelV6Interface (m_shell, m_surface, parentResource);
m_topLevel->d->create(m_shell->display()->getConnection(client), version, id);
emit m_shell->surfaceCreated(m_topLevel);
}
void XdgSurfaceV6Interface::Private::getPopupCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *parent, wl_resource *positioner)
{
auto s = cast<XdgSurfaceV6Interface::Private>(resource);
s->createPopup(client, wl_resource_get_version(resource), id, resource, parent, positioner);
}
void XdgSurfaceV6Interface::Private::createPopup(wl_client *client, uint32_t version, uint32_t id, wl_resource *parentResource, wl_resource *parentSurface, wl_resource *positioner)
{
if (m_topLevel) {
wl_resource_post_error(parentResource, ZXDG_SHELL_V6_ERROR_ROLE, "Toplevel already created on this surface");
return;
}
if (m_popup) {
wl_resource_post_error(parentResource, ZXDG_SHELL_V6_ERROR_ROLE, "Popup already created on this surface");
return;
}
auto xdgPositioner = m_shell->getPositioner(positioner);
if (!xdgPositioner) {
wl_resource_post_error(parentResource, ZXDG_SHELL_V6_ERROR_INVALID_POSITIONER, "Invalid positioner");
return;
}
m_popup = new XdgPopupV6Interface(m_shell, m_surface, parentResource);
auto pd = m_popup->d_func();
pd->create(m_shell->display()->getConnection(client), version, id);
auto parentXdgSurface = m_shell->realGetSurface(parentSurface);
if (parentXdgSurface) {
pd->parent = parentXdgSurface->surface();
} else {
wl_resource_post_error(parentResource, ZXDG_SHELL_V6_ERROR_INVALID_POPUP_PARENT, "Invalid popup parent");
return;
}
pd->initialSize = xdgPositioner->initialSize();
pd->anchorRect = xdgPositioner->anchorRect();
pd->anchorEdge = xdgPositioner->anchorEdge();
pd->gravity = xdgPositioner->gravity();
pd->constraintAdjustments = xdgPositioner->constraintAdjustments();
pd->anchorOffset = xdgPositioner->anchorOffset();
emit m_shell->xdgPopupCreated(m_popup.data());
}
void XdgSurfaceV6Interface::Private::ackConfigureCallback(wl_client *client, wl_resource *resource, uint32_t serial)
{
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
if (s->m_topLevel) {
s->m_topLevel->d_func()->ackConfigure(serial);
} else if (s->m_popup) {
s->m_popup->d_func()->ackConfigure(serial);
}
}
void XdgSurfaceV6Interface::Private::setWindowGeometryCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height)
{
// TODO: implement - not done for v5 either
Q_UNUSED(client)
Q_UNUSED(resource)
Q_UNUSED(x)
Q_UNUSED(y)
Q_UNUSED(width)
Q_UNUSED(height)
}
XdgSurfaceV6Interface::Private::Private(XdgSurfaceV6Interface *q, XdgShellV6Interface *c, SurfaceInterface *surface, wl_resource *parentResource)
: KWayland::Server::Resource::Private(q, c, parentResource, &zxdg_surface_v6_interface, &s_interface),
m_shell(c),
m_surface(surface)
{
}
XdgSurfaceV6Interface::Private::~Private() = default;
class XdgPositionerV6Interface::Private : public KWayland::Server::Resource::Private
{
public:
Private(XdgPositionerV6Interface *q, XdgShellV6Interface *c, wl_resource* parentResource);
QSize initialSize;
QRect anchorRect;
Qt::Edges anchorEdge;
Qt::Edges gravity;
PositionerConstraints constraintAdjustments;
QPoint anchorOffset;
private:
static void setSizeCallback(wl_client *client, wl_resource *resource, int32_t width, int32_t height);
static void setAnchorRectCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height);
static void setAnchorCallback(wl_client *client, wl_resource *resource, uint32_t anchor);
static void setGravityCallback(wl_client *client, wl_resource *resource, uint32_t gravity);
static void setConstraintAdjustmentCallback(wl_client *client, wl_resource *resource, uint32_t constraint_adjustment);
static void setOffsetCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y);
static const struct zxdg_positioner_v6_interface s_interface;
};
XdgPositionerV6Interface::Private::Private(XdgPositionerV6Interface *q, XdgShellV6Interface *c, wl_resource *parentResource)
: KWayland::Server::Resource::Private(q, c, parentResource, &zxdg_positioner_v6_interface, &s_interface)
{
}
#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct zxdg_positioner_v6_interface XdgPositionerV6Interface::Private::s_interface = {
resourceDestroyedCallback,
setSizeCallback,
setAnchorRectCallback,
setAnchorCallback,
setGravityCallback,
setConstraintAdjustmentCallback,
setOffsetCallback
};
#endif
void XdgPositionerV6Interface::Private::setSizeCallback(wl_client *client, wl_resource *resource, int32_t width, int32_t height) {
Q_UNUSED(client)
auto s = cast<Private>(resource);
s->initialSize = QSize(width, height);
}
void XdgPositionerV6Interface::Private::setAnchorRectCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height)
{
Q_UNUSED(client)
auto s = cast<Private>(resource);
s->anchorRect = QRect(x, y, width, height);
}
void XdgPositionerV6Interface::Private::setAnchorCallback(wl_client *client, wl_resource *resource, uint32_t anchor) {
Q_UNUSED(client)
auto s = cast<Private>(resource);
//Note - see David E's email to wayland-devel about this being bad API
if ((anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) &&
(anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT)) {
wl_resource_post_error(resource, ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, "Invalid arguments");
return;
}
if ((anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP) &&
(anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM)) {
wl_resource_post_error(resource, ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, "Invalid arguments");
return;
}
Qt::Edges edges;
if (anchor & ZXDG_POSITIONER_V6_ANCHOR_LEFT) {
edges |= Qt::LeftEdge;
}
if (anchor & ZXDG_POSITIONER_V6_ANCHOR_TOP) {
edges |= Qt::TopEdge;
}
if (anchor & ZXDG_POSITIONER_V6_ANCHOR_RIGHT) {
edges |= Qt::RightEdge;
}
if (anchor & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM) {
edges |= Qt::BottomEdge;
}
s->anchorEdge = edges;
}
void XdgPositionerV6Interface::Private::setGravityCallback(wl_client *client, wl_resource *resource, uint32_t gravity) {
Q_UNUSED(client)
auto s = cast<Private>(resource);
if ((gravity & ZXDG_POSITIONER_V6_GRAVITY_LEFT) &&
(gravity & ZXDG_POSITIONER_V6_GRAVITY_RIGHT)) {
wl_resource_post_error(resource, ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, "Invalid arguments");
return;
}
if ((gravity & ZXDG_POSITIONER_V6_GRAVITY_TOP) &&
(gravity & ZXDG_POSITIONER_V6_GRAVITY_BOTTOM)) {
wl_resource_post_error(resource, ZXDG_POSITIONER_V6_ERROR_INVALID_INPUT, "Invalid arguments");
return;
}
Qt::Edges edges;
if (gravity & ZXDG_POSITIONER_V6_ANCHOR_LEFT) {
edges |= Qt::LeftEdge;
}
if (gravity & ZXDG_POSITIONER_V6_ANCHOR_TOP) {
edges |= Qt::TopEdge;
}
if (gravity & ZXDG_POSITIONER_V6_ANCHOR_RIGHT) {
edges |= Qt::RightEdge;
}
if (gravity & ZXDG_POSITIONER_V6_ANCHOR_BOTTOM) {
edges |= Qt::BottomEdge;
}
s->gravity = edges;
}
void XdgPositionerV6Interface::Private::setConstraintAdjustmentCallback(wl_client *client, wl_resource *resource, uint32_t constraint_adjustment) {
Q_UNUSED(client)
auto s = cast<Private>(resource);
PositionerConstraints constraints;
if (constraint_adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X) {
constraints |= PositionerConstraint::SlideX;
}
if (constraint_adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y) {
constraints |= PositionerConstraint::SlideY;
}
if (constraint_adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X) {
constraints |= PositionerConstraint::FlipX;
}
if (constraint_adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y) {
constraints |= PositionerConstraint::FlipY;
}
if (constraint_adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_X) {
constraints |= PositionerConstraint::ResizeX;
}
if (constraint_adjustment & ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y) {
constraints |= PositionerConstraint::ResizeY;
}
s->constraintAdjustments = constraints;
}
void XdgPositionerV6Interface::Private::setOffsetCallback(wl_client *client, wl_resource *resource, int32_t x, int32_t y)
{
Q_UNUSED(client)
auto s = cast<Private>(resource);
s->anchorOffset = QPoint(x,y);
}
void XdgTopLevelV6Interface::Private::close()
{
zxdg_toplevel_v6_send_close(resource);
client->flush();
}
void XdgTopLevelV6Interface::Private::setMaxSizeCallback(wl_client *client, wl_resource *resource, int32_t width, int32_t height)
{
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
s->q_func()->maxSizeChanged(QSize(width, height));
}
void XdgTopLevelV6Interface::Private::setMinSizeCallback(wl_client *client, wl_resource *resource, int32_t width, int32_t height)
{
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
s->q_func()->minSizeChanged(QSize(width, height));
}
const struct zxdg_toplevel_v6_interface XdgTopLevelV6Interface::Private::s_interface = {
destroyCallback,
setParentCallback,
setTitleCallback,
setAppIdCallback,
showWindowMenuCallback,
moveCallback,
resizeCallback<zxdg_toplevel_v6_resize_edge>,
setMaxSizeCallback,
setMinSizeCallback,
setMaximizedCallback,
unsetMaximizedCallback,
setFullscreenCallback,
unsetFullscreenCallback,
setMinimizedCallback
};
void XdgTopLevelV6Interface::Private::destroyCallback(wl_client *client, wl_resource *resource)
{
Q_UNUSED(client)
wl_resource_destroy(resource);
}
void XdgTopLevelV6Interface::Private::setParentCallback(wl_client *client, wl_resource *resource, wl_resource *parent)
{
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
if (!parent) {
//setting null is valid API. Clear
s->parent = nullptr;
emit s->q_func()->transientForChanged();
} else {
auto parentSurface = static_cast<XdgShellV6Interface*>(s->q->global())->getSurface(parent);
if (s->parent.data() != parentSurface) {
s->parent = QPointer<XdgTopLevelV6Interface>(parentSurface);
emit s->q_func()->transientForChanged();
}
}
}
void XdgTopLevelV6Interface::Private::showWindowMenuCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial, int32_t x, int32_t y)
{
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
emit s->q_func()->windowMenuRequested(SeatInterface::get(seat), serial, QPoint(x, y));
}
XdgTopLevelV6Interface::Private::Private(XdgTopLevelV6Interface *q, XdgShellV6Interface *c, SurfaceInterface *surface, wl_resource *parentResource)
: XdgShellSurfaceInterface::Private(XdgShellInterfaceVersion::UnstableV6, q, c, surface, parentResource, &zxdg_toplevel_v6_interface, &s_interface)
{
}
void XdgTopLevelV6Interface::Private::setMaximizedCallback(wl_client *client, wl_resource *resource)
{
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
s->q_func()->maximizedChanged(true);
}
void XdgTopLevelV6Interface::Private::unsetMaximizedCallback(wl_client *client, wl_resource *resource)
{
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
s->q_func()->maximizedChanged(false);
}
void XdgTopLevelV6Interface::Private::setFullscreenCallback(wl_client *client, wl_resource *resource, wl_resource *output)
{
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
OutputInterface *o = nullptr;
if (output) {
o = OutputInterface::get(output);
}
s->q_func()->fullscreenChanged(true, o);
}
void XdgTopLevelV6Interface::Private::unsetFullscreenCallback(wl_client *client, wl_resource *resource)
{
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
s->q_func()->fullscreenChanged(false, nullptr);
}
void XdgTopLevelV6Interface::Private::setMinimizedCallback(wl_client *client, wl_resource *resource)
{
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
s->q_func()->minimizeRequested();
}
XdgTopLevelV6Interface::Private::~Private() = default;
#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct zxdg_popup_v6_interface XdgPopupV6Interface::Private::s_interface = {
resourceDestroyedCallback,
grabCallback
};
#endif
XdgPopupV6Interface::Private::Private(XdgPopupV6Interface *q, XdgShellV6Interface *c, SurfaceInterface *surface, wl_resource *parentResource)
: XdgShellPopupInterface::Private(XdgShellInterfaceVersion::UnstableV6, q, c, surface, parentResource, &zxdg_popup_v6_interface, &s_interface)
{
}
void XdgPopupV6Interface::Private::grabCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial)
{
Q_UNUSED(client)
auto s = cast<Private>(resource);
auto seatInterface = SeatInterface::get(seat);
s->q_func()->grabRequested(seatInterface, serial);
}
XdgPopupV6Interface::Private::~Private() = default;
quint32 XdgPopupV6Interface::Private::configure(const QRect &rect)
{
if (!resource) {
return 0;
}
const quint32 serial = global->display()->nextSerial();
configureSerials << serial;
zxdg_popup_v6_send_configure(resource, rect.x(), rect.y(), rect.width(), rect.height());
zxdg_surface_v6_send_configure(parentResource, serial);
client->flush();
return serial;
}
void XdgPopupV6Interface::Private::popupDone()
{
if (!resource) {
return;
}
// TODO: dismiss all child popups
zxdg_popup_v6_send_popup_done(resource);
client->flush();
}
XdgShellV6Interface::XdgShellV6Interface(Display *display, QObject *parent)
: XdgShellInterface(new Private(this, display), parent)
{
}
Display* XdgShellV6Interface::display() const
{
return d->display;
}
XdgShellV6Interface::~XdgShellV6Interface() = default;
XdgSurfaceV6Interface::XdgSurfaceV6Interface(XdgShellV6Interface *parent, SurfaceInterface *surface, wl_resource *parentResource)
: KWayland::Server::Resource(new Private(this, parent, surface, parentResource))
{
}
XdgSurfaceV6Interface::~XdgSurfaceV6Interface() = default;
SurfaceInterface* XdgSurfaceV6Interface::surface() const
{
Q_D();
return d->m_surface;
}
XdgPositionerV6Interface::XdgPositionerV6Interface(XdgShellV6Interface *parent, wl_resource *parentResource)
: KWayland::Server::Resource(new Private(this, parent, parentResource))
{
}
QSize XdgPositionerV6Interface::initialSize() const
{
Q_D();
return d->initialSize;
}
QRect XdgPositionerV6Interface::anchorRect() const
{
Q_D();
return d->anchorRect;
}
Qt::Edges XdgPositionerV6Interface::anchorEdge() const
{
Q_D();
return d->anchorEdge;
}
Qt::Edges XdgPositionerV6Interface::gravity() const
{
Q_D();
return d->gravity;
}
PositionerConstraints XdgPositionerV6Interface::constraintAdjustments() const
{
Q_D();
return d->constraintAdjustments;
}
QPoint XdgPositionerV6Interface::anchorOffset() const
{
Q_D();
return d->anchorOffset;
}
XdgPositionerV6Interface::Private *XdgPositionerV6Interface::d_func() const
{
return reinterpret_cast<Private*>(d.data());
}
XdgTopLevelV6Interface* XdgSurfaceV6Interface::topLevel() const
{
Q_D();
return d->m_topLevel.data();
}
XdgPopupV6Interface* XdgSurfaceV6Interface::popup() const
{
Q_D();
return d->m_popup.data();
}
XdgSurfaceV6Interface::Private *XdgSurfaceV6Interface::d_func() const
{
return reinterpret_cast<Private*>(d.data());
}
XdgTopLevelV6Interface::XdgTopLevelV6Interface(XdgShellV6Interface *parent, SurfaceInterface *surface, wl_resource *parentResource)
: KWayland::Server::XdgShellSurfaceInterface(new Private(this, parent, surface, parentResource))
{
}
XdgTopLevelV6Interface::~XdgTopLevelV6Interface() = default;
XdgTopLevelV6Interface::Private *XdgTopLevelV6Interface::d_func() const
{
return reinterpret_cast<Private*>(d.data());
}
XdgPopupV6Interface::XdgPopupV6Interface(XdgShellV6Interface *parent, SurfaceInterface *surface, wl_resource *parentResource)
: XdgShellPopupInterface(new Private(this, parent, surface, parentResource))
{
}
XdgPopupV6Interface::~XdgPopupV6Interface() = default;
XdgPopupV6Interface::Private *XdgPopupV6Interface::d_func() const
{
return reinterpret_cast<Private*>(d.data());
}
}
}

@ -0,0 +1,148 @@
/****************************************************************************
Copyright 2017 David Edmundson <davidedmundson@kde.org>
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 <http://www.gnu.org/licenses/>.
****************************************************************************/
#ifndef KWAYLAND_SERVER_XDGSHELL_V6_INTERFACE_P_H
#define KWAYLAND_SERVER_XDGSHELL_V6_INTERFACE_P_H
#include "global.h"
#include "resource.h"
#include "xdgshell_interface.h"
#include <KWayland/Server/kwaylandserver_export.h>
#include <QSize>
namespace KWayland
{
namespace Server
{
class Display;
class OutputInterface;
class SeatInterface;
class SurfaceInterface;
class XdgTopLevelV6Interface;
class XdgPopupV6Interface;
class XdgPositionerV6Interface;
class XdgSurfaceV6Interface;
template <typename T>
class GenericShellSurface;
class XdgShellV6Interface : public XdgShellInterface
{
Q_OBJECT
public:
virtual ~XdgShellV6Interface();
/**
* @returns The XdgTopLevelV6Interface for the @p native resource.
**/
XdgTopLevelV6Interface *getSurface(wl_resource *native);
//DAVE we want to rename this, as it's bloody confusing. But XdgShellInterface::getSurface exists and expects that
//also use a less terrible argument name than native. It's obvious it's native from the type
XdgPositionerV6Interface *getPositioner(wl_resource *native);
XdgSurfaceV6Interface *realGetSurface(wl_resource *native);
Display *display() const;
void ping(XdgShellSurfaceInterface * surface);
private:
explicit XdgShellV6Interface(Display *display, QObject *parent = nullptr);
friend class Display;
class Private;
Private *d_func() const;
};
class XdgSurfaceV6Interface : public KWayland::Server::Resource
{
Q_OBJECT
public:
virtual ~XdgSurfaceV6Interface();
SurfaceInterface* surface() const;
XdgTopLevelV6Interface* topLevel() const;
XdgPopupV6Interface *popup() const;
private:
explicit XdgSurfaceV6Interface(XdgShellV6Interface *parent, SurfaceInterface *surface, wl_resource *parentResource);
friend class XdgShellV6Interface;
class Private;
Private *d_func() const;
};
class XdgTopLevelV6Interface : public
XdgShellSurfaceInterface
{
Q_OBJECT
public:
virtual ~XdgTopLevelV6Interface();
private:
explicit XdgTopLevelV6Interface(XdgShellV6Interface *parent, SurfaceInterface *surface, wl_resource *parentResource);
friend class XdgShellV6Interface;
friend class XdgSurfaceV6Interface;
class Private;
Private *d_func() const;
};
class XdgPopupV6Interface : public XdgShellPopupInterface
{
Q_OBJECT
public:
virtual ~XdgPopupV6Interface();
private:
explicit XdgPopupV6Interface(XdgShellV6Interface *parent, SurfaceInterface *surface, wl_resource *parentResource);
friend class XdgShellV6Interface;
friend class XdgSurfaceV6Interface;
friend class GenericShellSurface<XdgPopupV6Interface>;
class Private;
Private *d_func() const;
};
/*
* This is a private internal class that keeps track of sent data
* At the time of PopupCreation these values are copied to the popup
*/
class XdgPositionerV6Interface: public KWayland::Server::Resource
{
public:
QSize initialSize() const;
QRect anchorRect() const;
Qt::Edges anchorEdge() const;
Qt::Edges gravity() const;
PositionerConstraints constraintAdjustments() const;
QPoint anchorOffset() const;
private:
explicit XdgPositionerV6Interface(XdgShellV6Interface *parent, wl_resource *parentResource);
friend class XdgShellV6Interface;
class Private;
Private *d_func() const;
};
}
}
#endif

@ -59,3 +59,8 @@ endif()
add_executable(plasmasurface-test plasmasurfacetest.cpp)
target_link_libraries(plasmasurface-test Qt5::Gui KF5::WaylandClient)
ecm_mark_as_test(plasmasurface-test)
add_executable(xdg-test xdgtest.cpp)
target_link_libraries(xdg-test Qt5::Gui KF5::WaylandClient)
ecm_mark_as_test(xdg-test)

@ -0,0 +1,176 @@
/********************************************************************
Copyright 2015 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "../src/client/compositor.h"
#include "../src/client/connection_thread.h"
#include "../src/client/event_queue.h"
#include "../src/client/registry.h"
#include "../src/client/shadow.h"
#include "../src/client/shell.h"
#include "../src/client/shm_pool.h"
#include "../src/client/surface.h"
#include "../src/client/xdgshell.h"
// Qt
#include <QGuiApplication>
#include <QImage>
#include <QThread>
using namespace KWayland::Client;
class XdgTest : public QObject
{
Q_OBJECT
public:
explicit XdgTest(QObject *parent = nullptr);
virtual ~XdgTest();
void init();
private:
void setupRegistry(Registry *registry);
void render();
void renderPopup();
QThread *m_connectionThread;
ConnectionThread *m_connectionThreadObject;
EventQueue *m_eventQueue = nullptr;
Compositor *m_compositor = nullptr;
ShmPool *m_shm = nullptr;
Surface *m_surface = nullptr;
XdgShell *m_xdgShell = nullptr;
XdgShellSurface *m_xdgShellSurface = nullptr;
Surface *m_popupSurface = nullptr;
XdgShellPopup *m_xdgShellPopup = nullptr;
};
XdgTest::XdgTest(QObject *parent)
: QObject(parent)
, m_connectionThread(new QThread(this))
, m_connectionThreadObject(new ConnectionThread())
{
}
XdgTest::~XdgTest()
{
m_connectionThread->quit();
m_connectionThread->wait();
m_connectionThreadObject->deleteLater();
}
void XdgTest::init()
{
connect(m_connectionThreadObject, &ConnectionThread::connected, this,
[this] {
m_eventQueue = new EventQueue(this);
m_eventQueue->setup(m_connectionThreadObject);
Registry *registry = new Registry(this);
setupRegistry(registry);
},
Qt::QueuedConnection
);
m_connectionThreadObject->moveToThread(m_connectionThread);
m_connectionThread->start();
m_connectionThreadObject->initConnection();
}
void XdgTest::setupRegistry(Registry *registry)
{
connect(registry, &Registry::compositorAnnounced, this,
[this, registry](quint32 name, quint32 version) {
m_compositor = registry->createCompositor(name, version, this);
}
);
connect(registry, &Registry::shmAnnounced, this,
[this, registry](quint32 name, quint32 version) {
m_shm = registry->createShmPool(name, version, this);
}
);
connect(registry, &Registry::xdgShellUnstableV6Announced, this,
[this, registry](quint32 name, quint32 version) {
m_xdgShell = registry->createXdgShell(name, version, this);
m_xdgShell->setEventQueue(m_eventQueue);
}
);
connect(registry, &Registry::interfacesAnnounced, this,
[this] {
Q_ASSERT(m_compositor);
Q_ASSERT(m_xdgShell);
Q_ASSERT(m_shm);
m_surface = m_compositor->createSurface(this);
Q_ASSERT(m_surface);
m_xdgShellSurface = m_xdgShell->createSurface(m_surface, this);
Q_ASSERT(m_xdgShellSurface);
connect(m_xdgShellSurface, &XdgShellSurface::sizeChanged, this, &XdgTest::render);
render();
//create popup
m_popupSurface = m_compositor->createSurface(this);
XdgPositioner positioner(QSize(50,50), QRect(100,100, 20, 20));
m_xdgShellPopup = m_xdgShell->createPopup(m_popupSurface, m_xdgShellSurface, positioner, this);
renderPopup();
}
);
registry->setEventQueue(m_eventQueue);
registry->create(m_connectionThreadObject);
registry->setup();
}
void XdgTest::render()
{
const QSize &size = m_xdgShellSurface->size().isValid() ? m_xdgShellSurface->size() : QSize(300, 200);
auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef();
buffer->setUsed(true);
QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(QColor(255, 255, 255, 128));
m_surface->attachBuffer(*buffer);
m_surface->damage(QRect(QPoint(0, 0), size));
m_surface->commit(Surface::CommitFlag::None);
buffer->setUsed(false);
}
void XdgTest::renderPopup()
{
QSize size(200,200);
auto buffer = m_shm->getBuffer(size, size.width() * 4).toStrongRef();
buffer->setUsed(true);
QImage image(buffer->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
image.fill(QColor(255, 0, 0, 255));
m_popupSurface->attachBuffer(*buffer);
m_popupSurface->damage(QRect(QPoint(0, 0), size));
m_popupSurface->commit(Surface::CommitFlag::None);
buffer->setUsed(false);
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
XdgTest client;
client.init();
return app.exec();
}
#include "xdgtest.moc"

@ -47,6 +47,9 @@ zwp_text_input_manager_v2;TextInputManagerUnstableV2
xdg_shell;XdgShellV5
xdg_surface;XdgSurfaceV5
xdg_popup;XdgPopupV5
zxdg_shell_v6;XdgShellV6
zxdg_surface_v6;XdgSurfaceV6
zxdg_popup_v6;XdgPopupV6
zwp_relative_pointer_manager_v1;RelativePointerManagerUnstableV1
zwp_relative_pointer_v1;RelativePointerUnstableV1
zwp_pointer_gestures_v1;PointerGesturesUnstableV1

@ -31,6 +31,30 @@ XdgShellInterface::Private::Private(XdgShellInterfaceVersion interfaceVersion, X
{
}
void XdgShellInterface::Private::setupTimer(quint32 serial)
{
QTimer *pingTimer = new QTimer();
pingTimer->setSingleShot(false);
pingTimer->setInterval(1000);
int attempt = 0;
connect(pingTimer, &QTimer::timeout, q, [this, serial, attempt]() mutable {
++attempt;
if (attempt == 1) {
emit q->pingDelayed(serial);
} else {
emit q->pingTimeout(serial);
auto timerIt = pingTimers.find(serial);
if (timerIt != pingTimers.end()) {
delete timerIt.value();
pingTimers.erase(timerIt);
}
}
});
pingTimers.insert(serial, pingTimer);
pingTimer->start();
}
XdgShellInterface::XdgShellInterface(Private *d, QObject *parent)
: Global(d, parent)
{
@ -50,12 +74,17 @@ XdgShellInterfaceVersion XdgShellInterface::interfaceVersion() const
return d->interfaceVersion;
}
quint32 XdgShellInterface::ping(XdgShellSurfaceInterface * surface)
{
return d_func()->ping(surface);
}
XdgShellInterface::Private *XdgShellInterface::d_func() const
{
return reinterpret_cast<Private*>(d.data());
}
XdgShellSurfaceInterface::Private::Private(XdgShellInterfaceVersion interfaceVersion, XdgShellSurfaceInterface *q, XdgShellInterface *c, SurfaceInterface *surface, wl_resource *parentResource, const wl_interface *interface, const void *implementation)
XdgShellSurfaceInterface::Private::Private(XdgShellInterfaceVersion interfaceVersion, XdgShellSurfaceInterface *q, Global *c, SurfaceInterface *surface, wl_resource *parentResource, const wl_interface *interface, const void *implementation)
: Resource::Private(q, c, parentResource, interface, implementation)
, GenericShellSurface<XdgShellSurfaceInterface>(q, surface)
, interfaceVersion(interfaceVersion)
@ -158,10 +187,69 @@ QPointer<SurfaceInterface> XdgShellPopupInterface::transientFor() const
return d->parent;
}
QSize XdgShellPopupInterface::initialSize() const
{
Q_D();
return d->initialSize;
}
QPoint XdgShellPopupInterface::transientOffset() const
{
QRect rect = anchorRect();
const QPoint center = rect.isEmpty() ? rect.topLeft() : rect.center();
rect = rect.adjusted(0,0,1,1); //compensate for the stupid QRect::right +1 fiasco
switch(anchorEdge()) {
case Qt::TopEdge | Qt::LeftEdge:
return rect.topLeft();
case Qt::TopEdge:
return QPoint(center.x(), rect.y());
case Qt::TopEdge | Qt::RightEdge:
return rect.topRight();
case Qt::RightEdge:
return QPoint(rect.right(), center.y());
case Qt::BottomEdge | Qt::RightEdge:
return rect.bottomRight();
case Qt::BottomEdge:
return QPoint(center.x(), rect.bottom());
case Qt::BottomEdge | Qt::LeftEdge:
return rect.bottomLeft();
case Qt::LeftEdge:
return QPoint(rect.left(), center.y());
default:
return center;
}
Q_UNREACHABLE();
}
QRect XdgShellPopupInterface::anchorRect() const
{
Q_D();
return d->anchorRect;
}
Qt::Edges XdgShellPopupInterface::anchorEdge() const
{
Q_D();
return d->transientOffset;
return d->anchorEdge;
}
Qt::Edges XdgShellPopupInterface::gravity() const
{
Q_D();
return d->gravity;
}
QPoint XdgShellPopupInterface::anchorOffset() const
{
Q_D();
return d->anchorOffset;
}
PositionerConstraints XdgShellPopupInterface::constraintAdjustments() const
{
Q_D();
return d->constraintAdjustments;
}
void XdgShellPopupInterface::popupDone()
@ -170,6 +258,12 @@ void XdgShellPopupInterface::popupDone()
return d->popupDone();
}
quint32 XdgShellPopupInterface::configure(const QRect &rect)
{
Q_D();
return d->configure(rect);
}
XdgShellPopupInterface::Private *XdgShellPopupInterface::d_func() const
{
return reinterpret_cast<Private*>(d.data());

@ -50,9 +50,47 @@ enum class XdgShellInterfaceVersion
/**
* xdg_shell (unstable v5)
**/
UnstableV5
UnstableV5,
/**
* zxdg_shell_v6 (unstable v6)
* @since 5.XDGMERGE_VERSION
**/
UnstableV6
};
/**
* Flags describing how a popup should be reposition if constrained
* @since 5.XDGMERGE_VERSION
*/
enum class PositionerConstraint {
/**
* Slide the popup on the X axis until there is room
*/
SlideX = 1 << 0,
/**
* Slide the popup on the Y axis until there is room
*/
SlideY = 1 << 1,
/**
* Invert the anchor and gravity on the X axis
*/
FlipX = 1 << 2,
/**
* Invert the anchor and gravity on the Y axis
*/
FlipY = 1 << 3,
/**
* Resize the popup in the X axis
*/
ResizeX = 1 << 4,
/**
* Resize the popup in the Y axis
*/
ResizeY = 1 << 5
};
Q_DECLARE_FLAGS(PositionerConstraints, PositionerConstraint)
/**
*
* @since 5.25
@ -73,19 +111,70 @@ public:
**/
XdgShellSurfaceInterface *getSurface(wl_resource *native);
/**
* Confirm the client is still alive and responding
*
* Will result in pong being emitted
*
* @returns unique identifier for this request
* @since XDGMERGE_VERSION
*/
quint32 ping(XdgShellSurfaceInterface * surface);
Q_SIGNALS:
void surfaceCreated(KWayland::Server::XdgShellSurfaceInterface *surface);
/**
* Emitted whenever a new popup got created.
*
* A popup only gets created in response to an action on the @p seat.
*
*
* @param surface The popup xdg shell surface which got created
* @param seat The seat on which an action triggered the popup
* @param serial The serial of the action on the seat
*
* XDGV5 only
* Use both xdgPopupCreated and XdgShellPopupInterface::grabbed to cover both XDGV5 and XDGV6
**/
void popupCreated(KWayland::Server::XdgShellPopupInterface *surface, KWayland::Server::SeatInterface *seat, quint32 serial);
/*
* Emitted whenever a new popup gets created.
*
* @param surface The popup xdg shell surface which got created
* @since XDGMERGE_VERSION
*/
void xdgPopupCreated(KWayland::Server::XdgShellPopupInterface *surface);
/*
* Emitted in response to a ping request
*
* @param serial unique identifier for the request
* @since XDGMERGE_VERSION
*/
void pongReceived(quint32 serial);
/*
* Emitted when the application takes more than expected
* to answer to a ping, this will always be emitted before
* eventuallt pingTimeout gets emitted
*
* @param serial unique identifier for the request
* @since XDGMERGE_VERSION
*/
void pingDelayed(quint32 serial);
/*
* Emitted when the application doesn't answer to a ping
* and the serve gave up on it
*
* @param serial unique identifier for the request
* @since XDGMERGE_VERSION
*/
void pingTimeout(quint32 serial);
protected:
class Private;
explicit XdgShellInterface(Private *d, QObject *parent = nullptr);
@ -239,6 +328,18 @@ Q_SIGNALS:
**/
void transientForChanged();
/**
* Emitted whenever the maximun size hint changes
* @since 5.XDGMERGE_VERSION
*/
void maxSizeChanged(const QSize &size);
/**
* Emitted whenever the minimum size hint changes
* @since 5.XDGMERGE_VERSION
*/
void minSizeChanged(const QSize &size);
protected:
class Private;
explicit XdgShellSurfaceInterface(Private *p);
@ -263,19 +364,73 @@ public:
**/
SurfaceInterface *surface() const;
/*
* Ask the popup surface to configure itself for the given configuration.
*
* @arg rect. The position of the surface relative to the transient parent
* @since 5.XDGMERGE_VERSION
*/
quint32 configure(const QRect &rect);
/**
* @returns the parent surface.
* @see transientOffset
**/
QPointer<SurfaceInterface> transientFor() const;
/**
* The offset of the Surface in the coordinate system of the SurfaceInterface this surface is a transient for.
*
* For XDG V6 this returns the point on the anchorRect defined by the anchor edge.
*
* @returns offset in parent coordinate system.
* @see transientFor
**/
QPoint transientOffset() const;
/**
* The size of the surface that is to be positioned.
*
* @since 5.XDGMERGE_VERSION
*/
QSize initialSize() const;
/**
* The area this popup should be positioned around
* @since 5.XDGMERGE_VERSION
*/
QRect anchorRect() const;
/**
* Which edge of the anchor should the popup be positioned around
* @since 5.XDGMERGE_VERSION
*/
Qt::Edges anchorEdge() const;
/**
* An additional offset that should be applied to the popup from the anchor rect
*
* @since 5.XDGMERGE_VERSION
*/
QPoint anchorOffset() const;
/**
* Specifies in what direction the popup should be positioned around the anchor
* i.e if the gravity is "bottom", then then the top of top of the poup will be at the anchor edge
* if the gravity is top, then the bottom of the popup will be at the anchor edge
*
* @since 5.XDGMERGE_VERSION
*/
//DAVE left + right is illegal, so this is possible a useless return value? Maybe an enum with 9 entries left, topleft, top, ..
Qt::Edges gravity() const;
/**
* Specifies how the compositor should position the popup if it does not fit in the requested position
* @since 5.XDGMERGE_VERSION
*/
PositionerConstraints constraintAdjustments() const;
/**
* Dismiss this popup. This indicates to the client that it should destroy this popup.
* The Compositor can invoke this method when e.g. the user clicked outside the popup
@ -283,6 +438,24 @@ public:
**/
void popupDone();
Q_SIGNALS:
/**
* A configure event with @p serial got acknowledged.
* Note: XdgV6 only
* @see configure
* @since 5.XDGMERGE_VERSION
**/
void configureAcknowledged(quint32 serial);
/**
* The client requested that this popup takes an explicit grab
*
* @param seat The seat on which an action triggered the popup
* @param serial The serial of the action on the seat
* @since 5.XDGMERGE_VERSION
*/
void grabRequested(KWayland::Server::SeatInterface *seat, quint32 serial);
protected:
class Private;
explicit XdgShellPopupInterface(Private *p);

@ -24,6 +24,8 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "generic_shell_surface_p.h"
#include "resource_p.h"
#include <QTimer>
namespace KWayland
{
namespace Server
@ -34,6 +36,11 @@ class XdgShellInterface::Private : public Global::Private
public:
XdgShellInterfaceVersion interfaceVersion;
virtual quint32 ping(XdgShellSurfaceInterface * surface) = 0;
void setupTimer(quint32 serial);
//pingserial/timer correspondence
QHash <quint32, QTimer *> pingTimers;
protected:
Private(XdgShellInterfaceVersion interfaceVersion, XdgShellInterface *q, Display *d, const wl_interface *interface, quint32 version);
XdgShellInterface *q;
@ -56,8 +63,7 @@ public:
XdgShellInterfaceVersion interfaceVersion;
protected:
Private(XdgShellInterfaceVersion interfaceVersion, XdgShellSurfaceInterface *q, XdgShellInterface *c, SurfaceInterface *surface, wl_resource *parentResource, const wl_interface *interface, const void *implementation);
Private(XdgShellInterfaceVersion interfaceVersion, XdgShellSurfaceInterface *q, Global *c, SurfaceInterface *surface, wl_resource *parentResource, const wl_interface *interface, const void *implementation);
};
class XdgShellPopupInterface::Private : public Resource::Private, public GenericShellSurface<XdgShellPopupInterface>
@ -70,8 +76,24 @@ public:
return reinterpret_cast<XdgShellPopupInterface *>(q);
}
virtual quint32 configure(const QRect &rect) {
Q_UNUSED(rect)
return 0;
};
QVector<quint32> configureSerials;
QPointer<SurfaceInterface> parent;
QPoint transientOffset;
QSize initialSize;
/*
*
*/
QRect anchorRect;
Qt::Edges anchorEdge;
Qt::Edges gravity;
PositionerConstraints constraintAdjustments;
QPoint anchorOffset;
XdgShellInterfaceVersion interfaceVersion;
protected:

Loading…
Cancel
Save