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
Pick-to: 6.2 5.15
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>
bb10
Gatis Paeglis 2018-11-07 14:49:37 +01:00 committed by Liang Qi
parent 02248eea55
commit e946e6895a
2 changed files with 119 additions and 12 deletions

View File

@ -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;
}

View File

@ -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