diff --git a/autotests/client/test_wayland_windowmanagement.cpp b/autotests/client/test_wayland_windowmanagement.cpp --- a/autotests/client/test_wayland_windowmanagement.cpp +++ b/autotests/client/test_wayland_windowmanagement.cpp @@ -64,6 +64,7 @@ void testShowingDesktop(); void testRequestShowingDesktop_data(); void testRequestShowingDesktop(); + void testParentWindow(); void cleanup(); @@ -470,5 +471,43 @@ QTEST(requestSpy.first().first().value(), "expectedValue"); } +void TestWindowManagement::testParentWindow() +{ + using namespace KWayland::Client; + // this test verifies the functionality of ParentWindows + QCOMPARE(m_windowManagement->windows().count(), 1); + auto parentWindow = m_windowManagement->windows().first(); + QVERIFY(parentWindow); + QVERIFY(parentWindow->parentWindow().isNull()); + + // now let's create a second window + QSignalSpy windowAddedSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); + QVERIFY(windowAddedSpy.isValid()); + auto serverTransient = m_windowManagementInterface->createWindow(this); + serverTransient->setParentWindow(m_windowInterface); + QVERIFY(windowAddedSpy.wait()); + auto transient = windowAddedSpy.first().first().value(); + QCOMPARE(transient->parentWindow().data(), parentWindow); + + // let's unset the parent + QSignalSpy parentWindowChangedSpy(transient, &PlasmaWindow::parentWindowChanged); + QVERIFY(parentWindowChangedSpy.isValid()); + serverTransient->setParentWindow(nullptr); + QVERIFY(parentWindowChangedSpy.wait()); + QVERIFY(transient->parentWindow().isNull()); + + // and set it again + serverTransient->setParentWindow(m_windowInterface); + QVERIFY(parentWindowChangedSpy.wait()); + QCOMPARE(transient->parentWindow().data(), parentWindow); + + // now let's try to unmap the parent + m_windowInterface->unmap(); + m_window = nullptr; + m_windowInterface = nullptr; + QVERIFY(parentWindowChangedSpy.wait()); + QVERIFY(transient->parentWindow().isNull()); +} + QTEST_GUILESS_MAIN(TestWindowManagement) #include "test_wayland_windowmanagement.moc" diff --git a/src/client/plasmawindowmanagement.h b/src/client/plasmawindowmanagement.h --- a/src/client/plasmawindowmanagement.h +++ b/src/client/plasmawindowmanagement.h @@ -439,6 +439,18 @@ **/ quint32 internalId() const; + /** + * The parent window of this PlasmaWindow. + * + * If there is a parent window, this window is a transient window for the + * parent window. If this method returns a null PlasmaWindow it means this + * window is a top level window and is not a transient window. + * + * @see parentWindowChanged + * @since 5.24 + **/ + QPointer parentWindow() const; + Q_SIGNALS: /** * The window title changed. @@ -561,6 +573,12 @@ * signal to perform cleanup. **/ void unmapped(); + /** + * This signal is emitted whenever the parent window changes. + * @see parentWindow + * @since 5.24 + **/ + void parentWindowChanged(); private: friend class PlasmaWindowManagement; diff --git a/src/client/plasmawindowmanagement.cpp b/src/client/plasmawindowmanagement.cpp --- a/src/client/plasmawindowmanagement.cpp +++ b/src/client/plasmawindowmanagement.cpp @@ -85,6 +85,8 @@ QIcon icon; PlasmaWindowManagement *wm = nullptr; bool unmapped = false; + QPointer parentWindow; + QMetaObject::Connection parentWindowUnmappedConnection; private: static void titleChangedCallback(void *data, org_kde_plasma_window *window, const char *title); @@ -94,6 +96,7 @@ static void themedIconNameChangedCallback(void *data, org_kde_plasma_window *window, const char *name); static void unmappedCallback(void *data, org_kde_plasma_window *window); static void initialStateCallback(void *data, org_kde_plasma_window *window); + static void parentWindowCallback(void *data, org_kde_plasma_window *window, org_kde_plasma_window *parent); void setActive(bool set); void setMinimized(bool set); void setMaximized(bool set); @@ -112,6 +115,7 @@ void setMovable(bool set); void setResizable(bool set); void setVirtualDesktopChangeable(bool set); + void setParentWindow(PlasmaWindow *parentWindow); static Private *cast(void *data) { return reinterpret_cast(data); @@ -326,9 +330,43 @@ virtualDesktopChangedCallback, themedIconNameChangedCallback, unmappedCallback, - initialStateCallback + initialStateCallback, + parentWindowCallback }; +void PlasmaWindow::Private::parentWindowCallback(void *data, org_kde_plasma_window *window, org_kde_plasma_window *parent) +{ + Q_UNUSED(window) + Private *p = cast(data); + const auto windows = p->wm->windows(); + auto it = std::find_if(windows.begin(), windows.end(), + [parent] (const PlasmaWindow *w) { + return *w == parent; + } + ); + p->setParentWindow(it != windows.end() ? *it : nullptr); +} + +void PlasmaWindow::Private::setParentWindow(PlasmaWindow *parent) +{ + const auto old = parentWindow; + QObject::disconnect(parentWindowUnmappedConnection); + if (parent && !parent->d->unmapped) { + parentWindow = QPointer(parent); + parentWindowUnmappedConnection = QObject::connect(parent, &PlasmaWindow::unmapped, q, + [this] { + setParentWindow(nullptr); + } + ); + } else { + parentWindow = QPointer(); + parentWindowUnmappedConnection = QMetaObject::Connection(); + } + if (parentWindow.data() != old.data()) { + emit q->parentWindowChanged(); + } +} + void PlasmaWindow::Private::initialStateCallback(void *data, org_kde_plasma_window *window) { Q_UNUSED(window) @@ -818,5 +856,10 @@ return d->internalId; } +QPointer PlasmaWindow::parentWindow() const +{ + return d->parentWindow; +} + } } diff --git a/src/client/protocols/plasma-window-management.xml b/src/client/protocols/plasma-window-management.xml --- a/src/client/protocols/plasma-window-management.xml +++ b/src/client/protocols/plasma-window-management.xml @@ -17,7 +17,7 @@ along with this program. If not, see . ]]> - + This interface manages application windows. It provides requests to show and hide the desktop and emits @@ -83,7 +83,7 @@ - + Manages and control an application window. @@ -219,5 +219,15 @@ initial_state event. + + + + This event will be sent whenever the parent window of this org_kde_plasma_window changes. + The passed parent is another org_kde_plasma_window and this org_kde_plasma_window is a + transient window to the parent window. If the parent argument is null, this + org_kde_plasma_window does not have a parent window. + + + diff --git a/src/client/registry.cpp b/src/client/registry.cpp --- a/src/client/registry.cpp +++ b/src/client/registry.cpp @@ -148,7 +148,7 @@ &Registry::plasmaShellRemoved }}, {Registry::Interface::PlasmaWindowManagement, { - 4, + 5, QByteArrayLiteral("org_kde_plasma_window_management"), &org_kde_plasma_window_management_interface, &Registry::plasmaWindowManagementAnnounced, diff --git a/src/server/plasmawindowmanagement_interface.h b/src/server/plasmawindowmanagement_interface.h --- a/src/server/plasmawindowmanagement_interface.h +++ b/src/server/plasmawindowmanagement_interface.h @@ -139,6 +139,14 @@ */ QHash minimizedGeometries() const; + /** + * Sets this PlasmaWindowInterface as a transient window to @p parentWindow. + * If @p parentWindow is @c nullptr, the PlasmaWindowInterface is a toplevel + * window and does not have a parent window. + * @since 5.24 + **/ + void setParentWindow(PlasmaWindowInterface *parentWindow); + Q_SIGNALS: void closeRequested(); /** diff --git a/src/server/plasmawindowmanagement_interface.cpp b/src/server/plasmawindowmanagement_interface.cpp --- a/src/server/plasmawindowmanagement_interface.cpp +++ b/src/server/plasmawindowmanagement_interface.cpp @@ -73,13 +73,17 @@ void setVirtualDesktop(quint32 desktop); void unmap(); void setState(org_kde_plasma_window_management_state flag, bool set); + void setParentWindow(PlasmaWindowInterface *parent); + wl_resource *resourceForParent(PlasmaWindowInterface *parent, wl_resource *child) const; QVector resources; quint32 windowId = 0; QHash minimizedGeometries; PlasmaWindowManagementInterface *wm; bool unmapped = false; + PlasmaWindowInterface *parentWindow = nullptr; + QMetaObject::Connection parentWindowDestroyConnection; private: static void unbind(wl_resource *resource); @@ -105,7 +109,7 @@ static const struct org_kde_plasma_window_interface s_interface; }; -const quint32 PlasmaWindowManagementInterface::Private::s_version = 4; +const quint32 PlasmaWindowManagementInterface::Private::s_version = 5; PlasmaWindowManagementInterface::Private::Private(PlasmaWindowManagementInterface *q, Display *d) : Global::Private(d, &org_kde_plasma_window_management_interface, s_version) @@ -321,6 +325,8 @@ org_kde_plasma_window_send_state_changed(resource, m_state); org_kde_plasma_window_send_themed_icon_name_changed(resource, m_themedIconName.toUtf8().constData()); + org_kde_plasma_window_send_parent_window(resource, resourceForParent(parentWindow, resource)); + if (unmapped) { org_kde_plasma_window_send_unmapped(resource); } @@ -406,6 +412,46 @@ } } +wl_resource *PlasmaWindowInterface::Private::resourceForParent(PlasmaWindowInterface *parent, wl_resource *child) const +{ + if (!parent) { + return nullptr; + } + auto it = std::find_if(parent->d->resources.begin(), parent->d->resources.end(), + [child] (wl_resource *parentResource) { + return wl_resource_get_client(child) == wl_resource_get_client(parentResource); + } + ); + if (it != parent->d->resources.end()) { + return *it; + } + return nullptr; +} + +void PlasmaWindowInterface::Private::setParentWindow(PlasmaWindowInterface *window) +{ + if (parentWindow == window) { + return; + } + QObject::disconnect(parentWindowDestroyConnection); + parentWindowDestroyConnection = QMetaObject::Connection(); + parentWindow = window; + if (parentWindow) { + parentWindowDestroyConnection = QObject::connect(window, &QObject::destroyed, q, + [this] { + parentWindow = nullptr; + parentWindowDestroyConnection = QMetaObject::Connection(); + for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) { + org_kde_plasma_window_send_parent_window(*it, nullptr); + } + } + ); + } + for (auto it = resources.constBegin(); it != resources.constEnd(); ++it) { + org_kde_plasma_window_send_parent_window(*it, resourceForParent(window, *it)); + } +} + void PlasmaWindowInterface::Private::closeCallback(wl_client *client, wl_resource *resource) { Q_UNUSED(client) @@ -658,5 +704,10 @@ d->setState(ORG_KDE_PLASMA_WINDOW_MANAGEMENT_STATE_VIRTUAL_DESKTOP_CHANGEABLE, set); } +void PlasmaWindowInterface::setParentWindow(PlasmaWindowInterface *parentWindow) +{ + d->setParentWindow(parentWindow); +} + } }