summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGatis Paeglis <gatis.paeglis@qt.io>2018-11-07 14:49:37 +0100
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2021-08-10 13:21:28 +0000
commit41f919c3471b9169e309679b3ce41c5c5bb1dff4 (patch)
treed01d1fba7473e66604fa6a576de032ff58066170
parent1527ca3bfb3c9aef97a3a8adb17c0fa530dd68f5 (diff)
xcb: implement missing bits from ICCCM 4.1.4 WM_STATE handling
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 <liang.qi@qt.io> Change-Id: I377a66ad3d5e43f14465d0ea670b2f43f96ed7d3 Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io> Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io> (cherry picked from commit e946e6895a8517a887ac246905e0769edd766fcc) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/plugins/platforms/xcb/qxcbwindow.cpp115
-rw-r--r--src/plugins/platforms/xcb/qxcbwindow.h16
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<Qt::WindowType>(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<Task> 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<const quint32 *>(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<Task> m_deferredTasks;
+ bool m_isWmManagedWindow = true;
+ QRect m_deferredGeometry;
};
class QXcbForeignWindow : public QXcbWindow