Improve the deconstruction of PlasmaWindows

Summary:
The protocol is extended by a dedicated destructor request. When a
PlasmaWindow is umapped we no longer destroy the resource directly,
but only send the unmap. The client is then supposed to clean up
(which it already did in that case) and will invoke the destructor.

The PlasmaWindowInterface object will be automatically deleted after
the unmap once all resources bound for it are destroyed.

The tests are extended by two new test cases which triggered protocol
errors on the client side prior to this change.

Reviewers: #plasma

Subscribers: plasma-devel

Projects: #plasma

Differential Revision: https://phabricator.kde.org/D1594
This commit is contained in:
Martin Gräßlin 2016-05-12 07:40:27 +02:00
parent f790973375
commit 372955bf05
3 changed files with 114 additions and 50 deletions

View file

@ -44,6 +44,8 @@ private Q_SLOTS:
void testWindowTitle();
void testMinimizedGeometry();
void testUseAfterUnmap();
void testServerDelete();
void cleanup();
@ -180,14 +182,6 @@ void TestWindowManagement::testMinimizedGeometry()
void TestWindowManagement::cleanup()
{
delete m_windowManagementInterface;
m_windowManagementInterface = nullptr;
delete m_windowInterface;
m_windowInterface = nullptr;
delete m_surfaceInterface;
m_surfaceInterface = nullptr;
if (m_surface) {
delete m_surface;
@ -220,9 +214,49 @@ void TestWindowManagement::cleanup()
}
m_connection = nullptr;
delete m_windowManagementInterface;
m_windowManagementInterface = nullptr;
delete m_windowInterface;
m_windowInterface = nullptr;
delete m_surfaceInterface;
m_surfaceInterface = nullptr;
delete m_display;
m_display = nullptr;
}
void TestWindowManagement::testUseAfterUnmap()
{
// this test verifies that when the client uses a window after it's unmapped, things don't break
QSignalSpy unmappedSpy(m_window, &KWayland::Client::PlasmaWindow::unmapped);
QVERIFY(unmappedSpy.isValid());
QSignalSpy destroyedSpy(m_window, &QObject::destroyed);
QVERIFY(destroyedSpy.isValid());
m_windowInterface->unmap();
m_window->requestClose();
QVERIFY(unmappedSpy.wait());
QVERIFY(destroyedSpy.wait());
m_window = nullptr;
QSignalSpy serverDestroyedSpy(m_windowInterface, &QObject::destroyed);
QVERIFY(serverDestroyedSpy.isValid());
QVERIFY(serverDestroyedSpy.wait());
m_windowInterface = nullptr;
}
void TestWindowManagement::testServerDelete()
{
QSignalSpy unmappedSpy(m_window, &KWayland::Client::PlasmaWindow::unmapped);
QVERIFY(unmappedSpy.isValid());
QSignalSpy destroyedSpy(m_window, &QObject::destroyed);
QVERIFY(destroyedSpy.isValid());
delete m_windowInterface;
m_windowInterface = nullptr;
QVERIFY(unmappedSpy.wait());
QVERIFY(destroyedSpy.wait());
m_window = nullptr;
}
QTEST_GUILESS_MAIN(TestWindowManagement)
#include "test_wayland_windowmanagement.moc"

View file

@ -74,17 +74,15 @@ public:
void unmap();
void setState(org_kde_plasma_window_management_state flag, bool set);
struct WindowResource {
wl_resource *resource;
wl_listener *destroyListener;
};
QList<WindowResource> resources;
QVector<wl_resource*> resources;
quint32 windowId = 0;
QHash<SurfaceInterface*, QRect> minimizedGeometries;
PlasmaWindowManagementInterface *wm;
bool unmapped = false;
private:
static void unbind(wl_resource *resource);
static void destroyListenerCallback(wl_listener *listener, void *data);
static void setStateCallback(wl_client *client, wl_resource *resource, uint32_t flags, uint32_t state);
static void setVirtualDesktopCallback(wl_client *client, wl_resource *resource, uint32_t number);
static void closeCallback(wl_client *client, wl_resource *resource);
@ -92,12 +90,12 @@ private:
static void requestResizeCallback(wl_client *client, wl_resource *resource);
static void setMinimizedGeometryCallback(wl_client *client, wl_resource *resource, wl_resource *panel, uint32_t x, uint32_t y, uint32_t width, uint32_t height);
static void unsetMinimizedGeometryCallback(wl_client *client, wl_resource *resource, wl_resource *panel);
static void destroyCallback(wl_client *client, wl_resource *resource);
static Private *cast(wl_resource *resource) {
return reinterpret_cast<Private*>(wl_resource_get_user_data(resource));
}
PlasmaWindowInterface *q;
PlasmaWindowManagementInterface *wm;
QString m_title;
QString m_appId;
QString m_themedIconName;
@ -107,7 +105,7 @@ private:
static const struct org_kde_plasma_window_interface s_interface;
};
const quint32 PlasmaWindowManagementInterface::Private::s_version = 3;
const quint32 PlasmaWindowManagementInterface::Private::s_version = 4;
PlasmaWindowManagementInterface::Private::Private(PlasmaWindowManagementInterface *q, Display *d)
: Global::Private(d, &org_kde_plasma_window_management_interface, s_version)
@ -245,6 +243,17 @@ PlasmaWindowInterface *PlasmaWindowManagementInterface::createWindow(QObject *pa
return window;
}
void PlasmaWindowManagementInterface::unmapWindow(PlasmaWindowInterface *window)
{
if (!window) {
return;
}
Q_D();
d->windows.removeOne(window);
Q_ASSERT(!d->windows.contains(window));
window->d->unmap();
}
#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct org_kde_plasma_window_interface PlasmaWindowInterface::Private::s_interface = {
setStateCallback,
@ -253,7 +262,8 @@ const struct org_kde_plasma_window_interface PlasmaWindowInterface::Private::s_i
unsetMinimizedGeometryCallback,
closeCallback,
requestMoveCallback,
requestResizeCallback
requestResizeCallback,
destroyCallback
};
#endif
@ -268,32 +278,32 @@ PlasmaWindowInterface::Private::~Private()
// need to copy, as destroy goes through the destroy listener and modifies the list as we iterate
const auto c = resources;
for (const auto &r : c) {
org_kde_plasma_window_send_unmapped(r.resource);
wl_resource_destroy(r.resource);
auto client = wl_resource_get_client(r);
org_kde_plasma_window_send_unmapped(r);
wl_resource_destroy(r);
wl_client_flush(client);
}
}
void PlasmaWindowInterface::Private::destroyCallback(wl_client *, wl_resource *r)
{
Private *p = cast(r);
p->resources.removeAll(r);
wl_resource_destroy(r);
if (p->unmapped && p->resources.isEmpty()) {
p->q->deleteLater();
}
}
void PlasmaWindowInterface::Private::unbind(wl_resource *resource)
{
Private *p = reinterpret_cast<Private*>(wl_resource_get_user_data(resource));
auto it = p->resources.begin();
while (it != p->resources.end()) {
if ((*it).resource == resource) {
wl_list_remove(&(*it).destroyListener->link);
delete (*it).destroyListener;
it = p->resources.erase(it);
} else {
it++;
}
p->resources.removeAll(resource);
if (p->unmapped && p->resources.isEmpty()) {
p->q->deleteLater();
}
}
void PlasmaWindowInterface::Private::destroyListenerCallback(wl_listener *listener, void *data)
{
Q_UNUSED(listener);
Private::unbind(reinterpret_cast<wl_resource*>(data));
}
void PlasmaWindowInterface::Private::createResource(wl_resource *parent, uint32_t id)
{
ClientConnection *c = wm->display()->getConnection(wl_resource_get_client(parent));
@ -301,15 +311,8 @@ void PlasmaWindowInterface::Private::createResource(wl_resource *parent, uint32_
if (!resource) {
return;
}
WindowResource r;
r.resource = resource;
r.destroyListener = new wl_listener;
r.destroyListener->notify = destroyListenerCallback;
r.destroyListener->link.prev = nullptr;
r.destroyListener->link.next = nullptr;
wl_resource_set_implementation(resource, &s_interface, this, unbind);
wl_resource_add_destroy_listener(resource, r.destroyListener);
resources << r;
resources << resource;
org_kde_plasma_window_send_virtual_desktop_changed(resource, m_virtualDesktop);
if (!m_appId.isEmpty()) {
@ -331,7 +334,7 @@ void PlasmaWindowInterface::Private::setAppId(const QString &appId)
m_appId = appId;
const QByteArray utf8 = m_appId.toUtf8();
for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) {
org_kde_plasma_window_send_app_id_changed((*it).resource, utf8.constData());
org_kde_plasma_window_send_app_id_changed(*it, utf8.constData());
}
}
@ -343,7 +346,7 @@ void PlasmaWindowInterface::Private::setThemedIconName(const QString &iconName)
m_themedIconName = iconName;
const QByteArray utf8 = m_themedIconName.toUtf8();
for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) {
org_kde_plasma_window_send_themed_icon_name_changed((*it).resource, utf8.constData());
org_kde_plasma_window_send_themed_icon_name_changed(*it, utf8.constData());
}
}
@ -355,7 +358,7 @@ void PlasmaWindowInterface::Private::setTitle(const QString &title)
m_title = title;
const QByteArray utf8 = m_title.toUtf8();
for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) {
org_kde_plasma_window_send_title_changed((*it).resource, utf8.constData());
org_kde_plasma_window_send_title_changed(*it, utf8.constData());
}
}
@ -366,15 +369,18 @@ void PlasmaWindowInterface::Private::setVirtualDesktop(quint32 desktop)
}
m_virtualDesktop = desktop;
for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) {
org_kde_plasma_window_send_virtual_desktop_changed((*it).resource, m_virtualDesktop);
org_kde_plasma_window_send_virtual_desktop_changed(*it, m_virtualDesktop);
}
}
void PlasmaWindowInterface::Private::unmap()
{
unmapped = true;
for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) {
org_kde_plasma_window_send_unmapped((*it).resource);
wl_resource_destroy((*it).resource);
org_kde_plasma_window_send_unmapped(*it);
}
if (resources.isEmpty()) {
q->deleteLater();
}
}
@ -391,7 +397,7 @@ void PlasmaWindowInterface::Private::setState(org_kde_plasma_window_management_s
}
m_state = newState;
for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) {
org_kde_plasma_window_send_state_changed((*it).resource, m_state);
org_kde_plasma_window_send_state_changed(*it, m_state);
}
}
@ -544,7 +550,7 @@ void PlasmaWindowInterface::setVirtualDesktop(quint32 desktop)
void PlasmaWindowInterface::unmap()
{
d->unmap();
d->wm->unmapWindow(this);
}
QHash<SurfaceInterface*, QRect> PlasmaWindowInterface::minimizedGeometries() const

View file

@ -52,6 +52,23 @@ public:
PlasmaWindowInterface *createWindow(QObject *parent);
QList<PlasmaWindowInterface*> windows() const;
/**
* Unmaps the @p window previously created with @link{createWindow}.
* The window will be unmapped and removed from the list of @link{windows}.
*
* Unmapping a @p window indicates to the client that it should destroy the
* resource created for the window. Once all resources for the @p window are
* destroyed, the @p window will get deleted automatically. There is no need
* to manually delete the @p window. A manual delete will trigger the unmap
* and resource destroy at the same time and can result in protocol errors on
* client side if it still accesses the resource before receiving the unmap event.
*
* @see createWindow
* @see windows
* @since 5.23
**/
void unmapWindow(PlasmaWindowInterface *window);
Q_SIGNALS:
void requestChangeShowingDesktop(ShowingDesktopState requestedState);
@ -106,6 +123,13 @@ public:
*/
void setVirtualDesktopChangeable(bool set);
/**
* This method removes the Window and the Client is supposed to release the resource
* bound for this Window. Once all resources are released the Window gets deleted.
*
* Prefer using @link{PlasmaWindowManagementInterface::unmapWindow}.
* @see PlasmaWindowManagementInterface::unmapWindow
**/
void unmap();
/**