Commit 53f27fee authored by Martin Flöser's avatar Martin Flöser
Browse files

Add support for xdg-shell

Summary:
This change introduces support for the unstable xdg-shell interface in
the server. The implementation is based on version 5 of the unstable
interface. This is the version used by toolkits like e.g. GTK.

There is also a version 6 of the protocol under development which is
incompatible. This makes it difficult to implement it in a backward
compatible way.

Because of that the implementation is a little bit different to other
interfaces and inspired by the TextInput interfaces:
On client side an XdgShell class is exposed which does not represent
it directly. Instead it delegates everything to an XdgShellUnstableV5
implementation. For the Surface/Popup the same is done.

In the Registry it's possible to create an XdgShell and it accepts
the XdgShellUnstableV5 and in future will accept XdgUnstableV6, etc.

On server side it also follows the approach from TextInput. That is
there is a version enum which gets passed to the factory method in
Display. It currently supports only V5, but in future can be extended
for V6. As there is lots of similar code between wl_shell, xdg_shell
and in future xdg_shell_unstable_v6 a templated GenericShellInterface
class is added which combines the common parts.

Reviewers: #plasma_on_wayland

Subscribers: plasma-devel

Tags: #plasma_on_wayland

Differential Revision: https://phabricator.kde.org/D2102
parent e0716c23
......@@ -37,6 +37,8 @@ set(SERVER_LIB_SRCS
textinput_interface.cpp
textinput_interface_v0.cpp
textinput_interface_v2.cpp
xdgshell_interface.cpp
xdgshell_v5_interface.cpp
)
ecm_add_wayland_server_protocol(SERVER_LIB_SRCS
......@@ -113,6 +115,11 @@ ecm_add_wayland_server_protocol(SERVER_LIB_SRCS
BASENAME text-input-unstable-v2
)
ecm_add_wayland_server_protocol(SERVER_LIB_SRCS
PROTOCOL ${KWAYLAND_SOURCE_DIR}/src/client/protocols/xdg-shell-unstable-v5.xml
BASENAME xdg-shell-v5
)
add_library(KF5WaylandServer ${SERVER_LIB_SRCS})
generate_export_header(KF5WaylandServer
BASE_NAME
......@@ -183,6 +190,7 @@ install(FILES
surface_interface.h
textinput_interface.h
touch_interface.h
xdgshell_interface.h
DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KWayland/Server COMPONENT Devel
)
......
......@@ -330,3 +330,15 @@ add_executable(testSelection ${testSelection_SRCS})
target_link_libraries( testSelection Qt5::Test Qt5::Gui KF5::WaylandClient KF5::WaylandServer Wayland::Client)
add_test(kwayland-testSelection testSelection)
ecm_mark_as_test(testSelection)
########################################################
# Test XdgShellV5
########################################################
set( testXdgShellV5_SRCS
test_xdg_shell.cpp
)
add_executable(testXdgShellV5 ${testXdgShellV5_SRCS})
target_link_libraries( testXdgShellV5 Qt5::Test Qt5::Gui KF5::WaylandServer KF5::WaylandClient Wayland::Client)
add_test(kwayland-testXdgShellV5 testXdgShellV5)
ecm_mark_as_test(testXdgShellV5)
......@@ -30,6 +30,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "../../src/client/server_decoration.h"
#include "../../src/client/shell.h"
#include "../../src/client/subcompositor.h"
#include "../../src/client/xdgshell.h"
#include "../../src/server/compositor_interface.h"
#include "../../src/server/datadevicemanager_interface.h"
#include "../../src/server/display.h"
......@@ -45,12 +46,14 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "../../src/server/outputmanagement_interface.h"
#include "../../src/server/outputdevice_interface.h"
#include "../../src/server/textinput_interface.h"
#include "../../src/server/xdgshell_interface.h"
// Wayland
#include <wayland-client-protocol.h>
#include <wayland-dpms-client-protocol.h>
#include <wayland-server-decoration-client-protocol.h>
#include <wayland-text-input-v0-client-protocol.h>
#include <wayland-text-input-v2-client-protocol.h>
#include <wayland-xdg-shell-v5-client-protocol.h>
class TestWaylandRegistry : public QObject
{
......@@ -76,6 +79,7 @@ private Q_SLOTS:
void testBindServerSideDecorationManager();
void testBindTextInputManagerUnstableV0();
void testBindTextInputManagerUnstableV2();
void testBindXdgShellUnstableV5();
void testGlobalSync();
void testGlobalSyncThreaded();
void testRemoval();
......@@ -96,6 +100,7 @@ private:
KWayland::Server::ServerSideDecorationManagerInterface *m_serverSideDecorationManager;
KWayland::Server::TextInputManagerInterface *m_textInputManagerV0;
KWayland::Server::TextInputManagerInterface *m_textInputManagerV2;
KWayland::Server::XdgShellInterface *m_xdgShellUnstableV5;
};
static const QString s_socketName = QStringLiteral("kwin-test-wayland-registry-0");
......@@ -114,6 +119,7 @@ TestWaylandRegistry::TestWaylandRegistry(QObject *parent)
, m_serverSideDecorationManager(nullptr)
, m_textInputManagerV0(nullptr)
, m_textInputManagerV2(nullptr)
, m_xdgShellUnstableV5(nullptr)
{
}
......@@ -152,6 +158,9 @@ void TestWaylandRegistry::init()
m_textInputManagerV2 = m_display->createTextInputManager(KWayland::Server::TextInputInterfaceVersion::UnstableV2);
QCOMPARE(m_textInputManagerV2->interfaceVersion(), KWayland::Server::TextInputInterfaceVersion::UnstableV2);
m_textInputManagerV2->create();
m_xdgShellUnstableV5 = m_display->createXdgShell(KWayland::Server::XdgShellInterfaceVersion::UnstableV5);
m_xdgShellUnstableV5->create();
QCOMPARE(m_xdgShellUnstableV5->interfaceVersion(), KWayland::Server::XdgShellInterfaceVersion::UnstableV5);
}
void TestWaylandRegistry::cleanup()
......@@ -289,6 +298,11 @@ void TestWaylandRegistry::testBindTextInputManagerUnstableV2()
TEST_BIND(KWayland::Client::Registry::Interface::TextInputManagerUnstableV2, SIGNAL(textInputManagerUnstableV2Announced(quint32,quint32)), bindTextInputManagerUnstableV2, zwp_text_input_manager_v2_destroy)
}
void TestWaylandRegistry::testBindXdgShellUnstableV5()
{
TEST_BIND(KWayland::Client::Registry::Interface::XdgShellUnstableV5, SIGNAL(xdgShellUnstableV5Announced(quint32,quint32)), bindXdgShellUnstableV5, xdg_shell_destroy)
}
#undef TEST_BIND
void TestWaylandRegistry::testRemoval()
......
This diff is collapsed.
......@@ -40,6 +40,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "shell_interface.h"
#include "subcompositor_interface.h"
#include "textinput_interface_p.h"
#include "xdgshell_v5_interface_p.h"
#include <QCoreApplication>
#include <QDebug>
......@@ -356,6 +357,18 @@ TextInputManagerInterface *Display::createTextInputManager(const TextInputInterf
return t;
}
XdgShellInterface *Display::createXdgShell(const XdgShellInterfaceVersion &version, QObject *parent)
{
XdgShellInterface *x = nullptr;
switch (version) {
case XdgShellInterfaceVersion::UnstableV5:
x = new XdgShellV5Interface(this, parent);
break;
}
connect(this, &Display::aboutToTerminate, x, [x] { delete x; });
return x;
}
void Display::createShm()
{
Q_ASSERT(d->display);
......
......@@ -71,6 +71,9 @@ class ShellInterface;
class SubCompositorInterface;
enum class TextInputInterfaceVersion;
class TextInputManagerInterface;
class XdgShellV5Interface;
enum class XdgShellInterfaceVersion;
class XdgShellInterface;
/**
* @brief Class holding the Wayland server display loop.
......@@ -179,6 +182,13 @@ public:
**/
TextInputManagerInterface *createTextInputManager(const TextInputInterfaceVersion &version, QObject *parent = nullptr);
/**
* Creates the XdgShell in interface @p version.
*
* @since 5.25
**/
XdgShellInterface *createXdgShell(const XdgShellInterfaceVersion &version, QObject *parent = nullptr);
/**
* Gets the ClientConnection for the given @p client.
* If there is no ClientConnection yet for the given @p client, it will be created.
......
/********************************************************************
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/>.
*********************************************************************/
#ifndef KWAYLAND_SERVER_GENERIC_SHELL_SURFACE_P_H
#define KWAYLAND_SERVER_GENERIC_SHELL_SURFACE_P_H
#include "seat_interface.h"
#include "surface_interface.h"
#include <wayland-server.h>
namespace KWayland
{
namespace Server
{
template <class T>
class GenericShellSurface
{
public:
GenericShellSurface(T *shellSurface, SurfaceInterface *surface)
: surface(surface)
, shellSurface(shellSurface)
{}
SurfaceInterface *surface;
QString title;
QByteArray windowClass;
protected:
void setTitle(const QString &title);
void setWindowClass(const QByteArray &wc);
static void moveCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial);
template <typename U>
static void resizeCallback(wl_client *client, wl_resource *resource, wl_resource * seat, uint32_t serial, uint32_t edges);
static void setTitleCallback(wl_client *client, wl_resource *resource, const char *title);
static void setAppIdCallback(wl_client *client, wl_resource *resource, const char *app_id);
private:
T *q_func() {
return shellSurface;
}
static typename T::Private *userData(wl_resource *resource) {
return reinterpret_cast<typename T::Private*>(wl_resource_get_user_data(resource));
}
T *shellSurface;
};
template <class T>
void GenericShellSurface<T>::setTitleCallback(wl_client *client, wl_resource *resource, const char *title)
{
auto s = userData(resource);
Q_ASSERT(client == *s->client);
s->setTitle(QString::fromUtf8(title));
}
template <class T>
void GenericShellSurface<T>::setAppIdCallback(wl_client *client, wl_resource *resource, const char *app_id)
{
auto s = userData(resource);
Q_ASSERT(client == *s->client);
s->setWindowClass(QByteArray(app_id));
}
template <class T>
void GenericShellSurface<T>::setTitle(const QString &t)
{
if (title == t) {
return;
}
title = t;
Q_Q(T);
emit q->titleChanged(title);
}
template <class T>
void GenericShellSurface<T>::setWindowClass(const QByteArray &wc)
{
if (windowClass == wc) {
return;
}
windowClass = wc;
Q_Q(T);
emit q->windowClassChanged(windowClass);
}
template <class T>
void GenericShellSurface<T>::moveCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial)
{
auto s = userData(resource);
Q_ASSERT(client == *s->client);
emit s->q_func()->moveRequested(SeatInterface::get(seat), serial);
}
namespace {
template <typename T>
Qt::Edges edgesToQtEdges(T edges);
}
template <class T>
template <typename U>
void GenericShellSurface<T>::resizeCallback(wl_client *client, wl_resource *resource, wl_resource * seat, uint32_t serial, uint32_t edges)
{
auto s = userData(resource);
Q_ASSERT(client == *s->client);
emit s->q_func()->resizeRequested(SeatInterface::get(seat), serial, edgesToQtEdges(U(edges)));
}
}
}
#endif
......@@ -18,6 +18,7 @@ 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 "shell_interface.h"
#include "generic_shell_surface_p.h"
#include "global_p.h"
#include "resource_p.h"
#include "display.h"
......@@ -64,15 +65,12 @@ const struct wl_shell_interface ShellInterface::Private::s_interface = {
#endif
class ShellSurfaceInterface::Private : public Resource::Private
class ShellSurfaceInterface::Private : public Resource::Private, public GenericShellSurface<ShellSurfaceInterface>
{
public:
Private(ShellSurfaceInterface *q, ShellInterface *shell, SurfaceInterface *surface, wl_resource *parentResource);
void ping();
SurfaceInterface *surface;
QString title;
QByteArray windowClass;
QScopedPointer<QTimer> pingTimer;
quint32 pingSerial = 0;
enum class WindowMode {
......@@ -87,12 +85,13 @@ public:
bool acceptsKeyboardFocus = true;
void setWindowMode(WindowMode newWindowMode);
ShellSurfaceInterface *q_func() {
return reinterpret_cast<ShellSurfaceInterface *>(q);
}
private:
// interface callbacks
static void pongCallback(wl_client *client, wl_resource *resource, uint32_t serial);
static void moveCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial);
static void resizeCallback(wl_client *client, wl_resource *resource, wl_resource *seat,
uint32_t serial, uint32_t edges);
static void setToplevelCallback(wl_client *client, wl_resource *resource);
static void setTransientCallback(wl_client *client, wl_resource *resource, wl_resource *parent,
int32_t x, int32_t y, uint32_t flags);
......@@ -101,16 +100,9 @@ private:
static void setPopupCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial,
wl_resource *parent, int32_t x, int32_t y, uint32_t flags);
static void setMaximizedCallback(wl_client *client, wl_resource *resource, wl_resource *output);
static void setTitleCallback(wl_client *client, wl_resource *resource, const char *title);
static void setClassCallback(wl_client *client, wl_resource *resource, const char *class_);
void setTitle(const QString &title);
void setWindowClass(const QByteArray &windowClass);
void pong(quint32 serial);
void setAcceptsFocus(quint32 flags);
ShellSurfaceInterface *q_func() {
return reinterpret_cast<ShellSurfaceInterface *>(q);
}
static const struct wl_shell_surface_interface s_interface;
};
......@@ -166,7 +158,7 @@ void ShellInterface::Private::createSurface(wl_client *client, uint32_t version,
*********************************/
ShellSurfaceInterface::Private::Private(ShellSurfaceInterface *q, ShellInterface *shell, SurfaceInterface *surface, wl_resource *parentResource)
: Resource::Private(q, shell, parentResource, &wl_shell_surface_interface, &s_interface)
, surface(surface)
, GenericShellSurface<KWayland::Server::ShellSurfaceInterface>(q, surface)
, pingTimer(new QTimer)
{
pingTimer->setSingleShot(true);
......@@ -177,14 +169,14 @@ ShellSurfaceInterface::Private::Private(ShellSurfaceInterface *q, ShellInterface
const struct wl_shell_surface_interface ShellSurfaceInterface::Private::s_interface = {
pongCallback,
moveCallback,
resizeCallback,
resizeCallback<wl_shell_surface_resize>,
setToplevelCallback,
setTransientCallback,
setFullscreenCallback,
setPopupCallback,
setMaximizedCallback,
setTitleCallback,
setClassCallback
setAppIdCallback
};
#endif
......@@ -262,17 +254,10 @@ void ShellSurfaceInterface::requestSize(const QSize &size)
d->client->flush();
}
void ShellSurfaceInterface::Private::moveCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial)
namespace {
template <>
Qt::Edges edgesToQtEdges(wl_shell_surface_resize edges)
{
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
emit s->q_func()->moveRequested(SeatInterface::get(seat), serial);
}
void ShellSurfaceInterface::Private::resizeCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial, uint32_t edges)
{
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
Qt::Edges qtEdges;
switch (edges) {
case WL_SHELL_SURFACE_RESIZE_TOP:
......@@ -299,10 +284,14 @@ void ShellSurfaceInterface::Private::resizeCallback(wl_client *client, wl_resour
case WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT:
qtEdges = Qt::BottomEdge | Qt::RightEdge;
break;
case WL_SHELL_SURFACE_RESIZE_NONE:
break;
default:
Q_UNREACHABLE();
break;
}
emit s->q_func()->resizeRequested(SeatInterface::get(seat), serial, qtEdges);
return qtEdges;
}
}
void ShellSurfaceInterface::Private::setToplevelCallback(wl_client *client, wl_resource *resource)
......@@ -401,40 +390,6 @@ void ShellSurfaceInterface::Private::setMaximizedCallback(wl_client *client, wl_
s->setWindowMode(WindowMode::Maximized);
}
void ShellSurfaceInterface::Private::setTitleCallback(wl_client *client, wl_resource *resource, const char *title)
{
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
s->setTitle(QString::fromUtf8(title));
}
void ShellSurfaceInterface::Private::setTitle(const QString &t)
{
if (title == t) {
return;
}
title = t;
Q_Q(ShellSurfaceInterface);
emit q->titleChanged(title);
}
void ShellSurfaceInterface::Private::setClassCallback(wl_client *client, wl_resource *resource, const char *class_)
{
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
s->setWindowClass(QByteArray(class_));
}
void ShellSurfaceInterface::Private::setWindowClass(const QByteArray &wc)
{
if (windowClass == wc) {
return;
}
windowClass = wc;
Q_Q(ShellSurfaceInterface);
emit q->windowClassChanged(windowClass);
}
SurfaceInterface *ShellSurfaceInterface::surface() const {
Q_D();
return d->surface;
......
......@@ -40,6 +40,8 @@ class Display;
class SeatInterface;
class SurfaceInterface;
class ShellSurfaceInterface;
template <typename T>
class GenericShellSurface;
/**
* @brief Global for the wl_shell interface.
......@@ -293,6 +295,7 @@ Q_SIGNALS:
private:
friend class ShellInterface;
explicit ShellSurfaceInterface(ShellInterface *shell, SurfaceInterface *parent, wl_resource *parentResource);
friend class GenericShellSurface<ShellSurfaceInterface>;
class Private;
Private *d_func() const;
};
......
/****************************************************************************
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/>.
****************************************************************************/
#include "xdgshell_v5_interface_p.h"
#include "xdgshell_interface_p.h"
#include "generic_shell_surface_p.h"
#include "display.h"
#include "global_p.h"
#include "resource_p.h"
#include "output_interface.h"
#include "seat_interface.h"
#include "surface_interface.h"
#include <wayland-xdg-shell-v5-server-protocol.h>
namespace KWayland
{
namespace Server
{
class XdgShellV5Interface::Private : public XdgShellInterface::Private
{
public:
Private(XdgShellV5Interface *q, Display *d);
QVector<XdgSurfaceV5Interface*> surfaces;
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;
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 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);
static void getXdgPopupCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * surface, wl_resource * parent, wl_resource * seat, uint32_t serial, int32_t x, int32_t y);
static void pongCallback(wl_client *client, wl_resource *resource, uint32_t serial);
XdgShellV5Interface *q;
static const struct xdg_shell_interface s_interface;
static const quint32 s_version;
};
class XdgPopupV5Interface::Private : public XdgShellPopupInterface::Private
{
public:
Private(XdgPopupV5Interface *q, XdgShellV5Interface *c, SurfaceInterface *surface, wl_resource *parentResource);
~Private();
void popupDone() override;
XdgPopupV5Interface *q_func() {
return reinterpret_cast<XdgPopupV5Interface *>(q);
}
private:
static const struct xdg_popup_interface s_interface;
};
const quint32 XdgShellV5Interface::Private::s_version = 1;
#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct xdg_shell_interface XdgShellV5Interface::Private::s_interface = {
destroyCallback,
useUnstableVersionCallback,
getXdgSurfaceCallback,
getXdgPopupCallback,
pongCallback
};
#endif
void XdgShellV5Interface::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 XdgShellV5Interface::Private::useUnstableVersionCallback(wl_client *client, wl_resource *resource, int32_t version)
{
Q_UNUSED(client)
Q_UNUSED(resource)
Q_UNUSED(version)
// TODO: implement
}
void XdgShellV5Interface::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 XdgShellV5Interface::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](XdgSurfaceV5Interface *s) {
return surface == s->surface();
}
);
if (it != surfaces.constEnd()) {
wl_resource_post_error(surface->resource(), XDG_SHELL_ERROR_ROLE, "ShellSurface already created");
return;
}
XdgSurfaceV5Interface *shellSurface = new XdgSurfaceV5Interface(q, surface, parentResource);
surfaces << shellSurface;
QObject::connect(shellSurface, &XdgSurfaceV5Interface::destroyed, q,
[this, shellSurface] {
surfaces.removeAll(shellSurface);