diff options
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 ®ion) 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) |