From 41f919c3471b9169e309679b3ce41c5c5bb1dff4 Mon Sep 17 00:00:00 2001 From: Gatis Paeglis Date: Wed, 7 Nov 2018 14:49:37 +0100 Subject: xcb: implement missing bits from ICCCM 4.1.4 WM_STATE handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ICCCM 4.1.4 says: Clients that want to re-use a client window (e.g. by mapping it again) after withdrawing it must wait for the withdrawal to be complete before proceeding. The preferred method for doing this is for clients to wait for a windown manager to update or remove the WM_STATE property. This patch implements the required logic. Qt 4 had something similar. Without this patch we are calling various setter functions on a native window while it is in an undefined state. Fixes: QTBUG-69515 Done-with: Liang Qi Change-Id: I377a66ad3d5e43f14465d0ea670b2f43f96ed7d3 Reviewed-by: Tor Arne Vestbø Reviewed-by: Allan Sandfeld Jensen (cherry picked from commit e946e6895a8517a887ac246905e0769edd766fcc) Reviewed-by: Qt Cherry-pick Bot --- src/plugins/platforms/xcb/qxcbwindow.cpp | 115 +++++++++++++++++++++++++++---- src/plugins/platforms/xcb/qxcbwindow.h | 16 +++++ 2 files changed, 119 insertions(+), 12 deletions(-) diff --git a/src/plugins/platforms/xcb/qxcbwindow.cpp b/src/plugins/platforms/xcb/qxcbwindow.cpp index 2ee5dc300c..61b2efebf5 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.cpp +++ b/src/plugins/platforms/xcb/qxcbwindow.cpp @@ -548,6 +548,11 @@ void QXcbWindow::setGeometry(const QRect &rect) { QPlatformWindow::setGeometry(rect); + if (shouldDeferTask(Task::SetGeometry)) { + m_deferredGeometry = rect; + return; + } + propagateSizeHints(); QXcbScreen *currentScreen = xcbScreen(); @@ -672,6 +677,9 @@ void QXcbWindow::setVisible(bool visible) void QXcbWindow::show() { + if (shouldDeferTask(Task::Map)) + return; + if (window()->isTopLevel()) { // update WM_NORMAL_HINTS @@ -722,6 +730,10 @@ void QXcbWindow::show() void QXcbWindow::hide() { + if (shouldDeferTask(Task::Unmap)) + return; + + m_wmStateValid = false; xcb_unmap_window(xcb_connection(), m_window); // send synthetic UnmapNotify event according to icccm 4.1.4 @@ -881,6 +893,9 @@ QXcbWindow::NetWmStates QXcbWindow::netWmStates() void QXcbWindow::setWindowFlags(Qt::WindowFlags flags) { + if (shouldDeferTask(Task::SetWindowFlags)) + return; + Qt::WindowType type = static_cast(int(flags & Qt::WindowType_Mask)); if (type == Qt::ToolTip) @@ -910,6 +925,8 @@ void QXcbWindow::setWindowFlags(Qt::WindowFlags flags) setTransparentForMouseEvents(flags & Qt::WindowTransparentForInput); updateDoesNotAcceptFocus(flags & Qt::WindowDoesNotAcceptFocus); + + m_isWmManagedWindow = !(flags & Qt::X11BypassWindowManagerHint); } void QXcbWindow::setMotifWmHints(Qt::WindowFlags flags) @@ -1109,6 +1126,9 @@ void QXcbWindow::setWindowState(Qt::WindowStates state) if (state == m_windowState) return; + if (shouldDeferTask(Task::SetWindowState)) + return; + // unset old state if (m_windowState & Qt::WindowMinimized) xcb_map_window(xcb_connection(), m_window); @@ -1826,6 +1846,10 @@ void QXcbWindow::handleUnmapNotifyEvent(const xcb_unmap_notify_event_t *event) if (event->window == m_window) { m_mapped = false; QWindowSystemInterface::handleExposeEvent(window(), QRegion()); + if (!m_isWmManagedWindow) { + m_wmStateValid = true; + handleDeferredTasks(); + } } } @@ -2140,30 +2164,98 @@ void QXcbWindow::handleLeaveNotifyEvent(const xcb_leave_notify_event_t *event) handleLeaveNotifyEvent(event->root_x, event->root_y, event->mode, event->detail, event->time); } +bool QXcbWindow::shouldDeferTask(Task task) +{ + if (m_wmStateValid) + return false; + + m_deferredTasks.append(task); + return true; +} + +void QXcbWindow::handleDeferredTasks() +{ + Q_ASSERT(m_wmStateValid == true); + if (m_deferredTasks.isEmpty()) + return; + + bool map = false; + bool unmap = false; + + QVector tasks; + for (auto taskIt = m_deferredTasks.rbegin(); taskIt != m_deferredTasks.rend(); ++taskIt) { + if (!tasks.contains(*taskIt)) + tasks.prepend(*taskIt); + } + + for (Task task : tasks) { + switch (task) { + case Task::Map: + map = true; + unmap = false; + break; + case Task::Unmap: + unmap = true; + map = false; + break; + case Task::SetGeometry: + setGeometry(m_deferredGeometry); + break; + case Task::SetWindowFlags: + setWindowFlags(window()->flags()); + break; + case Task::SetWindowState: + setWindowState(window()->windowState()); + break; + } + } + m_deferredTasks.clear(); + + if (map) { + Q_ASSERT(unmap == false); + show(); + } + if (unmap) { + Q_ASSERT(map == false); + hide(); + } +} + void QXcbWindow::handlePropertyNotifyEvent(const xcb_property_notify_event_t *event) { connection()->setTime(event->time); - const bool propertyDeleted = event->state == XCB_PROPERTY_DELETE; - - if (event->atom == atom(QXcbAtom::_NET_WM_STATE) || event->atom == atom(QXcbAtom::WM_STATE)) { - if (propertyDeleted) + const bool wmStateChanged = event->atom == atom(QXcbAtom::WM_STATE); + const bool netWmStateChanged = event->atom == atom(QXcbAtom::_NET_WM_STATE); + if (netWmStateChanged || wmStateChanged) { + if (wmStateChanged && !m_wmStateValid && m_isWmManagedWindow) { + // ICCCM 4.1.4 + // Clients that want to re-use a client window (e.g. by mapping it again) + // after withdrawing it must wait for the withdrawal to be complete before + // proceeding. The preferred method for doing this is for clients to wait for + // a window manager to update or remove the WM_STATE property. + m_wmStateValid = true; + handleDeferredTasks(); + } + if (event->state == XCB_PROPERTY_DELETE) return; - Qt::WindowStates newState = Qt::WindowNoState; - - if (event->atom == atom(QXcbAtom::WM_STATE)) { // WM_STATE: Quick check for 'Minimize'. + if (wmStateChanged) { auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), 0, m_window, atom(QXcbAtom::WM_STATE), XCB_ATOM_ANY, 0, 1024); if (reply && reply->format == 32 && reply->type == atom(QXcbAtom::WM_STATE)) { - const quint32 *data = (const quint32 *)xcb_get_property_value(reply.get()); - if (reply->length != 0) - m_minimized = (data[0] == XCB_ICCCM_WM_STATE_ICONIC - || (data[0] == XCB_ICCCM_WM_STATE_WITHDRAWN && m_minimized)); + auto data = static_cast(xcb_get_property_value(reply.get())); + if (reply->length != 0) { + const bool changedToWithdrawn = data[0] == XCB_ICCCM_WM_STATE_WITHDRAWN; + const bool changedToIconic = data[0] == XCB_ICCCM_WM_STATE_ICONIC; + m_minimized = changedToIconic || (changedToWithdrawn && m_minimized); + } } } + // _NET_WM_STATE handling + Qt::WindowStates newState = Qt::WindowNoState; const NetWmStates states = netWmStates(); // _NET_WM_STATE_HIDDEN should be set by the Window Manager to indicate that a window would // not be visible on the screen if its desktop/viewport were active and its coordinates were @@ -2185,7 +2277,6 @@ void QXcbWindow::handlePropertyNotifyEvent(const xcb_property_notify_event_t *ev if ((m_windowState & Qt::WindowMinimized) && connection()->mouseGrabber() == this) connection()->setMouseGrabber(nullptr); } - return; } else if (event->atom == atom(QXcbAtom::_NET_FRAME_EXTENTS)) { m_dirtyFrameMargins = true; } diff --git a/src/plugins/platforms/xcb/qxcbwindow.h b/src/plugins/platforms/xcb/qxcbwindow.h index 5e5aa96629..f7bed7f67b 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.h +++ b/src/plugins/platforms/xcb/qxcbwindow.h @@ -74,6 +74,14 @@ public: Q_DECLARE_FLAGS(NetWmStates, NetWmState) + enum Task { + Map, + Unmap, + SetGeometry, + SetWindowFlags, + SetWindowState + }; + QXcbWindow(QWindow *window); ~QXcbWindow(); @@ -143,6 +151,9 @@ public: QXcbWindow *toWindow() override; + bool shouldDeferTask(Task task); + void handleDeferredTasks(); + void handleMouseEvent(xcb_timestamp_t time, const QPoint &local, const QPoint &global, Qt::KeyboardModifiers modifiers, QEvent::Type type, Qt::MouseEventSource source); @@ -276,6 +287,11 @@ protected: int m_swapInterval = -1; qreal m_sizeHintsScaleFactor = 1.0; + + bool m_wmStateValid = true; + QVector m_deferredTasks; + bool m_isWmManagedWindow = true; + QRect m_deferredGeometry; }; class QXcbForeignWindow : public QXcbWindow -- cgit v1.2.3