summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohan Klokkhammer Helsing <johan.helsing@qt.io>2018-03-06 13:06:11 +0100
committerJohan Helsing <johan.helsing@qt.io>2018-05-07 11:34:41 +0000
commitc5ab40d2573fde27a14d7c05d8693966c65a400c (patch)
treed93c4647c90b0d72059b9148a6b4ca1d3f7cc272
parent9db98c7e0a2656988d5c549f0618b2ba882f1fc9 (diff)
Client: Add acked configure support and implement it for xdg-shell v6
The problem: The code in QWaylandWindow::setWindowStateInternal made many assumptions about how the shell surface responded to new window states, and when and if they were applied. Particularly: - The shell integrations support different subsets of Qt::WindowStates, so it doesn't make sense to map from states to setFullscreen, setNormal, etc. in QWaylandWindow because it really depends on the shell integration how it should be handled. - Some states are not supported/unknown on some shells and should immediately be rejected. - On some shells, particularly those where the current state is unknown, flags need to be resent even though they didn't change. - Should handleWindowStatesChanged be called immediately (client decides) or should it wait for a configure event (compositor decides)? I.e. the mState variable only makes sense for some shells. Furthermore, when state changes come from the compositors, some shell integrations require that a configure event is acked by the client and that the next committed buffer's size and content matches that configure event. Previously, we would just ack immediately and still send a buffer with the old size before a correct frame was drawn. i.e. sending incorrect acks, which would lead to protocol errors on some compositors. Additionally, QWaylandWindow::createDecoration() also assumed that windows should have decorations unless they are fullscreen. This is not always the case, particularly on ivi-application and probably on future shell integrations for embedded or tiling window managers etc. The Solution: The responsibility of mapping requested window states to Wayland requests have been moved to the QWaylandShellSurface implementation. QWaylandWindow now calls a new virtual, QWaylandShellSurface::requestWindowStates(Qt::WindowStates), instead of trying to be smart about it. The virtual getters and setters for window states have now been removed from the QWaylandShellSurface interface. It's now also the shell surface implementation's responsibility to call QWaylandWindow::handleWindowStatesChanged if and when it knows a new state is effective. QWaylandWindow::configure has been replaced with QWaylandWindow::applyConfigureWhenPossible(), which causes another new virtual, QWaylandShellSurface::applyConfigure(), to be called whenever we're free to resize and change the states of the next buffer (this is when states should be acked). This means that shells that use acked states need to store the pending states themselves, call applyConfigureWhenPossible(), wait for applyConfigure() to be called, call resizeFromApplyConfigure() and handleWindowStatesChanged(), and finally ack the applied state. Acked state for xdg-shell v5 and v6 has now been implemented, which also means we've now: [ChangeLog][QPA plugin] Implemented support for maximizing, minimizing, and setting fullscreen with xdg-shell unstable v6. [ChangeLog][QPA plugin] QWindow::isActive now follows configure events on xdg-shell unstable v6 (like v5). QWaylandWindow::createDecoration queries QWaylandShellSurface::wantsDecoration before creating window decorations, instead of using the previously unreliable QWaylandWindow::isFullscreen(). [ChangeLog][QPA plugin] Window decorations are now automatically disabled for ivi-application. The refactor also removes a couple of hacks: - QWindowSystemInterface::flushWindowSystemEvents() was called in QWaylandWindow::setWindowStates. Since this hack was introduced, the events now have oldState and newState members, and their values seem to make sense (ensured in the tests). - The hack for unminimizing on xdg-shell v5 in QWaylandWindow::createDecoration was not needed anymore. Finally, tests have been added for xdg-shell v6 to ensure that the right Wayland requests are sent, that we respond to configure events from the compositor, and that the Qt events and signals emitted on the client side make sense. Task-number: QTBUG-53702 Task-number: QTBUG-63417 Task-number: QTBUG-63748 Task-number: QTBUG-66928 Change-Id: Ib4c36b69105750f9dbdcc78adcf71e2e994cc70d Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
-rw-r--r--src/client/qwaylandshellsurface_p.h9
-rw-r--r--src/client/qwaylandwindow.cpp120
-rw-r--r--src/client/qwaylandwindow_p.h36
-rw-r--r--src/client/qwaylandwlshellsurface.cpp81
-rw-r--r--src/client/qwaylandwlshellsurface_p.h21
-rw-r--r--src/client/qwaylandxdgshellv6.cpp119
-rw-r--r--src/client/qwaylandxdgshellv6_p.h14
-rw-r--r--src/client/qwaylandxdgshellv6integration.cpp14
-rw-r--r--src/client/qwaylandxdgshellv6integration_p.h1
-rw-r--r--src/client/qwaylandxdgsurface.cpp149
-rw-r--r--src/client/qwaylandxdgsurface_p.h21
-rw-r--r--src/plugins/decorations/bradient/main.cpp7
-rw-r--r--src/plugins/shellintegration/ivi-shell/qwaylandivisurface.cpp8
-rw-r--r--src/plugins/shellintegration/ivi-shell/qwaylandivisurface_p.h2
-rw-r--r--tests/auto/client/client/tst_client.cpp20
-rw-r--r--tests/auto/client/shared/mockcompositor.cpp5
-rw-r--r--tests/auto/client/shared/mockcompositor.h14
-rw-r--r--tests/auto/client/shared/mockxdgshellv6.cpp35
-rw-r--r--tests/auto/client/shared/mockxdgshellv6.h5
-rw-r--r--tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp189
20 files changed, 591 insertions, 279 deletions
diff --git a/src/client/qwaylandshellsurface_p.h b/src/client/qwaylandshellsurface_p.h
index fdc309a63..6b6bb9e84 100644
--- a/src/client/qwaylandshellsurface_p.h
+++ b/src/client/qwaylandshellsurface_p.h
@@ -96,12 +96,9 @@ public:
inline QWaylandWindow *window() { return m_window; }
virtual void setType(Qt::WindowType type, QWaylandWindow *transientParent) = 0;
-
-protected:
- virtual void setMaximized() {}
- virtual void setFullscreen() {}
- virtual void setNormal() {}
- virtual void setMinimized() {}
+ virtual void applyConfigure() {}
+ virtual void requestWindowStates(Qt::WindowStates states) {Q_UNUSED(states);}
+ virtual bool wantsDecorations() const { return false; }
private:
QWaylandWindow *m_window = nullptr;
diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp
index 6154689df..21c9f82b0 100644
--- a/src/client/qwaylandwindow.cpp
+++ b/src/client/qwaylandwindow.cpp
@@ -200,11 +200,8 @@ void QWaylandWindow::initWindow()
else
setGeometry_helper(window()->geometry());
setMask(window()->mask());
- // setWindowStateInternal is a no-op if the argument is equal to mState,
- // but since we're creating the shellsurface only now we reset mState to
- // make sure the state gets sent out to the compositor
- mState = Qt::WindowNoState;
- setWindowStateInternal(window()->windowStates());
+ if (mShellSurface)
+ mShellSurface->requestWindowStates(window()->windowStates());
handleContentOrientationChange(window()->contentOrientation());
mFlags = window()->flags();
}
@@ -333,7 +330,16 @@ void QWaylandWindow::setGeometry(const QRect &rect)
sendExposeEvent(QRect(QPoint(), geometry().size()));
}
+void QWaylandWindow::resizeFromApplyConfigure(const QSize &sizeWithMargins, const QPoint &offset)
+{
+ QMargins margins = frameMargins();
+ int widthWithoutMargins = qMax(sizeWithMargins.width() - (margins.left()+margins.right()), 1);
+ int heightWithoutMargins = qMax(sizeWithMargins.height() - (margins.top()+margins.bottom()), 1);
+ QRect geometry(QPoint(), QSize(widthWithoutMargins, heightWithoutMargins));
+ mOffset += offset;
+ setGeometry(geometry);
+}
void QWaylandWindow::sendExposeEvent(const QRect &rect)
{
@@ -421,46 +427,24 @@ void QWaylandWindow::setMask(const QRegion &mask)
wl_surface::commit();
}
-void QWaylandWindow::configure(uint32_t edges, int32_t width, int32_t height)
+void QWaylandWindow::applyConfigureWhenPossible()
{
QMutexLocker resizeLocker(&mResizeLock);
- mConfigure.edges |= edges;
- mConfigure.width = width;
- mConfigure.height = height;
-
- if (!mRequestResizeSent && !mConfigure.isEmpty()) {
- mRequestResizeSent= true;
- QMetaObject::invokeMethod(this, "requestResize", Qt::QueuedConnection);
+ if (!mWaitingToApplyConfigure) {
+ mWaitingToApplyConfigure = true;
+ QMetaObject::invokeMethod(this, "applyConfigure", Qt::QueuedConnection);
}
}
-void QWaylandWindow::doResize()
+void QWaylandWindow::doApplyConfigure()
{
- if (mConfigure.isEmpty()) {
+ if (!mWaitingToApplyConfigure)
return;
- }
-
- int widthWithoutMargins = qMax(mConfigure.width-(frameMargins().left() +frameMargins().right()),1);
- int heightWithoutMargins = qMax(mConfigure.height-(frameMargins().top()+frameMargins().bottom()),1);
- widthWithoutMargins = qMax(widthWithoutMargins, window()->minimumSize().width());
- heightWithoutMargins = qMax(heightWithoutMargins, window()->minimumSize().height());
- QRect geometry = QRect(0,0, widthWithoutMargins, heightWithoutMargins);
-
- int x = 0;
- int y = 0;
- QSize size = this->geometry().size();
- if (mConfigure.edges & WL_SHELL_SURFACE_RESIZE_LEFT) {
- x = size.width() - geometry.width();
- }
- if (mConfigure.edges & WL_SHELL_SURFACE_RESIZE_TOP) {
- y = size.height() - geometry.height();
- }
- mOffset += QPoint(x, y);
-
- setGeometry(geometry);
+ if (mShellSurface)
+ mShellSurface->applyConfigure();
- mConfigure.clear();
+ mWaitingToApplyConfigure = false;
}
void QWaylandWindow::setCanResize(bool canResize)
@@ -472,8 +456,8 @@ void QWaylandWindow::setCanResize(bool canResize)
if (mResizeDirty) {
QWindowSystemInterface::handleGeometryChange(window(), geometry());
}
- if (!mConfigure.isEmpty()) {
- doResize();
+ if (mWaitingToApplyConfigure) {
+ doApplyConfigure();
sendExposeEvent(QRect(QPoint(), geometry().size()));
} else if (mResizeDirty) {
mResizeDirty = false;
@@ -482,15 +466,13 @@ void QWaylandWindow::setCanResize(bool canResize)
}
}
-void QWaylandWindow::requestResize()
+void QWaylandWindow::applyConfigure()
{
QMutexLocker lock(&mResizeLock);
- if (mCanResize || !mSentInitialResize) {
- doResize();
- }
+ if (mCanResize || !mSentInitialResize)
+ doApplyConfigure();
- mRequestResizeSent = false;
lock.unlock();
sendExposeEvent(QRect(QPoint(), geometry().size()));
QWindowSystemInterface::flushWindowSystemEvents();
@@ -672,10 +654,10 @@ void QWaylandWindow::setOrientationMask(Qt::ScreenOrientations mask)
mShellSurface->setContentOrientationMask(mask);
}
-void QWaylandWindow::setWindowState(Qt::WindowStates state)
+void QWaylandWindow::setWindowState(Qt::WindowStates states)
{
- if (setWindowStateInternal(state))
- QWindowSystemInterface::flushWindowSystemEvents(); // Required for oldState to work on WindowStateChanged
+ if (mShellSurface)
+ mShellSurface->requestWindowStates(states);
}
void QWaylandWindow::setWindowFlags(Qt::WindowFlags flags)
@@ -689,20 +671,6 @@ void QWaylandWindow::setWindowFlags(Qt::WindowFlags flags)
bool QWaylandWindow::createDecoration()
{
- // so far only xdg-shell support this "unminimize" trick, may be moved elsewhere
- if (mState & Qt::WindowMinimized) {
- QWaylandXdgSurface *xdgSurface = qobject_cast<QWaylandXdgSurface *>(mShellSurface);
- if ( xdgSurface ) {
- Qt::WindowStates states;
- if (xdgSurface->isFullscreen())
- states |= Qt::WindowFullScreen;
- if (xdgSurface->isMaximized())
- states |= Qt::WindowMaximized;
-
- setWindowStateInternal(states);
- }
- }
-
if (!mDisplay->supportsWindowDecoration())
return false;
@@ -719,12 +687,14 @@ bool QWaylandWindow::createDecoration()
default:
break;
}
- if (mFlags & Qt::FramelessWindowHint || isFullscreen())
+ if (mFlags & Qt::FramelessWindowHint)
decoration = false;
if (mFlags & Qt::BypassWindowManagerHint)
decoration = false;
if (mSubSurfaceWindow)
decoration = false;
+ if (mShellSurface && !mShellSurface->wantsDecorations())
+ decoration = false;
bool hadDecoration = mWindowDecoration;
if (decoration && !decorationPluginFailed) {
@@ -970,31 +940,11 @@ bool QWaylandWindow::setMouseGrabEnabled(bool grab)
return true;
}
-bool QWaylandWindow::setWindowStateInternal(Qt::WindowStates state)
+void QWaylandWindow::handleWindowStatesChanged(Qt::WindowStates states)
{
- if (mState == state) {
- return false;
- }
-
- // As of february 2013 QWindow::setWindowState sets the new state value after
- // QPlatformWindow::setWindowState returns, so we cannot rely on QWindow::windowState
- // here. We use then this mState variable.
- mState = state;
-
- if (mShellSurface) {
- createDecoration();
- if (state & Qt::WindowMaximized)
- mShellSurface->setMaximized();
- if (state & Qt::WindowFullScreen)
- mShellSurface->setFullscreen();
- if (state & Qt::WindowMinimized)
- mShellSurface->setMinimized();
- if (!state)
- mShellSurface->setNormal();
- }
-
- QWindowSystemInterface::handleWindowStateChanged(window(), mState);
- return true;
+ createDecoration();
+ QWindowSystemInterface::handleWindowStateChanged(window(), states, mLastReportedWindowStates);
+ mLastReportedWindowStates = states;
}
void QWaylandWindow::sendProperty(const QString &name, const QVariant &value)
diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h
index 3324bf700..55f3a515f 100644
--- a/src/client/qwaylandwindow_p.h
+++ b/src/client/qwaylandwindow_p.h
@@ -77,23 +77,6 @@ class QWaylandScreen;
class QWaylandShmBackingStore;
class QWaylandPointerEvent;
-class Q_WAYLAND_CLIENT_EXPORT QWaylandWindowConfigure
-{
-public:
- QWaylandWindowConfigure()
- { }
-
- void clear()
- { width = height = edges = 0; }
-
- bool isEmpty() const
- { return !height || !width; }
-
- int width = 0;
- int height = 0;
- uint32_t edges = 0;
-};
-
class Q_WAYLAND_CLIENT_EXPORT QWaylandWindow : public QObject, public QPlatformWindow, public QtWayland::wl_surface
{
Q_OBJECT
@@ -118,8 +101,9 @@ public:
void setWindowIcon(const QIcon &icon) override;
void setGeometry(const QRect &rect) override;
+ void resizeFromApplyConfigure(const QSize &sizeWithMargins, const QPoint &offset = {0, 0});
- void configure(uint32_t edges, int32_t width, int32_t height);
+ void applyConfigureWhenPossible(); //rename to possible?
using QtWayland::wl_surface::attach;
void attach(QWaylandBuffer *buffer, int x, int y);
@@ -145,8 +129,9 @@ public:
void handleContentOrientationChange(Qt::ScreenOrientation orientation) override;
void setOrientationMask(Qt::ScreenOrientations mask);
- void setWindowState(Qt::WindowStates state) override;
+ void setWindowState(Qt::WindowStates states) override;
void setWindowFlags(Qt::WindowFlags flags) override;
+ void handleWindowStatesChanged(Qt::WindowStates states);
void raise() override;
void lower() override;
@@ -170,9 +155,6 @@ public:
bool createDecoration();
- inline bool isMaximized() const { return mState & Qt::WindowMaximized; }
- inline bool isFullscreen() const { return mState & Qt::WindowFullScreen; }
-
#if QT_CONFIG(cursor)
void setMouseCursor(QWaylandInputDevice *device, const QCursor &cursor);
void restoreMouseCursor(QWaylandInputDevice *device);
@@ -181,7 +163,7 @@ public:
QWaylandWindow *transientParent() const;
QMutex *resizeMutex() { return &mResizeLock; }
- void doResize();
+ void doApplyConfigure();
void setCanResize(bool canResize);
bool setMouseGrabEnabled(bool grab) override;
@@ -206,7 +188,7 @@ public:
void requestUpdate() override;
public slots:
- void requestResize();
+ void applyConfigure();
protected:
void surface_enter(struct ::wl_output *output) override;
@@ -228,8 +210,7 @@ protected:
QWaitCondition mFrameSyncWait;
QMutex mResizeLock;
- QWaylandWindowConfigure mConfigure;
- bool mRequestResizeSent = false;
+ bool mWaitingToApplyConfigure = false;
bool mCanResize = true;
bool mResizeDirty = false;
bool mResizeAfterSwap;
@@ -241,9 +222,9 @@ protected:
QIcon mWindowIcon;
- Qt::WindowStates mState = Qt::WindowNoState;
Qt::WindowFlags mFlags;
QRegion mMask;
+ Qt::WindowStates mLastReportedWindowStates = Qt::WindowNoState;
QWaylandShmBackingStore *mBackingStore = nullptr;
@@ -251,7 +232,6 @@ private slots:
void handleScreenRemoved(QScreen *qScreen);
private:
- bool setWindowStateInternal(Qt::WindowStates flags);
void setGeometry_helper(const QRect &rect);
void initWindow();
void initializeWlSurface();
diff --git a/src/client/qwaylandwlshellsurface.cpp b/src/client/qwaylandwlshellsurface.cpp
index 098eddcbc..3601fd437 100644
--- a/src/client/qwaylandwlshellsurface.cpp
+++ b/src/client/qwaylandwlshellsurface.cpp
@@ -121,35 +121,70 @@ void QWaylandWlShellSurface::sendProperty(const QString &name, const QVariant &v
m_extendedWindow->updateGenericProperty(name, value);
}
-void QWaylandWlShellSurface::setMaximized()
+void QWaylandWlShellSurface::applyConfigure()
{
- m_maximized = true;
- m_size = m_window->window()->geometry().size();
- set_maximized(nullptr);
+ if ((m_pending.states & (Qt::WindowMaximized|Qt::WindowFullScreen))
+ && !(m_applied.states & (Qt::WindowMaximized|Qt::WindowFullScreen))) {
+ m_normalSize = m_window->window()->frameGeometry().size();
+ }
+
+ if (m_pending.states != m_applied.states)
+ m_window->handleWindowStatesChanged(m_pending.states);
+
+ if (!m_pending.size.isEmpty()) {
+ int x = 0;
+ int y = 0;
+ if (m_pending.edges & resize_left)
+ x = m_applied.size.width() - m_pending.size.width();
+ if (m_pending.edges & resize_top)
+ y = m_applied.size.height() - m_pending.size.height();
+ QPoint offset(x, y);
+ m_window->resizeFromApplyConfigure(m_pending.size, offset);
+ } else if (m_pending.size.isValid() && !m_normalSize.isEmpty()) {
+ m_window->resizeFromApplyConfigure(m_normalSize);
+ }
+
+ m_applied = m_pending;
}
-void QWaylandWlShellSurface::setFullscreen()
+bool QWaylandWlShellSurface::wantsDecorations() const
{
- m_fullscreen = true;
- m_size = m_window->window()->geometry().size();
- set_fullscreen(WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, 0, nullptr);
+ return !(m_pending.states & Qt::WindowFullScreen);
}
-void QWaylandWlShellSurface::setNormal()
+void QWaylandWlShellSurface::requestWindowStates(Qt::WindowStates states)
{
- if (m_fullscreen || m_maximized) {
- m_fullscreen = m_maximized = false;
- setTopLevel();
- QMargins m = m_window->frameMargins();
- m_window->configure(0, m_size.width() + m.left() + m.right(), m_size.height() + m.top() + m.bottom());
+ // On wl-shell the client is in charge of states, so diff from the pending state
+ Qt::WindowStates changedStates = m_pending.states ^ states;
+ Qt::WindowStates addedStates = changedStates & states;
+
+ if (addedStates & Qt::WindowMinimized)
+ qCWarning(lcQpaWayland) << "Minimizing is not supported on wl-shell. Consider using xdg-shell instead.";
+
+ if (addedStates & Qt::WindowMaximized) {
+ set_maximized(nullptr);
+ m_window->applyConfigureWhenPossible();
}
-}
-void QWaylandWlShellSurface::setMinimized()
-{
- qCWarning(lcQpaWayland) << "Minimization is not supported on wl-shell. Consider using xdg-shell instead.";
+ if (addedStates & Qt::WindowFullScreen) {
+ set_fullscreen(WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, 0, nullptr);
+ m_window->applyConfigureWhenPossible();
+ }
+
+ bool isNormal = ~states & (Qt::WindowMaximized | Qt::WindowFullScreen);
+ if (isNormal && (changedStates & (Qt::WindowMaximized | Qt::WindowFullScreen))) {
+ setTopLevel(); // set normal window
+ // There's usually no configure event after this, so just clear the rest of the pending
+ // configure here and queue the applyConfigure call
+ m_pending.size = {0, 0};
+ m_pending.edges = resize_none;
+ m_window->applyConfigureWhenPossible();
+ }
+
+ m_pending.states = states & ~Qt::WindowMinimized;
}
+
void QWaylandWlShellSurface::setTopLevel()
{
set_toplevel();
@@ -230,11 +265,13 @@ void QWaylandWlShellSurface::shell_surface_ping(uint32_t serial)
pong(serial);
}
-void QWaylandWlShellSurface::shell_surface_configure(uint32_t edges,
- int32_t width,
- int32_t height)
+void QWaylandWlShellSurface::shell_surface_configure(uint32_t edges, int32_t width, int32_t height)
{
- m_window->configure(edges, width, height);
+ m_pending.size = QSize(width, height);
+ m_pending.edges = static_cast<enum resize>(edges);
+ if (m_pending.edges && !m_pending.size.isEmpty())
+ m_normalSize = m_pending.size;
+ m_window->applyConfigureWhenPossible();
}
void QWaylandWlShellSurface::shell_surface_popup_done()
diff --git a/src/client/qwaylandwlshellsurface_p.h b/src/client/qwaylandwlshellsurface_p.h
index 497ec6043..ca81dd685 100644
--- a/src/client/qwaylandwlshellsurface_p.h
+++ b/src/client/qwaylandwlshellsurface_p.h
@@ -93,21 +93,26 @@ public:
void sendProperty(const QString &name, const QVariant &value) override;
void setType(Qt::WindowType type, QWaylandWindow *transientParent) override;
+ void applyConfigure() override;
+ bool wantsDecorations() const override;
-private:
- void setMaximized() override;
- void setFullscreen() override;
- void setNormal() override;
- void setMinimized() override;
+protected:
+ void requestWindowStates(Qt::WindowStates states) override;
+private:
void setTopLevel();
void updateTransientParent(QWindow *parent);
void setPopup(QWaylandWindow *parent, QWaylandInputDevice *device, uint serial);
QWaylandWindow *m_window = nullptr;
- bool m_maximized = false;
- bool m_fullscreen = false;
- QSize m_size;
+ struct {
+ Qt::WindowStates states = Qt::WindowNoState;
+ QSize size;
+ enum resize edges = resize_none;
+ } m_applied, m_pending;
+ QSize m_normalSize;
+ // There's really no need to have pending and applied state on wl-shell, but we do it just to
+ // keep the different shell implementations more similar.
QWaylandExtendedSurface *m_extendedWindow = nullptr;
void shell_surface_ping(uint32_t serial) override;
diff --git a/src/client/qwaylandxdgshellv6.cpp b/src/client/qwaylandxdgshellv6.cpp
index a166a3bc9..beabddbbb 100644
--- a/src/client/qwaylandxdgshellv6.cpp
+++ b/src/client/qwaylandxdgshellv6.cpp
@@ -46,8 +46,6 @@
#include "qwaylandscreen_p.h"
#include "qwaylandabstractdecoration_p.h"
-#include <QtCore/QDebug>
-
QT_BEGIN_NAMESPACE
namespace QtWaylandClient {
@@ -56,32 +54,65 @@ QWaylandXdgSurfaceV6::Toplevel::Toplevel(QWaylandXdgSurfaceV6 *xdgSurface)
: QtWayland::zxdg_toplevel_v6(xdgSurface->get_toplevel())
, m_xdgSurface(xdgSurface)
{
+ requestWindowStates(xdgSurface->window()->window()->windowStates());
}
QWaylandXdgSurfaceV6::Toplevel::~Toplevel()
{
+ if (m_applied.states & Qt::WindowActive) {
+ QWaylandWindow *window = m_xdgSurface->window();
+ window->display()->handleWindowDeactivated(window);
+ }
if (isInitialized())
destroy();
}
void QWaylandXdgSurfaceV6::Toplevel::applyConfigure()
{
- //TODO: resize, activate etc
- m_xdgSurface->m_window->configure(0, m_configureState.width, m_configureState.height);
+ if (!(m_applied.states & (Qt::WindowMaximized|Qt::WindowFullScreen)))
+ m_normalSize = m_xdgSurface->m_window->window()->frameGeometry().size();
+
+ if (m_pending.size.isEmpty() && !m_normalSize.isEmpty())
+ m_pending.size = m_normalSize;
+
+ if ((m_pending.states & Qt::WindowActive) && !(m_applied.states & Qt::WindowActive))
+ m_xdgSurface->m_window->display()->handleWindowActivated(m_xdgSurface->m_window);
+
+ if (!(m_pending.states & Qt::WindowActive) && (m_applied.states & Qt::WindowActive))
+ m_xdgSurface->m_window->display()->handleWindowDeactivated(m_xdgSurface->m_window);
+
+ // TODO: none of the other plugins send WindowActive either, but is it on purpose?
+ Qt::WindowStates statesWithoutActive = m_pending.states & ~Qt::WindowActive;
+
+ m_xdgSurface->m_window->handleWindowStatesChanged(statesWithoutActive);
+ m_xdgSurface->m_window->resizeFromApplyConfigure(m_pending.size);
+ m_applied = m_pending;
}
void QWaylandXdgSurfaceV6::Toplevel::zxdg_toplevel_v6_configure(int32_t width, int32_t height, wl_array *states)
{
- m_configureState.width = width;
- m_configureState.height = height;
+ m_pending.size = QSize(width, height);
- uint32_t *state = reinterpret_cast<uint32_t *>(states->data);
+ auto *xdgStates = static_cast<uint32_t *>(states->data);
size_t numStates = states->size / sizeof(uint32_t);
- m_configureState.states.reserve(numStates);
- m_configureState.states.clear();
- for (size_t i = 0; i < numStates; i++)
- m_configureState.states << state[i];
+ m_pending.states = Qt::WindowNoState;
+
+ for (size_t i = 0; i < numStates; i++) {
+ switch (xdgStates[i]) {
+ case ZXDG_TOPLEVEL_V6_STATE_ACTIVATED:
+ m_pending.states |= Qt::WindowActive;
+ break;
+ case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED:
+ m_pending.states |= Qt::WindowMaximized;
+ break;
+ case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN:
+ m_pending.states |= Qt::WindowFullScreen;
+ break;
+ default:
+ break;
+ }
+ }
}
void QWaylandXdgSurfaceV6::Toplevel::zxdg_toplevel_v6_close()
@@ -89,6 +120,32 @@ void QWaylandXdgSurfaceV6::Toplevel::zxdg_toplevel_v6_close()
m_xdgSurface->m_window->window()->close();
}
+void QWaylandXdgSurfaceV6::Toplevel::requestWindowStates(Qt::WindowStates states)
+{
+ // Re-send what's different from the applied state
+ Qt::WindowStates changedStates = m_applied.states ^ states;
+
+ if (changedStates & Qt::WindowMaximized) {
+ if (states & Qt::WindowMaximized)
+ set_maximized();
+ else
+ unset_maximized();
+ }
+
+ if (changedStates & Qt::WindowFullScreen) {
+ if (states & Qt::WindowFullScreen)
+ set_fullscreen(nullptr);
+ else
+ unset_fullscreen();
+ }
+
+ // Minimized state is not reported by the protocol, so always send it
+ if (states & Qt::WindowMinimized) {
+ set_minimized();
+ m_xdgSurface->window()->handleWindowStatesChanged(states & ~Qt::WindowMinimized);
+ }
+}
+
QWaylandXdgSurfaceV6::Popup::Popup(QWaylandXdgSurfaceV6 *xdgSurface, QWaylandXdgSurfaceV6 *parent,
QtWayland::zxdg_positioner_v6 *positioner)
: zxdg_popup_v6(xdgSurface->get_popup(parent->object(), positioner->object()))
@@ -104,7 +161,6 @@ QWaylandXdgSurfaceV6::Popup::~Popup()
void QWaylandXdgSurfaceV6::Popup::applyConfigure()
{
-
}
void QWaylandXdgSurfaceV6::Popup::zxdg_popup_v6_popup_done()
@@ -187,6 +243,34 @@ bool QWaylandXdgSurfaceV6::handleExpose(const QRegion &region)
return false;
}
+void QWaylandXdgSurfaceV6::applyConfigure()
+{
+ Q_ASSERT(m_pendingConfigureSerial != 0);
+
+ if (m_toplevel)
+ m_toplevel->applyConfigure();
+ if (m_popup)
+ m_popup->applyConfigure();
+
+ m_configured = true;
+ ack_configure(m_pendingConfigureSerial);
+
+ m_pendingConfigureSerial = 0;
+}
+
+bool QWaylandXdgSurfaceV6::wantsDecorations() const
+{
+ return m_toplevel && !(m_toplevel->m_pending.states & Qt::WindowFullScreen);
+}
+
+void QWaylandXdgSurfaceV6::requestWindowStates(Qt::WindowStates states)
+{
+ if (m_toplevel)
+ m_toplevel->requestWindowStates(states);
+ else
+ qCWarning(lcQpaWayland) << "Non-toplevel surfaces can't request window states";
+}
+
void QWaylandXdgSurfaceV6::setToplevel()
{
Q_ASSERT(!m_toplevel && !m_popup);
@@ -220,21 +304,14 @@ void QWaylandXdgSurfaceV6::setPopup(QWaylandWindow *parent, QWaylandInputDevice
void QWaylandXdgSurfaceV6::zxdg_surface_v6_configure(uint32_t serial)
{
- m_configured = true;
- if (m_toplevel)
- m_toplevel->applyConfigure();
- else if (m_popup)
- m_popup->applyConfigure();
-
+ m_window->applyConfigureWhenPossible();
+ m_pendingConfigureSerial = serial;
if (!m_exposeRegion.isEmpty()) {
QWindowSystemInterface::handleExposeEvent(m_window->window(), m_exposeRegion);
m_exposeRegion = QRegion();
}
- ack_configure(serial);
}
-
-
QWaylandXdgShellV6::QWaylandXdgShellV6(struct ::wl_registry *registry, uint32_t id, uint32_t availableVersion)
: QtWayland::zxdg_shell_v6(registry, id, qMin(availableVersion, 1u))
{
diff --git a/src/client/qwaylandxdgshellv6_p.h b/src/client/qwaylandxdgshellv6_p.h
index b72d3d18a..d2448bc66 100644
--- a/src/client/qwaylandxdgshellv6_p.h
+++ b/src/client/qwaylandxdgshellv6_p.h
@@ -73,6 +73,7 @@ class QWaylandXdgShellV6;
class Q_WAYLAND_CLIENT_EXPORT QWaylandXdgSurfaceV6 : public QWaylandShellSurface, public QtWayland::zxdg_surface_v6
{
+ Q_OBJECT
public:
QWaylandXdgSurfaceV6(QWaylandXdgShellV6 *shell, ::zxdg_surface_v6 *surface, QWaylandWindow *window);
~QWaylandXdgSurfaceV6() override;
@@ -85,8 +86,12 @@ public:
void setType(Qt::WindowType type, QWaylandWindow *transientParent) override;
bool handleExpose(const QRegion &) override;
+ bool handlesActiveState() const { return m_toplevel; }
+ void applyConfigure() override;
+ bool wantsDecorations() const override;
protected:
+ void requestWindowStates(Qt::WindowStates states) override;
void zxdg_surface_v6_configure(uint32_t serial) override;
private:
@@ -101,10 +106,12 @@ private:
void zxdg_toplevel_v6_configure(int32_t width, int32_t height, wl_array *states) override;
void zxdg_toplevel_v6_close() override;
+ void requestWindowStates(Qt::WindowStates states);
struct {
- int32_t width, height;
- QVarLengthArray<uint32_t> states;
- } m_configureState;
+ QSize size = {0, 0};
+ Qt::WindowStates states = Qt::WindowNoState;
+ } m_pending, m_applied;
+ QSize m_normalSize;
QWaylandXdgSurfaceV6 *m_xdgSurface = nullptr;
};
@@ -129,6 +136,7 @@ private:
Popup *m_popup = nullptr;
bool m_configured = false;
QRegion m_exposeRegion;
+ uint m_pendingConfigureSerial = 0;
};
class Q_WAYLAND_CLIENT_EXPORT QWaylandXdgShellV6 : public QtWayland::zxdg_shell_v6
diff --git a/src/client/qwaylandxdgshellv6integration.cpp b/src/client/qwaylandxdgshellv6integration.cpp
index cb82354b6..d3327ff39 100644
--- a/src/client/qwaylandxdgshellv6integration.cpp
+++ b/src/client/qwaylandxdgshellv6integration.cpp
@@ -78,6 +78,20 @@ QWaylandShellSurface *QWaylandXdgShellV6Integration::createShellSurface(QWayland
return m_xdgShell->getXdgSurface(window);
}
+void QWaylandXdgShellV6Integration::handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus)
+{
+ if (newFocus) {
+ auto *xdgSurface = qobject_cast<QWaylandXdgSurfaceV6 *>(newFocus->shellSurface());
+ if (xdgSurface && xdgSurface->handlesActiveState())
+ m_display->handleWindowActivated(newFocus);
+ }
+ if (oldFocus && qobject_cast<QWaylandXdgPopup *>(oldFocus->shellSurface())) {
+ auto *xdgSurface = qobject_cast<QWaylandXdgSurfaceV6 *>(oldFocus->shellSurface());
+ if (xdgSurface && xdgSurface->handlesActiveState())
+ m_display->handleWindowDeactivated(oldFocus);
+ }
+}
+
}
QT_END_NAMESPACE
diff --git a/src/client/qwaylandxdgshellv6integration_p.h b/src/client/qwaylandxdgshellv6integration_p.h
index bdfd19723..66323a775 100644
--- a/src/client/qwaylandxdgshellv6integration_p.h
+++ b/src/client/qwaylandxdgshellv6integration_p.h
@@ -67,6 +67,7 @@ public:
static QWaylandXdgShellV6Integration *create(QWaylandDisplay* display);
bool initialize(QWaylandDisplay *display) override;
QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override;
+ void handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) override;
private:
QWaylandXdgShellV6Integration(QWaylandDisplay *display);
diff --git a/src/client/qwaylandxdgsurface.cpp b/src/client/qwaylandxdgsurface.cpp
index 4dfc5e6da..5b5aacbf4 100644
--- a/src/client/qwaylandxdgsurface.cpp
+++ b/src/client/qwaylandxdgsurface.cpp
@@ -64,7 +64,7 @@ QWaylandXdgSurface::QWaylandXdgSurface(QWaylandXdgShell *shell, QWaylandWindow *
QWaylandXdgSurface::~QWaylandXdgSurface()
{
- if (m_active)
+ if (m_acked.states & Qt::WindowActive)
window()->display()->handleWindowDeactivated(m_window);
xdg_surface_destroy(object());
@@ -92,38 +92,6 @@ bool QWaylandXdgSurface::move(QWaylandInputDevice *inputDevice)
return true;
}
-void QWaylandXdgSurface::setMaximized()
-{
- if (!m_maximized)
- set_maximized();
-}
-
-void QWaylandXdgSurface::setFullscreen()
-{
- if (!m_fullscreen)
- set_fullscreen(nullptr);
-}
-
-void QWaylandXdgSurface::setNormal()
-{
- if (m_fullscreen || m_maximized || m_minimized) {
- if (m_maximized) {
- unset_maximized();
- }
- if (m_fullscreen) {
- unset_fullscreen();
- }
-
- m_fullscreen = m_maximized = m_minimized = false;
- }
-}
-
-void QWaylandXdgSurface::setMinimized()
-{
- m_minimized = true;
- set_minimized();
-}
-
void QWaylandXdgSurface::updateTransientParent(QWaylandWindow *parent)
{
if (!parent)
@@ -180,71 +148,88 @@ void QWaylandXdgSurface::setType(Qt::WindowType type, QWaylandWindow *transientP
updateTransientParent(transientParent);
}
+void QWaylandXdgSurface::applyConfigure()
+{
+ if (m_pending.isResizing)
+ m_normalSize = m_pending.size;
+ else if (!(m_acked.states & (Qt::WindowMaximized|Qt::WindowFullScreen)))
+ m_normalSize = m_window->window()->frameGeometry().size();
+
+ if ((m_pending.states & Qt::WindowActive) && !(m_acked.states & Qt::WindowActive))
+ m_window->display()->handleWindowActivated(m_window);
+
+ if (!(m_pending.states & Qt::WindowActive) && (m_acked.states & Qt::WindowActive))
+ m_window->display()->handleWindowDeactivated(m_window);
+
+ // TODO: none of the other plugins send WindowActive either, but is it on purpose?
+ Qt::WindowStates statesWithoutActive = m_pending.states & ~Qt::WindowActive;
+
+ m_window->handleWindowStatesChanged(statesWithoutActive);
+ if (!m_pending.size.isEmpty())
+ m_window->resizeFromApplyConfigure(m_pending.size);
+ else if (!m_normalSize.isEmpty())
+ m_window->resizeFromApplyConfigure(m_normalSize);
+ ack_configure(m_pending.serial);
+ m_acked = m_pending;
+}
+
+void QWaylandXdgSurface::requestWindowStates(Qt::WindowStates states)
+{
+ Qt::WindowStates changedStates = m_acked.states ^ states;
+
+ if (changedStates & Qt::WindowMaximized) {
+ if (states & Qt::WindowMaximized)
+ set_maximized();
+ else
+ unset_maximized();
+ }
+
+ if (changedStates & Qt::WindowFullScreen) {
+ if (states & Qt::WindowFullScreen)
+ set_fullscreen(nullptr);
+ else
+ unset_fullscreen();
+ }
+
+ // Minimized state is not reported by the protocol, so always send it
+ if (states & Qt::WindowMinimized) {
+ set_minimized();
+ window()->handleWindowStatesChanged(states & ~Qt::WindowMinimized);
+ }
+}
+
+bool QWaylandXdgSurface::wantsDecorations() const
+{
+ return !(m_pending.states & Qt::WindowFullScreen);
+}
+
void QWaylandXdgSurface::xdg_surface_configure(int32_t width, int32_t height, struct wl_array *states,uint32_t serial)
{
- uint32_t *state = reinterpret_cast<uint32_t*>(states->data);
+ uint32_t *xdgStates = reinterpret_cast<uint32_t*>(states->data);
size_t numStates = states->size / sizeof(uint32_t);
- bool aboutToMaximize = false;
- bool aboutToFullScreen = false;
- bool aboutToActivate = false;
-
+ m_pending.serial = serial;
+ m_pending.size = QSize(width, height);
+ m_pending.isResizing = false;
+ m_pending.states = Qt::WindowNoState;
for (size_t i = 0; i < numStates; i++) {
- switch (state[i]) {
+ switch (xdgStates[i]) {
case XDG_SURFACE_STATE_MAXIMIZED:
- aboutToMaximize = ((width > 0) && (height > 0));
+ m_pending.states |= Qt::WindowMaximized;
break;
case XDG_SURFACE_STATE_FULLSCREEN:
- aboutToFullScreen = true;
+ m_pending.states |= Qt::WindowFullScreen;
break;
case XDG_SURFACE_STATE_RESIZING:
- m_normalSize = QSize(width, height);
+ m_pending.isResizing = true;
break;
case XDG_SURFACE_STATE_ACTIVATED:
- aboutToActivate = true;
+ m_pending.states |= Qt::WindowActive;
break;
default:
break;
}
}
-
- if (!m_active && aboutToActivate) {
- m_active = true;
- window()->display()->handleWindowActivated(m_window);
- } else if (m_active && !aboutToActivate) {
- m_active = false;
- window()->display()->handleWindowDeactivated(m_window);
- }
-
- if (!m_fullscreen && aboutToFullScreen) {
- if (!m_maximized)
- m_normalSize = m_window->window()->frameGeometry().size();
- m_fullscreen = true;
- m_window->window()->showFullScreen();
- } else if (m_fullscreen && !aboutToFullScreen) {
- m_fullscreen = false;
- if ( m_maximized ) {
- m_window->window()->showMaximized();
- } else {
- m_window->window()->showNormal();
- }
- } else if (!m_maximized && aboutToMaximize) {
- if (!m_fullscreen)
- m_normalSize = m_window->window()->frameGeometry().size();
- m_maximized = true;
- m_window->window()->showMaximized();
- } else if (m_maximized && !aboutToMaximize) {
- m_maximized = false;
- m_window->window()->showNormal();
- }
-
- if (width <= 0 || height <= 0) {
- if (!m_normalSize.isEmpty())
- m_window->configure(0, m_normalSize.width(), m_normalSize.height());
- } else {
- m_window->configure(0, width, height);
- }
-
- ack_configure(serial);
+ m_window->applyConfigureWhenPossible();
}
void QWaylandXdgSurface::xdg_surface_close()
diff --git a/src/client/qwaylandxdgsurface_p.h b/src/client/qwaylandxdgsurface_p.h
index b8dd93f47..059e79d87 100644
--- a/src/client/qwaylandxdgsurface_p.h
+++ b/src/client/qwaylandxdgsurface_p.h
@@ -96,26 +96,23 @@ public:
void setWindowFlags(Qt::WindowFlags flags) override;
void sendProperty(const QString &name, const QVariant &value) override;
- bool isFullscreen() const { return m_fullscreen; }
- bool isMaximized() const { return m_maximized; }
-
void setType(Qt::WindowType type, QWaylandWindow *transientParent) override;
+ void applyConfigure() override;
+ void requestWindowStates(Qt::WindowStates states) override;
+ bool wantsDecorations() const override;
private:
- void setMaximized() override;
- void setFullscreen() override;
- void setNormal() override;
- void setMinimized() override;
-
void updateTransientParent(QWaylandWindow *parent);
private:
QWaylandWindow *m_window = nullptr;
QWaylandXdgShell* m_shell = nullptr;
- bool m_maximized = false;
- bool m_minimized = false;
- bool m_fullscreen = false;
- bool m_active = false;
+ struct {
+ Qt::WindowStates states = Qt::WindowNoState;
+ bool isResizing = false;
+ QSize size = {0, 0};
+ uint serial = 0;
+ } m_acked, m_pending;
QSize m_normalSize;
QMargins m_margins;
QWaylandExtendedSurface *m_extendedWindow = nullptr;
diff --git a/src/plugins/decorations/bradient/main.cpp b/src/plugins/decorations/bradient/main.cpp
index 9a7e91e6f..d3627d2f7 100644
--- a/src/plugins/decorations/bradient/main.cpp
+++ b/src/plugins/decorations/bradient/main.cpp
@@ -270,8 +270,7 @@ void QWaylandBradientDecoration::paint(QPaintDevice *device)
p.drawPixmap(closeButtonRect(), closePixmap, closePixmap.rect());
// Maximize button
- QPixmap maximizePixmap(waylandWindow()->isMaximized()
- ? qt_normalizeup_xpm : qt_maximize_xpm);
+ QPixmap maximizePixmap((window()->windowStates() & Qt::WindowMaximized) ? qt_normalizeup_xpm : qt_maximize_xpm);
p.drawPixmap(maximizeButtonRect(), maximizePixmap, maximizePixmap.rect());
// Minimize button
@@ -356,7 +355,7 @@ bool QWaylandBradientDecoration::handleMouse(QWaylandInputDevice *inputDevice, c
QWindowSystemInterface::handleCloseEvent(window());
} else if (maximizeButtonRect().contains(local)) {
if (clickButton(b, Maximize))
- window()->setWindowState(waylandWindow()->isMaximized() ? Qt::WindowNoState : Qt::WindowMaximized);
+ window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized);
} else if (minimizeButtonRect().contains(local)) {
if (clickButton(b, Minimize))
window()->setWindowState(Qt::WindowMinimized);
@@ -390,7 +389,7 @@ bool QWaylandBradientDecoration::handleTouch(QWaylandInputDevice *inputDevice, c
if (closeButtonRect().contains(local))
QWindowSystemInterface::handleCloseEvent(window());
else if (maximizeButtonRect().contains(local))
- window()->setWindowState(waylandWindow()->isMaximized() ? Qt::WindowNoState : Qt::WindowMaximized);
+ window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized);
else if (minimizeButtonRect().contains(local))
window()->setWindowState(Qt::WindowMinimized);
else if (local.y() <= margins().top())
diff --git a/src/plugins/shellintegration/ivi-shell/qwaylandivisurface.cpp b/src/plugins/shellintegration/ivi-shell/qwaylandivisurface.cpp
index ec529b124..871709cdf 100644
--- a/src/plugins/shellintegration/ivi-shell/qwaylandivisurface.cpp
+++ b/src/plugins/shellintegration/ivi-shell/qwaylandivisurface.cpp
@@ -82,6 +82,11 @@ void QWaylandIviSurface::setType(Qt::WindowType type, QWaylandWindow *transientP
Q_UNUSED(transientParent)
}
+void QWaylandIviSurface::applyConfigure()
+{
+ m_window->resizeFromApplyConfigure(m_pendingSize);
+}
+
void QWaylandIviSurface::createExtendedSurface(QWaylandWindow *window)
{
if (window->display()->windowExtension())
@@ -90,7 +95,8 @@ void QWaylandIviSurface::createExtendedSurface(QWaylandWindow *window)
void QWaylandIviSurface::ivi_surface_configure(int32_t width, int32_t height)
{
- this->m_window->configure(0, width, height);
+ m_pendingSize = {width, height};
+ m_window->applyConfigureWhenPossible();
}
void QWaylandIviSurface::ivi_controller_surface_visibility(int32_t visibility)
diff --git a/src/plugins/shellintegration/ivi-shell/qwaylandivisurface_p.h b/src/plugins/shellintegration/ivi-shell/qwaylandivisurface_p.h
index ff943060b..6ec28e758 100644
--- a/src/plugins/shellintegration/ivi-shell/qwaylandivisurface_p.h
+++ b/src/plugins/shellintegration/ivi-shell/qwaylandivisurface_p.h
@@ -63,6 +63,7 @@ public:
~QWaylandIviSurface() override;
void setType(Qt::WindowType type, QWaylandWindow *transientParent) override;
+ void applyConfigure() override;
private:
void createExtendedSurface(QWaylandWindow *window);
@@ -71,6 +72,7 @@ private:
QWaylandWindow *m_window = nullptr;
QWaylandExtendedSurface *m_extendedWindow = nullptr;
+ QSize m_pendingSize = {0, 0};
};
}
diff --git a/tests/auto/client/client/tst_client.cpp b/tests/auto/client/client/tst_client.cpp
index 05b8bac1a..7a7c7fa85 100644
--- a/tests/auto/client/client/tst_client.cpp
+++ b/tests/auto/client/client/tst_client.cpp
@@ -172,6 +172,7 @@ private slots:
void windowScreens();
void removePrimaryScreen();
void createDestroyWindow();
+ void activeWindowFollowsKeyboardFocus();
void events();
void backingStore();
void touchDrag();
@@ -313,7 +314,7 @@ void tst_WaylandClient::createDestroyWindow()
QTRY_VERIFY(!compositor->surface());
}
-void tst_WaylandClient::events()
+void tst_WaylandClient::activeWindowFollowsKeyboardFocus()
{
TestWindow window;
window.show();
@@ -324,6 +325,9 @@ void tst_WaylandClient::events()
QTRY_VERIFY(window.isExposed());
+ if (compositor->xdgToplevelV6())
+ QSKIP("On xdg-shell v6 focus is handled by configure events");
+
QCOMPARE(window.focusInEventCount, 0);
compositor->setKeyboardFocus(surface);
QTRY_COMPARE(window.focusInEventCount, 1);
@@ -333,9 +337,21 @@ void tst_WaylandClient::events()
compositor->setKeyboardFocus(QSharedPointer<MockSurface>(nullptr));
QTRY_COMPARE(window.focusOutEventCount, 1);
QTRY_COMPARE(QGuiApplication::focusWindow(), static_cast<QWindow *>(nullptr));
+}
+
+void tst_WaylandClient::events()
+{
+ TestWindow window;
+ window.show();
+
+ QSharedPointer<MockSurface> surface;
+ QTRY_VERIFY(surface = compositor->surface());
+ compositor->sendShellSurfaceConfigure(surface);
+
+ QTRY_VERIFY(window.isExposed());
compositor->setKeyboardFocus(surface);
- QTRY_COMPARE(window.focusInEventCount, 2);
+ QTRY_COMPARE(window.focusInEventCount, 1);
QTRY_COMPARE(QGuiApplication::focusWindow(), &window);
uint keyCode = 80; // arbitrarily chosen
diff --git a/tests/auto/client/shared/mockcompositor.cpp b/tests/auto/client/shared/mockcompositor.cpp
index 9ef08ad95..11b40d9fc 100644
--- a/tests/auto/client/shared/mockcompositor.cpp
+++ b/tests/auto/client/shared/mockcompositor.cpp
@@ -236,11 +236,14 @@ void MockCompositor::sendIviSurfaceConfigure(const QSharedPointer<MockIviSurface
processCommand(command);
}
-void MockCompositor::sendXdgToplevelV6Configure(const QSharedPointer<MockXdgToplevelV6> toplevel, const QSize &size)
+void MockCompositor::sendXdgToplevelV6Configure(const QSharedPointer<MockXdgToplevelV6> toplevel, const QSize &size, const QVector<uint> &states)
{
Command command = makeCommand(Impl::Compositor::sendXdgToplevelV6Configure, m_compositor);
command.parameters << QVariant::fromValue(toplevel);
command.parameters << QVariant::fromValue(size);
+ auto statesBytes = QByteArray::fromRawData(reinterpret_cast<const char *>(states.data()),
+ states.size() * static_cast<int>(sizeof(uint)));
+ command.parameters << statesBytes;
processCommand(command);
}
diff --git a/tests/auto/client/shared/mockcompositor.h b/tests/auto/client/shared/mockcompositor.h
index 34c20943a..d3568c165 100644
--- a/tests/auto/client/shared/mockcompositor.h
+++ b/tests/auto/client/shared/mockcompositor.h
@@ -175,12 +175,21 @@ private:
Q_DECLARE_METATYPE(QSharedPointer<MockIviSurface>)
-class MockXdgToplevelV6
+class MockXdgToplevelV6 : public QObject
{
+ Q_OBJECT
public:
Impl::XdgToplevelV6 *handle() const { return m_toplevel; }
void sendConfigure(const QSharedPointer<MockXdgToplevelV6> toplevel);
+
+signals:
+ uint setMinimizedRequested();
+ uint setMaximizedRequested();
+ uint unsetMaximizedRequested();
+ uint setFullscreenRequested();
+ uint unsetFullscreenRequested();
+
private:
MockXdgToplevelV6(Impl::XdgToplevelV6 *toplevel) : m_toplevel(toplevel) {}
friend class Impl::Compositor;
@@ -234,7 +243,8 @@ public:
void sendSurfaceLeave(const QSharedPointer<MockSurface> &surface, QSharedPointer<MockOutput> &output);
void sendShellSurfaceConfigure(const QSharedPointer<MockSurface> surface, const QSize &size = QSize(0, 0));
void sendIviSurfaceConfigure(const QSharedPointer<MockIviSurface> iviSurface, const QSize &size);
- void sendXdgToplevelV6Configure(const QSharedPointer<MockXdgToplevelV6> toplevel, const QSize &size = QSize(0, 0));
+ void sendXdgToplevelV6Configure(const QSharedPointer<MockXdgToplevelV6> toplevel, const QSize &size = QSize(0, 0),
+ const QVector<uint> &states = { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED });
void waitForStartDrag();
QSharedPointer<MockSurface> surface();
diff --git a/tests/auto/client/shared/mockxdgshellv6.cpp b/tests/auto/client/shared/mockxdgshellv6.cpp
index 39e03296d..6f6f0b905 100644
--- a/tests/auto/client/shared/mockxdgshellv6.cpp
+++ b/tests/auto/client/shared/mockxdgshellv6.cpp
@@ -39,8 +39,8 @@ void Compositor::sendXdgToplevelV6Configure(void *data, const QList<QVariant> &p
Q_ASSERT(toplevel && toplevel->resource());
QSize size = parameters.at(1).toSize();
Q_ASSERT(size.isValid());
- QByteArray states;
- toplevel->send_configure(size.width(), size.height(), states);
+ auto statesBytes = parameters.at(2).toByteArray();
+ toplevel->send_configure(size.width(), size.height(), statesBytes);
toplevel->xdgSurface()->send_configure(compositor->nextSerial());
}
@@ -87,6 +87,37 @@ void XdgToplevelV6::zxdg_toplevel_v6_destroy(QtWaylandServer::zxdg_toplevel_v6::
wl_resource_destroy(resource->handle);
}
+void XdgToplevelV6::zxdg_toplevel_v6_set_minimized(QtWaylandServer::zxdg_toplevel_v6::Resource *resource)
+{
+ Q_UNUSED(resource);
+ emit m_mockToplevel->setMinimizedRequested();
+}
+
+void XdgToplevelV6::zxdg_toplevel_v6_set_maximized(QtWaylandServer::zxdg_toplevel_v6::Resource *resource)
+{
+ Q_UNUSED(resource);
+ emit m_mockToplevel->setMaximizedRequested();
+}
+
+void XdgToplevelV6::zxdg_toplevel_v6_unset_maximized(QtWaylandServer::zxdg_toplevel_v6::Resource *resource)
+{
+ Q_UNUSED(resource);
+ emit m_mockToplevel->unsetMaximizedRequested();
+}
+
+void XdgToplevelV6::zxdg_toplevel_v6_set_fullscreen(QtWaylandServer::zxdg_toplevel_v6::Resource *resource, wl_resource *output)
+{
+ Q_UNUSED(resource);
+ Q_UNUSED(output);
+ emit m_mockToplevel->setFullscreenRequested();
+}
+
+void XdgToplevelV6::zxdg_toplevel_v6_unset_fullscreen(QtWaylandServer::zxdg_toplevel_v6::Resource *resource)
+{
+ Q_UNUSED(resource);
+ emit m_mockToplevel->unsetFullscreenRequested();
+}
+
void Impl::XdgShellV6::zxdg_shell_v6_get_xdg_surface(QtWaylandServer::zxdg_shell_v6::Resource *resource, uint32_t id, wl_resource *surface)
{
new XdgSurfaceV6(this, Surface::fromResource(surface), resource->client(), id);
diff --git a/tests/auto/client/shared/mockxdgshellv6.h b/tests/auto/client/shared/mockxdgshellv6.h
index 92b808ba8..faadb785a 100644
--- a/tests/auto/client/shared/mockxdgshellv6.h
+++ b/tests/auto/client/shared/mockxdgshellv6.h
@@ -74,6 +74,11 @@ public:
protected:
void zxdg_toplevel_v6_destroy_resource(Resource *) override { delete this; }
void zxdg_toplevel_v6_destroy(Resource *resource) override;
+ void zxdg_toplevel_v6_set_minimized(Resource *resource) override;
+ void zxdg_toplevel_v6_set_maximized(Resource *resource) override;
+ void zxdg_toplevel_v6_unset_maximized(Resource *resource) override;
+ void zxdg_toplevel_v6_set_fullscreen(Resource *resource, struct ::wl_resource *output) override;
+ void zxdg_toplevel_v6_unset_fullscreen(Resource *resource) override;
private:
XdgSurfaceV6 *m_xdgSurface = nullptr;
diff --git a/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp b/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp
index 364cd1099..0f72f58a9 100644
--- a/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp
+++ b/tests/auto/client/xdgshellv6/tst_xdgshellv6.cpp
@@ -42,6 +42,7 @@ static const QSize screenSize(1600, 1200);
class TestWindow : public QWindow
{
+ Q_OBJECT
public:
TestWindow()
{
@@ -49,6 +50,16 @@ public:
setGeometry(0, 0, 32, 32);
create();
}
+
+ bool event(QEvent *event) override
+ {
+ if (event->type() == QEvent::WindowStateChange)
+ emit windowStateChangeEventReceived(static_cast<QWindowStateChangeEvent *>(event)->oldState());
+ return QWindow::event(event);
+ }
+
+signals:
+ void windowStateChangeEventReceived(uint oldState);
};
class tst_WaylandClientXdgShellV6 : public QObject
@@ -58,6 +69,7 @@ public:
tst_WaylandClientXdgShellV6(MockCompositor *c)
: m_compositor(c)
{
+ qRegisterMetaType<Qt::WindowState>();
QSocketNotifier *notifier = new QSocketNotifier(m_compositor->waylandFileDescriptor(), QSocketNotifier::Read, this);
connect(notifier, &QSocketNotifier::activated, this, &tst_WaylandClientXdgShellV6::processWaylandEvents);
// connect to the event dispatcher to make sure to flush out the outgoing message queue
@@ -82,6 +94,11 @@ public slots:
private slots:
void createDestroyWindow();
void configure();
+ void showMinimized();
+ void setMinimized();
+ void unsetMaximized();
+ void focusWindowFollowsConfigure();
+ void windowStateChangedEvents();
private:
MockCompositor *m_compositor = nullptr;
@@ -118,10 +135,182 @@ void tst_WaylandClientXdgShellV6::configure()
QSharedPointer<MockXdgToplevelV6> toplevel;
QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6());
+
const QSize newSize(123, 456);
m_compositor->sendXdgToplevelV6Configure(toplevel, newSize);
QTRY_VERIFY(window.isExposed());
+ QTRY_COMPARE(window.visibility(), QWindow::Windowed);
+ QTRY_COMPARE(window.windowStates(), Qt::WindowNoState);
QTRY_COMPARE(window.frameGeometry(), QRect(QPoint(), newSize));
+
+ m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED, ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED });
+ QTRY_COMPARE(window.visibility(), QWindow::Maximized);
+ QTRY_COMPARE(window.windowStates(), Qt::WindowMaximized);
+ QTRY_COMPARE(window.frameGeometry(), QRect(QPoint(), screenSize));
+
+ m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED, ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN });
+ QTRY_COMPARE(window.visibility(), QWindow::FullScreen);
+ QTRY_COMPARE(window.windowStates(), Qt::WindowFullScreen);
+ QTRY_COMPARE(window.frameGeometry(), QRect(QPoint(), screenSize));
+
+ //The window should remember it's original size
+ m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED });
+ QTRY_COMPARE(window.visibility(), QWindow::Windowed);
+ QTRY_COMPARE(window.windowStates(), Qt::WindowNoState);
+ QTRY_COMPARE(window.frameGeometry(), QRect(QPoint(), newSize));
+}
+
+void tst_WaylandClientXdgShellV6::showMinimized()
+{
+ // On xdg-shell v6 there's really no way for the compositor to tell the window if it's minimized
+ // There are wl_surface.enter events and so on, but there's really no way to differentiate
+ // between a window preview and an unminimized window.
+ TestWindow window;
+ window.showMinimized();
+ QCOMPARE(window.windowStates(), Qt::WindowMinimized); // should return minimized until
+ QTRY_COMPARE(window.windowStates(), Qt::WindowNoState); // rejected by handleWindowStateChanged
+}
+
+void tst_WaylandClientXdgShellV6::setMinimized()
+{
+ TestWindow window;
+ window.show();
+
+ QSharedPointer<MockXdgToplevelV6> toplevel;
+ QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6());
+
+ m_compositor->sendXdgToplevelV6Configure(toplevel);
+ QTRY_COMPARE(window.visibility(), QWindow::Windowed);
+ QTRY_COMPARE(window.windowStates(), Qt::WindowNoState);
+
+ QSignalSpy setMinimizedSpy(toplevel.data(), SIGNAL(setMinimizedRequested()));
+ QSignalSpy windowStateChangeSpy(&window, SIGNAL(windowStateChangeEventReceived(uint)));
+
+ window.setVisibility(QWindow::Minimized);
+ QCOMPARE(window.visibility(), QWindow::Minimized);
+ QCOMPARE(window.windowStates(), Qt::WindowMinimized);
+ QTRY_COMPARE(setMinimizedSpy.count(), 1);
+ {
+ QTRY_VERIFY(windowStateChangeSpy.count() > 0);
+ Qt::WindowStates oldStates(windowStateChangeSpy.takeFirst().at(0).toUInt());
+ QCOMPARE(oldStates, Qt::WindowNoState);
+ }
+
+ // In the meantime the compositor may minimize, do nothing or reshow the window without
+ // telling us.
+
+ QTRY_COMPARE(window.visibility(), QWindow::Windowed); // verify that we don't know anything
+ QTRY_COMPARE(window.windowStates(), Qt::WindowNoState);
+ {
+ QTRY_COMPARE(windowStateChangeSpy.count(), 1);
+ Qt::WindowStates oldStates(windowStateChangeSpy.takeFirst().at(0).toUInt());
+ QCOMPARE(oldStates, Qt::WindowNoState); // because the window never was minimized
+ }
+
+ // Setting visibility again should send another set_minimized request
+ window.setVisibility(QWindow::Minimized);
+ QTRY_COMPARE(setMinimizedSpy.count(), 2);
+}
+
+void tst_WaylandClientXdgShellV6::unsetMaximized()
+{
+ TestWindow window;
+ window.show();
+
+ QSharedPointer<MockXdgToplevelV6> toplevel;
+ QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6());
+
+ QSignalSpy unsetMaximizedSpy(toplevel.data(), SIGNAL(unsetMaximizedRequested()));
+
+ QSignalSpy windowStateChangedSpy(&window, SIGNAL(windowStateChanged(Qt::WindowState)));
+
+ m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED });
+
+ QTRY_COMPARE(windowStateChangedSpy.count(), 1);
+ QCOMPARE(windowStateChangedSpy.takeFirst().at(0).toUInt(), Qt::WindowMaximized);
+
+ window.setWindowStates(Qt::WindowNoState);
+
+ QTRY_COMPARE(unsetMaximizedSpy.count(), 1);
+ QTRY_COMPARE(windowStateChangedSpy.count(), 1);
+ QCOMPARE(windowStateChangedSpy.takeFirst().at(0).toUInt(), Qt::WindowNoState);
+
+ m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), {});
+
+ QTRY_COMPARE(windowStateChangedSpy.count(), 1);
+ QCOMPARE(windowStateChangedSpy.takeFirst().at(0).toUInt(), Qt::WindowNoState);
+}
+
+void tst_WaylandClientXdgShellV6::focusWindowFollowsConfigure()
+{
+ TestWindow window;
+ window.show();
+
+ QSharedPointer<MockXdgToplevelV6> toplevel;
+ QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6());
+ QTRY_VERIFY(!window.isActive());
+
+ QSignalSpy windowStateChangeSpy(&window, SIGNAL(windowStateChangeEventReceived(uint)));
+
+ m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), { ZXDG_TOPLEVEL_V6_STATE_ACTIVATED });
+ QTRY_VERIFY(window.isActive());
+
+ m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), {});
+ QTRY_VERIFY(!window.isActive());
+}
+
+void tst_WaylandClientXdgShellV6::windowStateChangedEvents()
+{
+ TestWindow window;
+ window.show();
+
+ QSharedPointer<MockXdgToplevelV6> toplevel;
+ QTRY_VERIFY(toplevel = m_compositor->xdgToplevelV6());
+
+ QSignalSpy eventSpy(&window, SIGNAL(windowStateChangeEventReceived(uint)));
+ QSignalSpy signalSpy(&window, SIGNAL(windowStateChanged(Qt::WindowState)));
+
+ m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED });
+
+ QTRY_COMPARE(window.windowStates(), Qt::WindowMaximized);
+ QTRY_COMPARE(window.windowState(), Qt::WindowMaximized);
+ {
+ QTRY_COMPARE(eventSpy.count(), 1);
+ Qt::WindowStates oldStates(eventSpy.takeFirst().at(0).toUInt());
+ QCOMPARE(oldStates, Qt::WindowNoState);
+
+ QTRY_COMPARE(signalSpy.count(), 1);
+ uint newState = signalSpy.takeFirst().at(0).toUInt();
+ QCOMPARE(newState, Qt::WindowMaximized);
+ }
+
+ m_compositor->sendXdgToplevelV6Configure(toplevel, screenSize, { ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN });
+
+ QTRY_COMPARE(window.windowStates(), Qt::WindowFullScreen);
+ QTRY_COMPARE(window.windowState(), Qt::WindowFullScreen);
+ {
+ QTRY_COMPARE(eventSpy.count(), 1);
+ Qt::WindowStates oldStates(eventSpy.takeFirst().at(0).toUInt());
+ QCOMPARE(oldStates, Qt::WindowMaximized);
+
+ QTRY_COMPARE(signalSpy.count(), 1);
+ uint newState = signalSpy.takeFirst().at(0).toUInt();
+ QCOMPARE(newState, Qt::WindowFullScreen);
+ }
+
+ m_compositor->sendXdgToplevelV6Configure(toplevel, QSize(0, 0), {});
+
+ QTRY_COMPARE(window.windowStates(), Qt::WindowNoState);
+ QTRY_COMPARE(window.windowState(), Qt::WindowNoState);
+ {
+ QTRY_COMPARE(eventSpy.count(), 1);
+ Qt::WindowStates oldStates(eventSpy.takeFirst().at(0).toUInt());
+ QCOMPARE(oldStates, Qt::WindowFullScreen);
+
+ QTRY_COMPARE(signalSpy.count(), 1);
+ uint newState = signalSpy.takeFirst().at(0).toUInt();
+ QCOMPARE(newState, Qt::WindowNoState);
+ }
}
int main(int argc, char **argv)