Commit d0b3eab8 authored by Vlad Zahorodnii's avatar Vlad Zahorodnii Committed by Vlad Zahorodnii

[server] Make double-buffered properties in xdg-shell double-buffered

Summary:
So far all double-buffered properties in our implementation of xdg-shell
weren't actually double-buffered. When a property setter is invoked, we
pray to the God hoping that the client committed associated surface.

This change introduces private SurfaceRole class. The new class provides
a way for SurfaceInterface to commit pending state of associated shell
surface.

The chosen architecture allows us to do more in the future. For example,
we could use SurfaceRole to prevent associating several roles to a single
wl_surface object, e.g. xdg-toplevel to a pointer surface, etc.

Test Plan: This change breaks support for client-side decorated clients in KWin.

Reviewers: #kwin

Subscribers: kde-frameworks-devel

Tags: #frameworks

Differential Revision: https://phabricator.kde.org/D23745
parent c4a49fbf
......@@ -57,14 +57,18 @@ void XdgShellTestStable::testMaxSize()
QVERIFY(maxSizeSpy.isValid());
xdgSurface->setMaxSize(QSize(100, 100));
surface->commit(Surface::CommitFlag::None);
QVERIFY(maxSizeSpy.wait());
QCOMPARE(maxSizeSpy.count(), 1);
QCOMPARE(maxSizeSpy.last().at(0).value<QSize>(), QSize(100,100));
QCOMPARE(serverXdgSurface->maximumSize(), QSize(100, 100));
xdgSurface->setMaxSize(QSize(200, 200));
surface->commit(Surface::CommitFlag::None);
QVERIFY(maxSizeSpy.wait());
QCOMPARE(maxSizeSpy.count(), 2);
QCOMPARE(maxSizeSpy.last().at(0).value<QSize>(), QSize(200,200));
QCOMPARE(serverXdgSurface->maximumSize(), QSize(200, 200));
}
......@@ -141,14 +145,18 @@ void XdgShellTestStable::testMinSize()
QVERIFY(minSizeSpy.isValid());
xdgSurface->setMinSize(QSize(200, 200));
surface->commit(Surface::CommitFlag::None);
QVERIFY(minSizeSpy.wait());
QCOMPARE(minSizeSpy.count(), 1);
QCOMPARE(minSizeSpy.last().at(0).value<QSize>(), QSize(200,200));
QCOMPARE(serverXdgSurface->minimumSize(), QSize(200, 200));
xdgSurface->setMinSize(QSize(100, 100));
surface->commit(Surface::CommitFlag::None);
QVERIFY(minSizeSpy.wait());
QCOMPARE(minSizeSpy.count(), 2);
QCOMPARE(minSizeSpy.last().at(0).value<QSize>(), QSize(100,100));
QCOMPARE(serverXdgSurface->minimumSize(), QSize(100, 100));
}
//top level then toplevel
......@@ -224,11 +232,10 @@ void XdgShellTestStable::testWindowGeometry()
SURFACE
QSignalSpy windowGeometryChangedSpy(serverXdgSurface, &XdgShellSurfaceInterface::windowGeometryChanged);
xdgSurface->setWindowGeometry(QRect(50, 50, 400, 400));
windowGeometryChangedSpy.wait();
surface->commit(Surface::CommitFlag::None);
QVERIFY(windowGeometryChangedSpy.wait());
QCOMPARE(serverXdgSurface->windowGeometry(), QRect(50, 50, 400, 400));
//add a popup to this surface
XdgPositioner positioner(QSize(10,10), QRect(100,100,50,50));
QSignalSpy xdgPopupCreatedSpy(m_xdgShellInterface, &XdgShellInterface::xdgPopupCreated);
......@@ -240,7 +247,8 @@ void XdgShellTestStable::testWindowGeometry()
QSignalSpy popupWindowGeometryChangedSpy(serverXdgPopup, &XdgShellPopupInterface::windowGeometryChanged);
xdgPopupSurface->setWindowGeometry(QRect(60, 60, 300, 300));
popupWindowGeometryChangedSpy.wait();
popupSurface->commit(Surface::CommitFlag::None);
QVERIFY(popupWindowGeometryChangedSpy.wait());
QCOMPARE(serverXdgPopup->windowGeometry(), QRect(60, 60, 300, 300));
}
......
......@@ -55,14 +55,18 @@ void XdgShellTestV6::testMaxSize()
QVERIFY(maxSizeSpy.isValid());
xdgSurface->setMaxSize(QSize(100, 100));
surface->commit(Surface::CommitFlag::None);
QVERIFY(maxSizeSpy.wait());
QCOMPARE(maxSizeSpy.count(), 1);
QCOMPARE(maxSizeSpy.last().at(0).value<QSize>(), QSize(100,100));
QCOMPARE(serverXdgSurface->maximumSize(), QSize(100, 100));
xdgSurface->setMaxSize(QSize(200, 200));
surface->commit(Surface::CommitFlag::None);
QVERIFY(maxSizeSpy.wait());
QCOMPARE(maxSizeSpy.count(), 2);
QCOMPARE(maxSizeSpy.last().at(0).value<QSize>(), QSize(200,200));
QCOMPARE(serverXdgSurface->maximumSize(), QSize(200, 200));
}
......@@ -139,14 +143,18 @@ void XdgShellTestV6::testMinSize()
QVERIFY(minSizeSpy.isValid());
xdgSurface->setMinSize(QSize(200, 200));
surface->commit(Surface::CommitFlag::None);
QVERIFY(minSizeSpy.wait());
QCOMPARE(minSizeSpy.count(), 1);
QCOMPARE(minSizeSpy.last().at(0).value<QSize>(), QSize(200,200));
QCOMPARE(serverXdgSurface->minimumSize(), QSize(200, 200));
xdgSurface->setMinSize(QSize(100, 100));
surface->commit(Surface::CommitFlag::None);
QVERIFY(minSizeSpy.wait());
QCOMPARE(minSizeSpy.count(), 2);
QCOMPARE(minSizeSpy.last().at(0).value<QSize>(), QSize(100,100));
QCOMPARE(serverXdgSurface->minimumSize(), QSize(100, 100));
}
//top level then toplevel
......
......@@ -49,6 +49,7 @@ set(SERVER_LIB_SRCS
slide_interface.cpp
subcompositor_interface.cpp
surface_interface.cpp
surfacerole.cpp
textinput_interface.cpp
textinput_interface_v0.cpp
textinput_interface_v2.cpp
......
......@@ -22,6 +22,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "seat_interface.h"
#include "surface_interface.h"
#include "surfacerole_p.h"
#include <wayland-server.h>
namespace KWayland
......@@ -31,11 +32,12 @@ namespace Server
{
template <class T>
class GenericShellSurface
class GenericShellSurface : public SurfaceRole
{
public:
GenericShellSurface(T *shellSurface, SurfaceInterface *surface)
: surface(surface)
: SurfaceRole(surface)
, surface(surface)
, shellSurface(shellSurface)
{}
......
......@@ -71,6 +71,8 @@ public:
Private(ShellSurfaceInterface *q, ShellInterface *shell, SurfaceInterface *surface, wl_resource *parentResource);
void ping();
void commit() override;
QScopedPointer<QTimer> pingTimer;
quint32 pingSerial = 0;
enum class WindowMode {
......@@ -195,6 +197,10 @@ ShellSurfaceInterface::ShellSurfaceInterface(ShellInterface *shell, SurfaceInter
ShellSurfaceInterface::~ShellSurfaceInterface() = default;
void ShellSurfaceInterface::Private::commit()
{
}
void ShellSurfaceInterface::Private::pongCallback(wl_client *client, wl_resource *resource, uint32_t serial)
{
auto s = cast<Private>(resource);
......
......@@ -27,6 +27,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "region_interface.h"
#include "subcompositor_interface.h"
#include "subsurface_interface_p.h"
#include "surfacerole_p.h"
// Qt
#include <QListIterator>
// Wayland
......@@ -529,6 +530,9 @@ void SurfaceInterface::Private::commit()
subSurface->d_func()->commit();
}
}
if (role) {
role->commit();
}
emit q->committed();
}
......
......@@ -374,6 +374,7 @@ private:
friend class ContrastManagerInterface;
friend class IdleInhibitManagerUnstableV1Interface;
friend class PointerConstraintsUnstableV1Interface;
friend class SurfaceRole;
explicit SurfaceInterface(CompositorInterface *parent, wl_resource *parentResource);
class Private;
......
......@@ -34,6 +34,7 @@ namespace Server
{
class IdleInhibitorInterface;
class SurfaceRole;
class SurfaceInterface::Private : public Resource::Private
{
......@@ -86,6 +87,8 @@ public:
void commitSubSurface();
void commit();
SurfaceRole *role = nullptr;
State current;
State pending;
State subSurfacePending;
......
/****************************************************************************
Copyright 2019 Vlad Zagorodniy <vladzzag@gmail.com>
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 "surfacerole_p.h"
#include "surface_interface_p.h"
#include "surface_interface.h"
namespace KWayland
{
namespace Server
{
SurfaceRole::SurfaceRole(SurfaceInterface *surface)
: m_surface(surface)
{
m_surface->d_func()->role = this;
}
SurfaceRole::~SurfaceRole()
{
// Lifetime of the surface role is not bounded to the associated surface.
if (m_surface) {
m_surface->d_func()->role = nullptr;
}
}
}
}
/****************************************************************************
Copyright 2019 Vlad Zagorodniy <vladzzag@gmail.com>
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_SURFACEROLE_P_H
#define KWAYLAND_SERVER_SURFACEROLE_P_H
#include <QPointer>
namespace KWayland
{
namespace Server
{
class SurfaceInterface;
class SurfaceRole
{
public:
explicit SurfaceRole(SurfaceInterface *surface);
virtual ~SurfaceRole();
virtual void commit() = 0;
private:
QPointer<SurfaceInterface> m_surface;
Q_DISABLE_COPY(SurfaceRole)
};
}
}
#endif // KWAYLAND_SERVER_SURFACEROLE_P_H
......@@ -157,7 +157,19 @@ void XdgShellSurfaceInterface::close()
QRect XdgShellSurfaceInterface::windowGeometry() const
{
Q_D();
return d->windowGeometry;
return d->windowGeometry();
}
QSize XdgShellSurfaceInterface::minimumSize() const
{
Q_D();
return d->minimumSize();
}
QSize XdgShellSurfaceInterface::maximumSize() const
{
Q_D();
return d->maximumSize();
}
XdgShellSurfaceInterface::Private *XdgShellSurfaceInterface::d_func() const
......@@ -261,7 +273,7 @@ PositionerConstraints XdgShellPopupInterface::constraintAdjustments() const
QRect XdgShellPopupInterface::windowGeometry() const
{
Q_D();
return d->windowGeometry;
return d->windowGeometry();
}
void XdgShellPopupInterface::popupDone()
......
......@@ -287,6 +287,18 @@ public:
*/
QRect windowGeometry() const;
/**
* @returns The minimum size for the window specified by the client.
* @since 5.65
*/
QSize minimumSize() const;
/**
* @returns The maximum size for the window specified by the client.
* @since 5.65
*/
QSize maximumSize() const;
Q_SIGNALS:
/**
* Emitted whenever the title changes.
......
......@@ -53,6 +53,9 @@ public:
virtual void close() = 0;
virtual quint32 configure(States states, const QSize &size) = 0;
virtual QRect windowGeometry() const = 0;
virtual QSize minimumSize() const = 0;
virtual QSize maximumSize() const = 0;
XdgShellSurfaceInterface *q_func() {
return reinterpret_cast<XdgShellSurfaceInterface *>(q);
......@@ -60,7 +63,6 @@ public:
QVector<quint32> configureSerials;
QPointer<XdgShellSurfaceInterface> parent;
QRect windowGeometry;
XdgShellInterfaceVersion interfaceVersion;
protected:
......@@ -72,6 +74,7 @@ class XdgShellPopupInterface::Private : public Resource::Private, public Generic
public:
virtual ~Private();
virtual void popupDone() = 0;
virtual QRect windowGeometry() const = 0;
XdgShellPopupInterface *q_func() {
return reinterpret_cast<XdgShellPopupInterface *>(q);
......@@ -94,7 +97,6 @@ public:
Qt::Edges gravity;
PositionerConstraints constraintAdjustments;
QPoint anchorOffset;
QRect windowGeometry;
XdgShellInterfaceVersion interfaceVersion;
......
/****************************************************************************
Copyright 2017 David Edmundson <davidedmundson@kde.org>
Copyright 2019 Vlad Zagorodniy <vladzzag@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
......@@ -74,6 +75,9 @@ public:
Private(XdgPopupStableInterface *q, XdgShellStableInterface *c, SurfaceInterface *surface, wl_resource *parentResource);
~Private() override;
QRect windowGeometry() const override;
void commit() override;
void ackConfigure(quint32 serial) {
if (!configureSerials.contains(serial)) {
return;
......@@ -94,9 +98,23 @@ public:
return static_cast<XdgPopupStableInterface *>(q);
}
private:
void setWindowGeometryCallback(const QRect &rect);
static void grabCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial);
static const struct xdg_popup_interface s_interface;
struct ShellSurfaceState
{
QRect windowGeometry;
bool windowGeometryIsSet = false;
};
ShellSurfaceState m_currentState;
ShellSurfaceState m_pendingState;
friend class XdgSurfaceStableInterface;
};
class XdgSurfaceStableInterface::Private : public KWayland::Server::Resource::Private
......@@ -135,7 +153,11 @@ public:
Private(XdgTopLevelStableInterface* q, XdgShellStableInterface* c, SurfaceInterface* surface, wl_resource* parentResource);
~Private() override;
QRect windowGeometry() const override;
QSize minimumSize() const override;
QSize maximumSize() const override;
void close() override;
void commit() override;
void ackConfigure(quint32 serial) {
if (!configureSerials.contains(serial)) {
......@@ -188,6 +210,8 @@ public:
}
private:
void setWindowGeometryCallback(const QRect &rect);
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);
......@@ -200,6 +224,22 @@ private:
static void setMinimizedCallback(wl_client *client, wl_resource *resource);
static const struct xdg_toplevel_interface s_interface;
struct ShellSurfaceState
{
QRect windowGeometry;
QSize minimumSize = QSize(0, 0);
QSize maximiumSize = QSize(INT32_MAX, INT32_MAX);
bool windowGeometryIsSet = false;
bool minimumSizeIsSet = false;
bool maximumSizeIsSet = false;
};
ShellSurfaceState m_currentState;
ShellSurfaceState m_pendingState;
friend class XdgSurfaceStableInterface;
};
......@@ -441,6 +481,8 @@ void XdgSurfaceStableInterface::Private::getTopLevelCallback(wl_client *client,
void XdgSurfaceStableInterface::Private::createTopLevel(wl_client *client, uint32_t version, uint32_t id, wl_resource *parentResource)
{
// FIXME: That's incorrect! The client may have asked us to create an xdg-toplevel
// for a pointer surface or a subsurface. We have to post an error in that case.
if (m_topLevel) {
wl_resource_post_error(parentResource, XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED, "Toplevel already created on this surface");
return;
......@@ -464,7 +506,8 @@ void XdgSurfaceStableInterface::Private::getPopupCallback(wl_client *client, wl_
void XdgSurfaceStableInterface::Private::createPopup(wl_client *client, uint32_t version, uint32_t id, wl_resource *parentResource, wl_resource *parentSurface, wl_resource *positioner)
{
// FIXME: That's incorrect! The client may have asked us to create an xdg-popup
// for a pointer surface or a subsurface. We have to post an error in that case.
if (m_topLevel) {
wl_resource_post_error(parentResource, XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED, "Toplevel already created on this surface");
return;
......@@ -518,14 +561,15 @@ void XdgSurfaceStableInterface::Private::setWindowGeometryCallback(wl_client *cl
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
const QRect windowRect(x, y, width, height);
if (width < 0 || height < 0) {
wl_resource_post_error(resource, XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE , "Tried to set invalid xdg-surface geometry");
return;
}
if (s->m_topLevel) {
s->m_topLevel->d_func()->windowGeometry = windowRect;
emit s->m_topLevel->windowGeometryChanged(windowRect);
s->m_topLevel->d_func()->setWindowGeometryCallback(QRect(x, y, width, height));
} else if (s->m_popup) {
s->m_popup->d_func()->windowGeometry = windowRect;
emit s->m_popup->windowGeometryChanged(windowRect);
s->m_popup->d_func()->setWindowGeometryCallback(QRect(x, y, width, height));
}
}
......@@ -706,24 +750,90 @@ void XdgPositionerStableInterface::Private::setOffsetCallback(wl_client *client,
s->anchorOffset = QPoint(x,y);
}
QRect XdgTopLevelStableInterface::Private::windowGeometry() const
{
return m_currentState.windowGeometry;
}
QSize XdgTopLevelStableInterface::Private::minimumSize() const
{
return m_currentState.minimumSize;
}
QSize XdgTopLevelStableInterface::Private::maximumSize() const
{
return m_currentState.maximiumSize;
}
void XdgTopLevelStableInterface::Private::close()
{
xdg_toplevel_send_close(resource);
client->flush();
}
void XdgTopLevelStableInterface::Private::commit()
{
const bool windowGeometryChanged = m_pendingState.windowGeometryIsSet;
const bool minimumSizeChanged = m_pendingState.minimumSizeIsSet;
const bool maximumSizeChanged = m_pendingState.maximumSizeIsSet;
if (windowGeometryChanged) {
m_currentState.windowGeometry = m_pendingState.windowGeometry;
}
if (minimumSizeChanged) {
m_currentState.minimumSize = m_pendingState.minimumSize;
}
if (maximumSizeChanged) {
m_currentState.maximiumSize = m_pendingState.maximiumSize;
}
m_pendingState = ShellSurfaceState{};
if (windowGeometryChanged) {
emit q_func()->windowGeometryChanged(m_currentState.windowGeometry);
}
if (minimumSizeChanged) {
emit q_func()->minSizeChanged(m_currentState.minimumSize);
}
if (maximumSizeChanged) {
emit q_func()->maxSizeChanged(m_currentState.maximiumSize);
}
}
void XdgTopLevelStableInterface::Private::setMaxSizeCallback(wl_client *client, wl_resource *resource, int32_t width, int32_t height)
{
if (width < 0 || height < 0) {
wl_resource_post_error(resource, XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, "Tried to set invalid xdg-toplevel maximum size");
return;
}
if (width == 0) {
width = INT32_MAX;
}
if (height == 0) {
height = INT32_MAX;
}
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
s->q_func()->maxSizeChanged(QSize(width, height));
s->m_pendingState.maximiumSize = QSize(width, height);
s->m_pendingState.maximumSizeIsSet = true;
}
void XdgTopLevelStableInterface::Private::setMinSizeCallback(wl_client *client, wl_resource *resource, int32_t width, int32_t height)
{
if (width < 0 || height < 0) {
wl_resource_post_error(resource, XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE, "Tried to set invalid xdg-toplevel minimum size");
return;
}
auto s = cast<Private>(resource);
Q_ASSERT(client == *s->client);
s->q_func()->minSizeChanged(QSize(width, height));
s->m_pendingState.minimumSize = QSize(width, height);
s->m_pendingState.minimumSizeIsSet = true;
}
void XdgTopLevelStableInterface::Private::setWindowGeometryCallback(const QRect &rect)
{
m_pendingState.windowGeometry = rect;
m_pendingState.windowGeometryIsSet = true;
}
const struct xdg_toplevel_interface XdgTopLevelStableInterface::Private::s_interface = {
......@@ -831,6 +941,32 @@ XdgPopupStableInterface::Private::Private(XdgPopupStableInterface *q, XdgShellSt
{
}
QRect XdgPopupStableInterface::Private::windowGeometry() const
{
return m_currentState.windowGeometry;
}
void XdgPopupStableInterface::Private::commit()
{
const bool windowGeometryChanged = m_pendingState.windowGeometryIsSet;
if (windowGeometryChanged) {
m_currentState.windowGeometry = m_pendingState.windowGeometry;
}
m_pendingState = ShellSurfaceState{};
if (windowGeometryChanged) {
emit q_func()->windowGeometryChanged(m_currentState.windowGeometry);
}
}
void XdgPopupStableInterface::Private::setWindowGeometryCallback(const QRect &rect)
{
m_pendingState.windowGeometry = rect;
m_pendingState.windowGeometryIsSet = true;
}
void XdgPopupStableInterface::Private::grabCallback(wl_client *client, wl_resource *resource, wl_resource *seat, uint32_t serial)
{
Q_UNUSED(client)
......
/****************************************************************************
Copyright 2016 Martin Gräßlin <mgraesslin@kde.org>
Copyright 2019 Vlad Zagorodniy <vladzzag@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
......@@ -71,6 +72,8 @@ public:
Private(XdgPopupV5Interface *q, XdgShellV5Interface *c, SurfaceInterface *surface, wl_resource *parentResource);
~Private();
QRect windowGeometry() const override;