1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qxcbwindow.h" |
5 | |
6 | #include <QtDebug> |
7 | #include <QMetaEnum> |
8 | #include <QScreen> |
9 | #include <QtGui/QIcon> |
10 | #include <QtGui/QRegion> |
11 | #include <QtGui/private/qhighdpiscaling_p.h> |
12 | |
13 | #include "qxcbintegration.h" |
14 | #include "qxcbconnection.h" |
15 | #include "qxcbscreen.h" |
16 | #if QT_CONFIG(draganddrop) |
17 | #include "qxcbdrag.h" |
18 | #endif |
19 | #include "qxcbkeyboard.h" |
20 | #include "qxcbimage.h" |
21 | #include "qxcbwmsupport.h" |
22 | #include "qxcbimage.h" |
23 | #include "qxcbnativeinterface.h" |
24 | #include "qxcbsystemtraytracker.h" |
25 | |
26 | #include <qpa/qplatformintegration.h> |
27 | #include <qpa/qplatformcursor.h> |
28 | |
29 | #include <algorithm> |
30 | |
31 | #include <xcb/xcb_icccm.h> |
32 | #include <xcb/xfixes.h> |
33 | #include <xcb/shape.h> |
34 | #include <xcb/xinput.h> |
35 | |
36 | #include <private/qguiapplication_p.h> |
37 | #include <private/qwindow_p.h> |
38 | |
39 | #include <qpa/qplatformbackingstore.h> |
40 | #include <qpa/qwindowsysteminterface.h> |
41 | |
42 | #include <stdio.h> |
43 | |
44 | #if QT_CONFIG(xcb_xlib) |
45 | #define register /* C++17 deprecated register */ |
46 | #include <X11/Xlib.h> |
47 | #include <X11/Xutil.h> |
48 | #undef register |
49 | #endif |
50 | |
51 | #define XCOORD_MAX 32767 |
52 | enum { |
53 | defaultWindowWidth = 160, |
54 | defaultWindowHeight = 160 |
55 | }; |
56 | |
57 | QT_BEGIN_NAMESPACE |
58 | |
59 | using namespace Qt::StringLiterals; |
60 | |
61 | Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window" ); |
62 | |
63 | Q_DECLARE_TYPEINFO(xcb_rectangle_t, Q_PRIMITIVE_TYPE); |
64 | |
65 | #undef FocusIn |
66 | |
67 | enum QX11EmbedFocusInDetail { |
68 | XEMBED_FOCUS_CURRENT = 0, |
69 | XEMBED_FOCUS_FIRST = 1, |
70 | XEMBED_FOCUS_LAST = 2 |
71 | }; |
72 | |
73 | enum QX11EmbedInfoFlags { |
74 | XEMBED_MAPPED = (1 << 0), |
75 | }; |
76 | |
77 | enum QX11EmbedMessageType { |
78 | XEMBED_EMBEDDED_NOTIFY = 0, |
79 | XEMBED_WINDOW_ACTIVATE = 1, |
80 | XEMBED_WINDOW_DEACTIVATE = 2, |
81 | XEMBED_REQUEST_FOCUS = 3, |
82 | XEMBED_FOCUS_IN = 4, |
83 | XEMBED_FOCUS_OUT = 5, |
84 | XEMBED_FOCUS_NEXT = 6, |
85 | XEMBED_FOCUS_PREV = 7, |
86 | XEMBED_MODALITY_ON = 10, |
87 | XEMBED_MODALITY_OFF = 11, |
88 | XEMBED_REGISTER_ACCELERATOR = 12, |
89 | XEMBED_UNREGISTER_ACCELERATOR = 13, |
90 | XEMBED_ACTIVATE_ACCELERATOR = 14 |
91 | }; |
92 | |
93 | const quint32 XEMBED_VERSION = 0; |
94 | |
95 | QXcbScreen *QXcbWindow::parentScreen() |
96 | { |
97 | return parent() ? static_cast<QXcbWindow*>(parent())->parentScreen() : xcbScreen(); |
98 | } |
99 | |
100 | QXcbScreen *QXcbWindow::initialScreen() const |
101 | { |
102 | // Resolve initial screen via QWindowPrivate::screenForGeometry(), |
103 | // which works in platform independent coordinates, as opposed to |
104 | // QPlatformWindow::screenForGeometry() that uses native coordinates. |
105 | QWindowPrivate *windowPrivate = qt_window_private(window: window()); |
106 | QScreen *screen = windowPrivate->screenForGeometry(rect: window()->geometry()); |
107 | return static_cast<QXcbScreen*>(screen->handle()); |
108 | } |
109 | |
110 | // Returns \c true if we should set WM_TRANSIENT_FOR on \a w |
111 | static inline bool isTransient(const QWindow *w) |
112 | { |
113 | return w->type() == Qt::Dialog |
114 | || w->type() == Qt::Sheet |
115 | || w->type() == Qt::Tool |
116 | || w->type() == Qt::SplashScreen |
117 | || w->type() == Qt::ToolTip |
118 | || w->type() == Qt::Drawer |
119 | || w->type() == Qt::Popup; |
120 | } |
121 | |
122 | void QXcbWindow::setImageFormatForVisual(const xcb_visualtype_t *visual) |
123 | { |
124 | if (qt_xcb_imageFormatForVisual(connection: connection(), depth: m_depth, visual, imageFormat: &m_imageFormat, needsRgbSwap: &m_imageRgbSwap)) |
125 | return; |
126 | |
127 | switch (m_depth) { |
128 | case 32: |
129 | case 24: |
130 | qWarning(msg: "Using RGB32 fallback, if this works your X11 server is reporting a bad screen format." ); |
131 | m_imageFormat = QImage::Format_RGB32; |
132 | break; |
133 | case 16: |
134 | qWarning(msg: "Using RGB16 fallback, if this works your X11 server is reporting a bad screen format." ); |
135 | m_imageFormat = QImage::Format_RGB16; |
136 | default: |
137 | break; |
138 | } |
139 | } |
140 | |
141 | #if QT_CONFIG(xcb_xlib) |
142 | static inline XTextProperty* qstringToXTP(Display *dpy, const QString& s) |
143 | { |
144 | #include <X11/Xatom.h> |
145 | |
146 | static XTextProperty tp = { .value: nullptr, .encoding: 0, .format: 0, .nitems: 0 }; |
147 | static bool free_prop = true; // we can't free tp.value in case it references |
148 | // the data of the static QByteArray below. |
149 | if (tp.value) { |
150 | if (free_prop) |
151 | XFree(tp.value); |
152 | tp.value = nullptr; |
153 | free_prop = true; |
154 | } |
155 | |
156 | int errCode = 0; |
157 | QByteArray mapped = s.toLocal8Bit(); // should always be utf-8 |
158 | char* tl[2]; |
159 | tl[0] = mapped.data(); |
160 | tl[1] = nullptr; |
161 | errCode = XmbTextListToTextProperty(display: dpy, list: tl, count: 1, style: XStdICCTextStyle, text_prop_return: &tp); |
162 | if (errCode < 0) |
163 | qCDebug(lcQpaXcb, "XmbTextListToTextProperty result code %d" , errCode); |
164 | |
165 | if (errCode < 0) { |
166 | static QByteArray qcs; |
167 | qcs = s.toLatin1(); |
168 | tp.value = (uchar*)qcs.data(); |
169 | tp.encoding = XA_STRING; |
170 | tp.format = 8; |
171 | tp.nitems = qcs.size(); |
172 | free_prop = false; |
173 | } |
174 | return &tp; |
175 | } |
176 | #endif // QT_CONFIG(xcb_xlib) |
177 | |
178 | // TODO move this into a utility function in QWindow or QGuiApplication |
179 | static QWindow *childWindowAt(QWindow *win, const QPoint &p) |
180 | { |
181 | for (QObject *obj : win->children()) { |
182 | if (obj->isWindowType()) { |
183 | QWindow *childWin = static_cast<QWindow *>(obj); |
184 | if (childWin->isVisible()) { |
185 | if (QWindow *recurse = childWindowAt(win: childWin, p)) |
186 | return recurse; |
187 | } |
188 | } |
189 | } |
190 | if (!win->isTopLevel() |
191 | && !(win->flags() & Qt::WindowTransparentForInput) |
192 | && win->geometry().contains(p: win->parent()->mapFromGlobal(pos: p))) { |
193 | return win; |
194 | } |
195 | return nullptr; |
196 | } |
197 | |
198 | static const char *wm_window_type_property_id = "_q_xcb_wm_window_type" ; |
199 | static const char *wm_window_role_property_id = "_q_xcb_wm_window_role" ; |
200 | |
201 | QXcbWindow::QXcbWindow(QWindow *window) |
202 | : QPlatformWindow(window) |
203 | { |
204 | setConnection(xcbScreen()->connection()); |
205 | } |
206 | |
207 | enum : quint32 { |
208 | baseEventMask |
209 | = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY |
210 | | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_FOCUS_CHANGE, |
211 | |
212 | defaultEventMask = baseEventMask |
213 | | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
214 | | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
215 | | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
216 | | XCB_EVENT_MASK_POINTER_MOTION, |
217 | |
218 | transparentForInputEventMask = baseEventMask |
219 | | XCB_EVENT_MASK_VISIBILITY_CHANGE |
220 | | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
221 | | XCB_EVENT_MASK_COLOR_MAP_CHANGE | XCB_EVENT_MASK_OWNER_GRAB_BUTTON |
222 | }; |
223 | |
224 | void QXcbWindow::create() |
225 | { |
226 | destroy(); |
227 | |
228 | m_windowState = Qt::WindowNoState; |
229 | m_trayIconWindow = isTrayIconWindow(window: window()); |
230 | |
231 | Qt::WindowType type = window()->type(); |
232 | |
233 | QXcbScreen *currentScreen = xcbScreen(); |
234 | QXcbScreen *platformScreen = parent() ? parentScreen() : initialScreen(); |
235 | QRect rect = parent() |
236 | ? QHighDpi::toNativeLocalPosition(value: window()->geometry(), context: platformScreen) |
237 | : QHighDpi::toNativePixels(value: window()->geometry(), context: platformScreen); |
238 | |
239 | if (type == Qt::Desktop) { |
240 | m_window = platformScreen->root(); |
241 | m_depth = platformScreen->screen()->root_depth; |
242 | m_visualId = platformScreen->screen()->root_visual; |
243 | const xcb_visualtype_t *visual = nullptr; |
244 | if (connection()->hasDefaultVisualId()) { |
245 | visual = platformScreen->visualForId(visualid: connection()->defaultVisualId()); |
246 | if (visual) |
247 | m_visualId = connection()->defaultVisualId(); |
248 | if (!visual) |
249 | qWarning(msg: "Could not use default visual id. Falling back to root_visual for screen." ); |
250 | } |
251 | if (!visual) |
252 | visual = platformScreen->visualForId(visualid: m_visualId); |
253 | setImageFormatForVisual(visual); |
254 | connection()->addWindowEventListener(id: m_window, eventListener: this); |
255 | return; |
256 | } |
257 | |
258 | const QSize minimumSize = windowMinimumSize(); |
259 | if (rect.width() > 0 || rect.height() > 0) { |
260 | rect.setWidth(qBound(min: 1, val: rect.width(), XCOORD_MAX)); |
261 | rect.setHeight(qBound(min: 1, val: rect.height(), XCOORD_MAX)); |
262 | } else if (minimumSize.width() > 0 || minimumSize.height() > 0) { |
263 | rect.setSize(minimumSize); |
264 | } else { |
265 | rect.setWidth(QHighDpi::toNativePixels(value: int(defaultWindowWidth), context: platformScreen->QPlatformScreen::screen())); |
266 | rect.setHeight(QHighDpi::toNativePixels(value: int(defaultWindowHeight), context: platformScreen->QPlatformScreen::screen())); |
267 | } |
268 | |
269 | QPlatformWindow::setGeometry(rect); |
270 | |
271 | if (platformScreen != currentScreen) |
272 | QWindowSystemInterface::handleWindowScreenChanged(window: window(), newScreen: platformScreen->QPlatformScreen::screen()); |
273 | |
274 | xcb_window_t xcb_parent_id = platformScreen->root(); |
275 | if (parent()) { |
276 | xcb_parent_id = static_cast<QXcbWindow *>(parent())->xcb_window(); |
277 | m_embedded = parent()->isForeignWindow(); |
278 | |
279 | QSurfaceFormat parentFormat = parent()->window()->requestedFormat(); |
280 | if (window()->surfaceType() != QSurface::OpenGLSurface && parentFormat.hasAlpha()) { |
281 | window()->setFormat(parentFormat); |
282 | } |
283 | } |
284 | |
285 | resolveFormat(format: platformScreen->surfaceFormatFor(format: window()->requestedFormat())); |
286 | |
287 | const xcb_visualtype_t *visual = nullptr; |
288 | |
289 | if (m_trayIconWindow && connection()->systemTrayTracker()) { |
290 | visual = platformScreen->visualForId(visualid: connection()->systemTrayTracker()->visualId()); |
291 | } else if (connection()->hasDefaultVisualId()) { |
292 | visual = platformScreen->visualForId(visualid: connection()->defaultVisualId()); |
293 | if (!visual) |
294 | qWarning() << "Failed to use requested visual id." ; |
295 | } |
296 | |
297 | if (parent()) { |
298 | // When using a Vulkan QWindow via QWidget::createWindowContainer() we |
299 | // must make sure the visuals are compatible. Now, the parent will be |
300 | // of RasterGLSurface which typically chooses a GLX/EGL compatible |
301 | // visual which may not be what the Vulkan window would choose. |
302 | // Therefore, take the parent's visual. |
303 | if (window()->surfaceType() == QSurface::VulkanSurface |
304 | && parent()->window()->surfaceType() != QSurface::VulkanSurface) |
305 | { |
306 | visual = platformScreen->visualForId(visualid: static_cast<QXcbWindow *>(parent())->visualId()); |
307 | } |
308 | } |
309 | |
310 | if (!visual) |
311 | visual = createVisual(); |
312 | |
313 | if (!visual) { |
314 | qWarning() << "Falling back to using screens root_visual." ; |
315 | visual = platformScreen->visualForId(visualid: platformScreen->screen()->root_visual); |
316 | } |
317 | |
318 | Q_ASSERT(visual); |
319 | |
320 | m_visualId = visual->visual_id; |
321 | m_depth = platformScreen->depthOfVisual(visualid: m_visualId); |
322 | setImageFormatForVisual(visual); |
323 | |
324 | quint32 mask = XCB_CW_BACK_PIXMAP |
325 | | XCB_CW_BORDER_PIXEL |
326 | | XCB_CW_BIT_GRAVITY |
327 | | XCB_CW_OVERRIDE_REDIRECT |
328 | | XCB_CW_SAVE_UNDER |
329 | | XCB_CW_EVENT_MASK |
330 | | XCB_CW_COLORMAP; |
331 | |
332 | quint32 values[] = { |
333 | XCB_BACK_PIXMAP_NONE, |
334 | platformScreen->screen()->black_pixel, |
335 | XCB_GRAVITY_NORTH_WEST, |
336 | type == Qt::Popup || type == Qt::ToolTip || (window()->flags() & Qt::BypassWindowManagerHint), |
337 | type == Qt::Popup || type == Qt::Tool || type == Qt::SplashScreen || type == Qt::ToolTip || type == Qt::Drawer, |
338 | defaultEventMask, |
339 | platformScreen->colormapForVisual(visualid: m_visualId) |
340 | }; |
341 | |
342 | m_window = xcb_generate_id(c: xcb_connection()); |
343 | xcb_create_window(c: xcb_connection(), |
344 | depth: m_depth, |
345 | wid: m_window, // window id |
346 | parent: xcb_parent_id, // parent window id |
347 | x: rect.x(), |
348 | y: rect.y(), |
349 | width: rect.width(), |
350 | height: rect.height(), |
351 | border_width: 0, // border width |
352 | class: XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class |
353 | visual: m_visualId, // visual |
354 | value_mask: mask, |
355 | value_list: values); |
356 | |
357 | connection()->addWindowEventListener(id: m_window, eventListener: this); |
358 | |
359 | propagateSizeHints(); |
360 | |
361 | xcb_atom_t properties[5]; |
362 | int propertyCount = 0; |
363 | properties[propertyCount++] = atom(atom: QXcbAtom::AtomWM_DELETE_WINDOW); |
364 | properties[propertyCount++] = atom(atom: QXcbAtom::AtomWM_TAKE_FOCUS); |
365 | properties[propertyCount++] = atom(atom: QXcbAtom::Atom_NET_WM_PING); |
366 | |
367 | if (connection()->hasXSync()) |
368 | properties[propertyCount++] = atom(atom: QXcbAtom::Atom_NET_WM_SYNC_REQUEST); |
369 | |
370 | if (window()->flags() & Qt::WindowContextHelpButtonHint) |
371 | properties[propertyCount++] = atom(atom: QXcbAtom::Atom_NET_WM_CONTEXT_HELP); |
372 | |
373 | xcb_change_property(c: xcb_connection(), |
374 | mode: XCB_PROP_MODE_REPLACE, |
375 | window: m_window, |
376 | property: atom(atom: QXcbAtom::AtomWM_PROTOCOLS), |
377 | type: XCB_ATOM_ATOM, |
378 | format: 32, |
379 | data_len: propertyCount, |
380 | data: properties); |
381 | m_syncValue.hi = 0; |
382 | m_syncValue.lo = 0; |
383 | |
384 | const QByteArray wmClass = QXcbIntegration::instance()->wmClass(); |
385 | if (!wmClass.isEmpty()) { |
386 | xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, |
387 | window: m_window, property: atom(atom: QXcbAtom::AtomWM_CLASS), |
388 | type: XCB_ATOM_STRING, format: 8, data_len: wmClass.size(), data: wmClass.constData()); |
389 | } |
390 | |
391 | if (connection()->hasXSync()) { |
392 | m_syncCounter = xcb_generate_id(c: xcb_connection()); |
393 | xcb_sync_create_counter(c: xcb_connection(), id: m_syncCounter, initial_value: m_syncValue); |
394 | |
395 | xcb_change_property(c: xcb_connection(), |
396 | mode: XCB_PROP_MODE_REPLACE, |
397 | window: m_window, |
398 | property: atom(atom: QXcbAtom::Atom_NET_WM_SYNC_REQUEST_COUNTER), |
399 | type: XCB_ATOM_CARDINAL, |
400 | format: 32, |
401 | data_len: 1, |
402 | data: &m_syncCounter); |
403 | } |
404 | |
405 | // set the PID to let the WM kill the application if unresponsive |
406 | quint32 pid = getpid(); |
407 | xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: m_window, |
408 | property: atom(atom: QXcbAtom::Atom_NET_WM_PID), type: XCB_ATOM_CARDINAL, format: 32, |
409 | data_len: 1, data: &pid); |
410 | |
411 | const QByteArray clientMachine = QSysInfo::machineHostName().toLocal8Bit(); |
412 | if (!clientMachine.isEmpty()) { |
413 | xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: m_window, |
414 | property: atom(atom: QXcbAtom::AtomWM_CLIENT_MACHINE), type: XCB_ATOM_STRING, format: 8, |
415 | data_len: clientMachine.size(), data: clientMachine.constData()); |
416 | } |
417 | |
418 | // Create WM_HINTS property on the window, so we can xcb_icccm_get_wm_hints*() |
419 | // from various setter functions for adjusting the hints. |
420 | xcb_icccm_wm_hints_t hints; |
421 | memset(s: &hints, c: 0, n: sizeof(hints)); |
422 | hints.flags = XCB_ICCCM_WM_HINT_WINDOW_GROUP; |
423 | hints.window_group = connection()->clientLeader(); |
424 | xcb_icccm_set_wm_hints(c: xcb_connection(), window: m_window, hints: &hints); |
425 | |
426 | xcb_window_t leader = connection()->clientLeader(); |
427 | xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: m_window, |
428 | property: atom(atom: QXcbAtom::AtomWM_CLIENT_LEADER), type: XCB_ATOM_WINDOW, format: 32, |
429 | data_len: 1, data: &leader); |
430 | |
431 | /* Add XEMBED info; this operation doesn't initiate the embedding. */ |
432 | quint32 data[] = { XEMBED_VERSION, XEMBED_MAPPED }; |
433 | xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: m_window, |
434 | property: atom(atom: QXcbAtom::Atom_XEMBED_INFO), |
435 | type: atom(atom: QXcbAtom::Atom_XEMBED_INFO), |
436 | format: 32, data_len: 2, data: (void *)data); |
437 | |
438 | if (connection()->hasXInput2()) |
439 | connection()->xi2SelectDeviceEvents(window: m_window); |
440 | |
441 | setWindowState(window()->windowStates()); |
442 | setWindowFlags(window()->flags()); |
443 | setWindowTitle(window()->title()); |
444 | |
445 | // force sync to read outstanding requests - see QTBUG-29106 |
446 | connection()->sync(); |
447 | |
448 | #if QT_CONFIG(draganddrop) |
449 | connection()->drag()->dndEnable(win: this, on: true); |
450 | #endif |
451 | |
452 | const qreal opacity = qt_window_private(window: window())->opacity; |
453 | if (!qFuzzyCompare(p1: opacity, p2: qreal(1.0))) |
454 | setOpacity(opacity); |
455 | |
456 | setMask(QHighDpi::toNativeLocalRegion(pointRegion: window()->mask(), window: window())); |
457 | |
458 | if (window()->isTopLevel()) |
459 | setWindowIcon(window()->icon()); |
460 | |
461 | if (window()->dynamicPropertyNames().contains(t: wm_window_role_property_id)) { |
462 | QByteArray wmWindowRole = window()->property(name: wm_window_role_property_id).toByteArray(); |
463 | setWindowRole(QString::fromLatin1(ba: wmWindowRole)); |
464 | } |
465 | |
466 | if (m_trayIconWindow) |
467 | m_embedded = requestSystemTrayWindowDock(); |
468 | } |
469 | |
470 | QXcbWindow::~QXcbWindow() |
471 | { |
472 | destroy(); |
473 | } |
474 | |
475 | QXcbForeignWindow::QXcbForeignWindow(QWindow *window, WId nativeHandle) |
476 | : QXcbWindow(window) |
477 | { |
478 | m_window = nativeHandle; |
479 | |
480 | // Reflect the foreign window's geometry as our own |
481 | if (auto geometry = Q_XCB_REPLY(xcb_get_geometry, xcb_connection(), m_window)) { |
482 | QRect nativeGeometry(geometry->x, geometry->y, geometry->width, geometry->height); |
483 | QPlatformWindow::setGeometry(nativeGeometry); |
484 | } |
485 | } |
486 | |
487 | QXcbForeignWindow::~QXcbForeignWindow() |
488 | { |
489 | // Clear window so that destroy() does not affect it |
490 | m_window = 0; |
491 | |
492 | if (connection()->mouseGrabber() == this) |
493 | connection()->setMouseGrabber(nullptr); |
494 | if (connection()->mousePressWindow() == this) |
495 | connection()->setMousePressWindow(nullptr); |
496 | } |
497 | |
498 | void QXcbWindow::destroy() |
499 | { |
500 | if (connection()->focusWindow() == this) |
501 | doFocusOut(); |
502 | if (connection()->mouseGrabber() == this) |
503 | connection()->setMouseGrabber(nullptr); |
504 | |
505 | if (m_syncCounter && connection()->hasXSync()) |
506 | xcb_sync_destroy_counter(c: xcb_connection(), counter: m_syncCounter); |
507 | if (m_window) { |
508 | if (m_netWmUserTimeWindow) { |
509 | xcb_delete_property(c: xcb_connection(), window: m_window, property: atom(atom: QXcbAtom::Atom_NET_WM_USER_TIME_WINDOW)); |
510 | // Some window managers, like metacity, do XSelectInput on the _NET_WM_USER_TIME_WINDOW window, |
511 | // without trapping BadWindow (which crashes when the user time window is destroyed). |
512 | connection()->sync(); |
513 | xcb_destroy_window(c: xcb_connection(), window: m_netWmUserTimeWindow); |
514 | m_netWmUserTimeWindow = XCB_NONE; |
515 | } |
516 | connection()->removeWindowEventListener(id: m_window); |
517 | xcb_destroy_window(c: xcb_connection(), window: m_window); |
518 | m_window = 0; |
519 | } |
520 | |
521 | m_mapped = false; |
522 | m_recreationReasons = RecreationNotNeeded; |
523 | |
524 | if (m_pendingSyncRequest) |
525 | m_pendingSyncRequest->invalidate(); |
526 | } |
527 | |
528 | void QXcbWindow::setGeometry(const QRect &rect) |
529 | { |
530 | setWindowState(Qt::WindowNoState); |
531 | |
532 | QPlatformWindow::setGeometry(rect); |
533 | |
534 | propagateSizeHints(); |
535 | |
536 | QXcbScreen *currentScreen = xcbScreen(); |
537 | QXcbScreen *newScreen = parent() ? parentScreen() : static_cast<QXcbScreen*>(screenForGeometry(newGeometry: rect)); |
538 | |
539 | if (!newScreen) |
540 | newScreen = xcbScreen(); |
541 | |
542 | if (newScreen != currentScreen) |
543 | QWindowSystemInterface::handleWindowScreenChanged(window: window(), newScreen: newScreen->QPlatformScreen::screen()); |
544 | |
545 | if (qt_window_private(window: window())->positionAutomatic) { |
546 | const quint32 mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; |
547 | const qint32 values[] = { |
548 | qBound<qint32>(min: 1, val: rect.width(), XCOORD_MAX), |
549 | qBound<qint32>(min: 1, val: rect.height(), XCOORD_MAX), |
550 | }; |
551 | xcb_configure_window(c: xcb_connection(), window: m_window, value_mask: mask, value_list: reinterpret_cast<const quint32*>(values)); |
552 | } else { |
553 | const quint32 mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; |
554 | const qint32 values[] = { |
555 | qBound<qint32>(min: -XCOORD_MAX, val: rect.x(), XCOORD_MAX), |
556 | qBound<qint32>(min: -XCOORD_MAX, val: rect.y(), XCOORD_MAX), |
557 | qBound<qint32>(min: 1, val: rect.width(), XCOORD_MAX), |
558 | qBound<qint32>(min: 1, val: rect.height(), XCOORD_MAX), |
559 | }; |
560 | xcb_configure_window(c: xcb_connection(), window: m_window, value_mask: mask, value_list: reinterpret_cast<const quint32*>(values)); |
561 | if (window()->parent() && !window()->transientParent()) { |
562 | // Wait for server reply for parented windows to ensure that a few window |
563 | // moves will come as a one event. This is important when native widget is |
564 | // moved a few times in X and Y directions causing native scroll. Widget |
565 | // must get single event to not trigger unwanted widget position changes |
566 | // and then expose events causing backingstore flushes with incorrect |
567 | // offset causing image crruption. |
568 | connection()->sync(); |
569 | } |
570 | } |
571 | |
572 | xcb_flush(c: xcb_connection()); |
573 | } |
574 | |
575 | QMargins QXcbWindow::frameMargins() const |
576 | { |
577 | if (m_dirtyFrameMargins) { |
578 | if (connection()->wmSupport()->isSupportedByWM(atom: atom(atom: QXcbAtom::Atom_NET_FRAME_EXTENTS))) { |
579 | auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, m_window, |
580 | atom(QXcbAtom::Atom_NET_FRAME_EXTENTS), XCB_ATOM_CARDINAL, 0, 4); |
581 | if (reply && reply->type == XCB_ATOM_CARDINAL && reply->format == 32 && reply->value_len == 4) { |
582 | quint32 *data = (quint32 *)xcb_get_property_value(R: reply.get()); |
583 | // _NET_FRAME_EXTENTS format is left, right, top, bottom |
584 | m_frameMargins = QMargins(data[0], data[2], data[1], data[3]); |
585 | m_dirtyFrameMargins = false; |
586 | return m_frameMargins; |
587 | } |
588 | } |
589 | |
590 | // _NET_FRAME_EXTENTS property is not available, so |
591 | // walk up the window tree to get the frame parent |
592 | xcb_window_t window = m_window; |
593 | xcb_window_t parent = m_window; |
594 | |
595 | bool foundRoot = false; |
596 | |
597 | const QList<xcb_window_t> &virtualRoots = |
598 | connection()->wmSupport()->virtualRoots(); |
599 | |
600 | while (!foundRoot) { |
601 | auto reply = Q_XCB_REPLY_UNCHECKED(xcb_query_tree, xcb_connection(), parent); |
602 | if (reply) { |
603 | if (reply->root == reply->parent || virtualRoots.indexOf(t: reply->parent) != -1 || reply->parent == XCB_WINDOW_NONE) { |
604 | foundRoot = true; |
605 | } else { |
606 | window = parent; |
607 | parent = reply->parent; |
608 | } |
609 | } else { |
610 | m_dirtyFrameMargins = false; |
611 | m_frameMargins = QMargins(); |
612 | return m_frameMargins; |
613 | } |
614 | } |
615 | |
616 | QPoint offset; |
617 | |
618 | auto reply = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(), window, parent, 0, 0); |
619 | if (reply) { |
620 | offset = QPoint(reply->dst_x, reply->dst_y); |
621 | } |
622 | |
623 | auto geom = Q_XCB_REPLY(xcb_get_geometry, xcb_connection(), parent); |
624 | if (geom) { |
625 | // -- |
626 | // add the border_width for the window managers frame... some window managers |
627 | // do not use a border_width of zero for their frames, and if we the left and |
628 | // top strut, we ensure that pos() is absolutely correct. frameGeometry() |
629 | // will still be incorrect though... perhaps i should have foffset as well, to |
630 | // indicate the frame offset (equal to the border_width on X). |
631 | // - Brad |
632 | // -- copied from qwidget_x11.cpp |
633 | |
634 | int left = offset.x() + geom->border_width; |
635 | int top = offset.y() + geom->border_width; |
636 | int right = geom->width + geom->border_width - geometry().width() - offset.x(); |
637 | int bottom = geom->height + geom->border_width - geometry().height() - offset.y(); |
638 | |
639 | m_frameMargins = QMargins(left, top, right, bottom); |
640 | } |
641 | |
642 | m_dirtyFrameMargins = false; |
643 | } |
644 | |
645 | return m_frameMargins; |
646 | } |
647 | |
648 | void QXcbWindow::setVisible(bool visible) |
649 | { |
650 | if (visible) |
651 | show(); |
652 | else |
653 | hide(); |
654 | } |
655 | |
656 | void QXcbWindow::show() |
657 | { |
658 | if (window()->isTopLevel()) { |
659 | if (m_recreationReasons != RecreationNotNeeded) { |
660 | qCDebug(lcQpaWindow) << "QXcbWindow: need to recreate window" << window() << m_recreationReasons; |
661 | create(); |
662 | m_recreationReasons = RecreationNotNeeded; |
663 | } |
664 | |
665 | // update WM_NORMAL_HINTS |
666 | propagateSizeHints(); |
667 | |
668 | // update WM_TRANSIENT_FOR |
669 | xcb_window_t transientXcbParent = 0; |
670 | if (isTransient(w: window())) { |
671 | const QWindow *tp = window()->transientParent(); |
672 | if (tp && tp->handle()) |
673 | transientXcbParent = tp->handle()->winId(); |
674 | // Default to client leader if there is no transient parent, else modal dialogs can |
675 | // be hidden by their parents. |
676 | if (!transientXcbParent) |
677 | transientXcbParent = connection()->clientLeader(); |
678 | if (transientXcbParent) { // ICCCM 4.1.2.6 |
679 | xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: m_window, |
680 | property: XCB_ATOM_WM_TRANSIENT_FOR, type: XCB_ATOM_WINDOW, format: 32, |
681 | data_len: 1, data: &transientXcbParent); |
682 | } |
683 | } |
684 | if (!transientXcbParent) |
685 | xcb_delete_property(c: xcb_connection(), window: m_window, property: XCB_ATOM_WM_TRANSIENT_FOR); |
686 | |
687 | // update _NET_WM_STATE |
688 | setNetWmStateOnUnmappedWindow(); |
689 | } |
690 | |
691 | // QWidget-attribute Qt::WA_ShowWithoutActivating. |
692 | const auto showWithoutActivating = window()->property(name: "_q_showWithoutActivating" ); |
693 | if (showWithoutActivating.isValid() && showWithoutActivating.toBool()) |
694 | updateNetWmUserTime(timestamp: 0); |
695 | else if (connection()->time() != XCB_TIME_CURRENT_TIME) |
696 | updateNetWmUserTime(timestamp: connection()->time()); |
697 | |
698 | if (m_trayIconWindow) |
699 | return; // defer showing until XEMBED_EMBEDDED_NOTIFY |
700 | |
701 | xcb_map_window(c: xcb_connection(), window: m_window); |
702 | |
703 | if (QGuiApplication::modalWindow() == window()) |
704 | requestActivateWindow(); |
705 | |
706 | xcbScreen()->windowShown(window: this); |
707 | |
708 | connection()->sync(); |
709 | } |
710 | |
711 | void QXcbWindow::hide() |
712 | { |
713 | xcb_unmap_window(c: xcb_connection(), window: m_window); |
714 | |
715 | // send synthetic UnmapNotify event according to icccm 4.1.4 |
716 | q_padded_xcb_event<xcb_unmap_notify_event_t> event = {}; |
717 | event.response_type = XCB_UNMAP_NOTIFY; |
718 | event.event = xcbScreen()->root(); |
719 | event.window = m_window; |
720 | event.from_configure = false; |
721 | xcb_send_event(c: xcb_connection(), propagate: false, destination: xcbScreen()->root(), |
722 | event_mask: XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, event: (const char *)&event); |
723 | |
724 | xcb_flush(c: xcb_connection()); |
725 | |
726 | if (connection()->mouseGrabber() == this) |
727 | connection()->setMouseGrabber(nullptr); |
728 | if (QPlatformWindow *w = connection()->mousePressWindow()) { |
729 | // Unset mousePressWindow when it (or one of its parents) is unmapped |
730 | while (w) { |
731 | if (w == this) { |
732 | connection()->setMousePressWindow(nullptr); |
733 | break; |
734 | } |
735 | w = w->parent(); |
736 | } |
737 | } |
738 | |
739 | m_mapped = false; |
740 | |
741 | // Hiding a modal window doesn't send an enter event to its transient parent when the |
742 | // mouse is already over the parent window, so the enter event must be emulated. |
743 | if (window()->isModal()) { |
744 | // Get the cursor position at modal window screen |
745 | const QPoint nativePos = xcbScreen()->cursor()->pos(); |
746 | const QPoint cursorPos = QHighDpi::fromNativePixels(value: nativePos, context: xcbScreen()->screenForPosition(point: nativePos)->screen()); |
747 | |
748 | // Find the top level window at cursor position. |
749 | // Don't use QGuiApplication::topLevelAt(): search only the virtual siblings of this window's screen |
750 | QWindow *enterWindow = nullptr; |
751 | const auto screens = xcbScreen()->virtualSiblings(); |
752 | for (QPlatformScreen *screen : screens) { |
753 | if (screen->geometry().contains(p: cursorPos)) { |
754 | const QPoint devicePosition = QHighDpi::toNativePixels(value: cursorPos, context: screen->screen()); |
755 | enterWindow = screen->topLevelAt(point: devicePosition); |
756 | break; |
757 | } |
758 | } |
759 | |
760 | if (enterWindow && enterWindow != window()) { |
761 | // Find the child window at cursor position, otherwise use the top level window |
762 | if (QWindow *childWindow = childWindowAt(win: enterWindow, p: cursorPos)) |
763 | enterWindow = childWindow; |
764 | const QPoint localPos = enterWindow->mapFromGlobal(pos: cursorPos); |
765 | QWindowSystemInterface::handleEnterEvent(window: enterWindow, |
766 | local: localPos * QHighDpiScaling::factor(context: enterWindow), |
767 | global: nativePos); |
768 | } |
769 | } |
770 | } |
771 | |
772 | bool QXcbWindow::relayFocusToModalWindow() const |
773 | { |
774 | QWindow *w = static_cast<QWindowPrivate *>(QObjectPrivate::get(o: window()))->eventReceiver(); |
775 | // get top-level window |
776 | while (w && w->parent()) |
777 | w = w->parent(); |
778 | |
779 | QWindow *modalWindow = nullptr; |
780 | const bool blocked = QGuiApplicationPrivate::instance()->isWindowBlocked(window: w, blockingWindow: &modalWindow); |
781 | if (blocked && modalWindow != w) { |
782 | modalWindow->requestActivate(); |
783 | connection()->flush(); |
784 | return true; |
785 | } |
786 | |
787 | return false; |
788 | } |
789 | |
790 | void QXcbWindow::doFocusIn() |
791 | { |
792 | if (relayFocusToModalWindow()) |
793 | return; |
794 | QWindow *w = static_cast<QWindowPrivate *>(QObjectPrivate::get(o: window()))->eventReceiver(); |
795 | connection()->setFocusWindow(w); |
796 | QWindowSystemInterface::handleWindowActivated(window: w, r: Qt::ActiveWindowFocusReason); |
797 | } |
798 | |
799 | void QXcbWindow::doFocusOut() |
800 | { |
801 | connection()->setFocusWindow(nullptr); |
802 | relayFocusToModalWindow(); |
803 | // Do not set the active window to nullptr if there is a FocusIn coming. |
804 | connection()->focusInTimer().start(); |
805 | } |
806 | |
807 | struct QtMotifWmHints { |
808 | quint32 flags, functions, decorations; |
809 | qint32 input_mode; // unused |
810 | quint32 status; // unused |
811 | }; |
812 | |
813 | enum { |
814 | MWM_HINTS_FUNCTIONS = (1L << 0), |
815 | |
816 | MWM_FUNC_ALL = (1L << 0), |
817 | MWM_FUNC_RESIZE = (1L << 1), |
818 | MWM_FUNC_MOVE = (1L << 2), |
819 | MWM_FUNC_MINIMIZE = (1L << 3), |
820 | MWM_FUNC_MAXIMIZE = (1L << 4), |
821 | MWM_FUNC_CLOSE = (1L << 5), |
822 | |
823 | MWM_HINTS_DECORATIONS = (1L << 1), |
824 | |
825 | MWM_DECOR_ALL = (1L << 0), |
826 | MWM_DECOR_BORDER = (1L << 1), |
827 | MWM_DECOR_RESIZEH = (1L << 2), |
828 | MWM_DECOR_TITLE = (1L << 3), |
829 | = (1L << 4), |
830 | MWM_DECOR_MINIMIZE = (1L << 5), |
831 | MWM_DECOR_MAXIMIZE = (1L << 6), |
832 | }; |
833 | |
834 | QXcbWindow::NetWmStates QXcbWindow::netWmStates() |
835 | { |
836 | NetWmStates result; |
837 | |
838 | auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(), |
839 | 0, m_window, atom(QXcbAtom::Atom_NET_WM_STATE), |
840 | XCB_ATOM_ATOM, 0, 1024); |
841 | |
842 | if (reply && reply->format == 32 && reply->type == XCB_ATOM_ATOM) { |
843 | const xcb_atom_t *states = static_cast<const xcb_atom_t *>(xcb_get_property_value(R: reply.get())); |
844 | const xcb_atom_t *statesEnd = states + reply->length; |
845 | if (statesEnd != std::find(first: states, last: statesEnd, val: atom(atom: QXcbAtom::Atom_NET_WM_STATE_ABOVE))) |
846 | result |= NetWmStateAbove; |
847 | if (statesEnd != std::find(first: states, last: statesEnd, val: atom(atom: QXcbAtom::Atom_NET_WM_STATE_BELOW))) |
848 | result |= NetWmStateBelow; |
849 | if (statesEnd != std::find(first: states, last: statesEnd, val: atom(atom: QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN))) |
850 | result |= NetWmStateFullScreen; |
851 | if (statesEnd != std::find(first: states, last: statesEnd, val: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ))) |
852 | result |= NetWmStateMaximizedHorz; |
853 | if (statesEnd != std::find(first: states, last: statesEnd, val: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT))) |
854 | result |= NetWmStateMaximizedVert; |
855 | if (statesEnd != std::find(first: states, last: statesEnd, val: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MODAL))) |
856 | result |= NetWmStateModal; |
857 | if (statesEnd != std::find(first: states, last: statesEnd, val: atom(atom: QXcbAtom::Atom_NET_WM_STATE_STAYS_ON_TOP))) |
858 | result |= NetWmStateStaysOnTop; |
859 | if (statesEnd != std::find(first: states, last: statesEnd, val: atom(atom: QXcbAtom::Atom_NET_WM_STATE_DEMANDS_ATTENTION))) |
860 | result |= NetWmStateDemandsAttention; |
861 | if (statesEnd != std::find(first: states, last: statesEnd, val: atom(atom: QXcbAtom::Atom_NET_WM_STATE_HIDDEN))) |
862 | result |= NetWmStateHidden; |
863 | } else { |
864 | qCDebug(lcQpaXcb, "getting net wm state (%x), empty\n" , m_window); |
865 | } |
866 | |
867 | return result; |
868 | } |
869 | |
870 | void QXcbWindow::setWindowFlags(Qt::WindowFlags flags) |
871 | { |
872 | Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask)); |
873 | |
874 | if (type == Qt::ToolTip) |
875 | flags |= Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint; |
876 | if (type == Qt::Popup) |
877 | flags |= Qt::X11BypassWindowManagerHint; |
878 | |
879 | Qt::WindowFlags oldflags = window()->flags(); |
880 | if ((oldflags & Qt::WindowStaysOnTopHint) != (flags & Qt::WindowStaysOnTopHint)) |
881 | m_recreationReasons |= WindowStaysOnTopHintChanged; |
882 | if ((oldflags & Qt::WindowStaysOnBottomHint) != (flags & Qt::WindowStaysOnBottomHint)) |
883 | m_recreationReasons |= WindowStaysOnBottomHintChanged; |
884 | |
885 | const quint32 mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; |
886 | const quint32 values[] = { |
887 | // XCB_CW_OVERRIDE_REDIRECT |
888 | (flags & Qt::BypassWindowManagerHint) ? 1u : 0, |
889 | // XCB_CW_EVENT_MASK |
890 | (flags & Qt::WindowTransparentForInput) ? transparentForInputEventMask : defaultEventMask |
891 | }; |
892 | |
893 | xcb_change_window_attributes(c: xcb_connection(), window: xcb_window(), value_mask: mask, value_list: values); |
894 | |
895 | WindowTypes wmWindowTypes; |
896 | if (window()->dynamicPropertyNames().contains(t: wm_window_type_property_id)) { |
897 | wmWindowTypes = static_cast<WindowTypes>( |
898 | qvariant_cast<int>(v: window()->property(name: wm_window_type_property_id))); |
899 | } |
900 | |
901 | setWmWindowType(types: wmWindowTypes, flags); |
902 | setNetWmState(flags); |
903 | setMotifWmHints(flags); |
904 | |
905 | setTransparentForMouseEvents(flags & Qt::WindowTransparentForInput); |
906 | updateDoesNotAcceptFocus(doesNotAcceptFocus: flags & Qt::WindowDoesNotAcceptFocus); |
907 | } |
908 | |
909 | void QXcbWindow::setMotifWmHints(Qt::WindowFlags flags) |
910 | { |
911 | Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask)); |
912 | |
913 | QtMotifWmHints mwmhints; |
914 | memset(s: &mwmhints, c: 0, n: sizeof(mwmhints)); |
915 | |
916 | if (type != Qt::SplashScreen) { |
917 | mwmhints.flags |= MWM_HINTS_DECORATIONS; |
918 | |
919 | bool customize = flags & Qt::CustomizeWindowHint; |
920 | if (type == Qt::Window && !customize) { |
921 | const Qt::WindowFlags defaultFlags = Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint; |
922 | if (!(flags & defaultFlags)) |
923 | flags |= defaultFlags; |
924 | } |
925 | if (!(flags & Qt::FramelessWindowHint) && !(customize && !(flags & Qt::WindowTitleHint))) { |
926 | mwmhints.decorations |= MWM_DECOR_BORDER; |
927 | mwmhints.decorations |= MWM_DECOR_RESIZEH; |
928 | mwmhints.decorations |= MWM_DECOR_TITLE; |
929 | |
930 | if (flags & Qt::WindowSystemMenuHint) |
931 | mwmhints.decorations |= MWM_DECOR_MENU; |
932 | |
933 | if (flags & Qt::WindowMinimizeButtonHint) { |
934 | mwmhints.decorations |= MWM_DECOR_MINIMIZE; |
935 | mwmhints.functions |= MWM_FUNC_MINIMIZE; |
936 | } |
937 | |
938 | if (flags & Qt::WindowMaximizeButtonHint) { |
939 | mwmhints.decorations |= MWM_DECOR_MAXIMIZE; |
940 | mwmhints.functions |= MWM_FUNC_MAXIMIZE; |
941 | } |
942 | |
943 | if (flags & Qt::WindowCloseButtonHint) |
944 | mwmhints.functions |= MWM_FUNC_CLOSE; |
945 | } |
946 | } else { |
947 | // if type == Qt::SplashScreen |
948 | mwmhints.decorations = MWM_DECOR_ALL; |
949 | } |
950 | |
951 | if (mwmhints.functions != 0) { |
952 | mwmhints.flags |= MWM_HINTS_FUNCTIONS; |
953 | mwmhints.functions |= MWM_FUNC_MOVE | MWM_FUNC_RESIZE; |
954 | } else { |
955 | mwmhints.functions = MWM_FUNC_ALL; |
956 | } |
957 | |
958 | if (!(flags & Qt::FramelessWindowHint) |
959 | && flags & Qt::CustomizeWindowHint |
960 | && flags & Qt::WindowTitleHint |
961 | && !(flags & |
962 | (Qt::WindowMinimizeButtonHint |
963 | | Qt::WindowMaximizeButtonHint |
964 | | Qt::WindowCloseButtonHint))) |
965 | { |
966 | // a special case - only the titlebar without any button |
967 | mwmhints.flags = MWM_HINTS_FUNCTIONS; |
968 | mwmhints.functions = MWM_FUNC_MOVE | MWM_FUNC_RESIZE; |
969 | mwmhints.decorations = 0; |
970 | } |
971 | |
972 | if (mwmhints.flags) { |
973 | xcb_change_property(c: xcb_connection(), |
974 | mode: XCB_PROP_MODE_REPLACE, |
975 | window: m_window, |
976 | property: atom(atom: QXcbAtom::Atom_MOTIF_WM_HINTS), |
977 | type: atom(atom: QXcbAtom::Atom_MOTIF_WM_HINTS), |
978 | format: 32, |
979 | data_len: 5, |
980 | data: &mwmhints); |
981 | } else { |
982 | xcb_delete_property(c: xcb_connection(), window: m_window, property: atom(atom: QXcbAtom::Atom_MOTIF_WM_HINTS)); |
983 | } |
984 | } |
985 | |
986 | void QXcbWindow::setNetWmState(bool set, xcb_atom_t one, xcb_atom_t two) |
987 | { |
988 | xcb_client_message_event_t event; |
989 | |
990 | event.response_type = XCB_CLIENT_MESSAGE; |
991 | event.format = 32; |
992 | event.sequence = 0; |
993 | event.window = m_window; |
994 | event.type = atom(atom: QXcbAtom::Atom_NET_WM_STATE); |
995 | event.data.data32[0] = set ? 1 : 0; |
996 | event.data.data32[1] = one; |
997 | event.data.data32[2] = two; |
998 | event.data.data32[3] = 0; |
999 | event.data.data32[4] = 0; |
1000 | |
1001 | xcb_send_event(c: xcb_connection(), propagate: 0, destination: xcbScreen()->root(), |
1002 | event_mask: XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, |
1003 | event: (const char *)&event); |
1004 | } |
1005 | |
1006 | void QXcbWindow::setNetWmState(Qt::WindowStates state) |
1007 | { |
1008 | if ((m_windowState ^ state) & Qt::WindowMaximized) { |
1009 | setNetWmState(set: state & Qt::WindowMaximized, |
1010 | one: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ), |
1011 | two: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT)); |
1012 | } |
1013 | |
1014 | if ((m_windowState ^ state) & Qt::WindowFullScreen) |
1015 | setNetWmState(set: state & Qt::WindowFullScreen, one: atom(atom: QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN)); |
1016 | } |
1017 | |
1018 | void QXcbWindow::setNetWmState(Qt::WindowFlags flags) |
1019 | { |
1020 | setNetWmState(set: flags & Qt::WindowStaysOnTopHint, |
1021 | one: atom(atom: QXcbAtom::Atom_NET_WM_STATE_ABOVE), |
1022 | two: atom(atom: QXcbAtom::Atom_NET_WM_STATE_STAYS_ON_TOP)); |
1023 | setNetWmState(set: flags & Qt::WindowStaysOnBottomHint, one: atom(atom: QXcbAtom::Atom_NET_WM_STATE_BELOW)); |
1024 | } |
1025 | |
1026 | void QXcbWindow::setNetWmStateOnUnmappedWindow() |
1027 | { |
1028 | if (Q_UNLIKELY(m_mapped)) |
1029 | qCWarning(lcQpaXcb()) << "internal error: " << Q_FUNC_INFO << "called on mapped window" ; |
1030 | |
1031 | NetWmStates states; |
1032 | const Qt::WindowFlags flags = window()->flags(); |
1033 | if (flags & Qt::WindowStaysOnTopHint) { |
1034 | states |= NetWmStateAbove; |
1035 | states |= NetWmStateStaysOnTop; |
1036 | } else if (flags & Qt::WindowStaysOnBottomHint) { |
1037 | states |= NetWmStateBelow; |
1038 | } |
1039 | |
1040 | if (window()->windowStates() & Qt::WindowMinimized) |
1041 | states |= NetWmStateHidden; |
1042 | |
1043 | if (window()->windowStates() & Qt::WindowFullScreen) |
1044 | states |= NetWmStateFullScreen; |
1045 | |
1046 | if (window()->windowStates() & Qt::WindowMaximized) { |
1047 | states |= NetWmStateMaximizedHorz; |
1048 | states |= NetWmStateMaximizedVert; |
1049 | } |
1050 | |
1051 | if (window()->modality() != Qt::NonModal) |
1052 | states |= NetWmStateModal; |
1053 | |
1054 | // According to EWMH: |
1055 | // "The Window Manager should remove _NET_WM_STATE whenever a window is withdrawn". |
1056 | // Which means that we don't have to read this property before changing it on a withdrawn |
1057 | // window. But there are situations where users want to adjust this property as well |
1058 | // (e4cea305ed2ba3c9f580bf9d16c59a1048af0e8a), so instead of overwriting the property |
1059 | // we first read it and then merge our hints with the existing values, allowing a user |
1060 | // to set custom hints. |
1061 | |
1062 | QList<xcb_atom_t> atoms; |
1063 | auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(), |
1064 | 0, m_window, atom(QXcbAtom::Atom_NET_WM_STATE), |
1065 | XCB_ATOM_ATOM, 0, 1024); |
1066 | if (reply && reply->format == 32 && reply->type == XCB_ATOM_ATOM && reply->value_len > 0) { |
1067 | const xcb_atom_t *data = static_cast<const xcb_atom_t *>(xcb_get_property_value(R: reply.get())); |
1068 | atoms.resize(size: reply->value_len); |
1069 | memcpy(dest: (void *)&atoms.first(), src: (void *)data, n: reply->value_len * sizeof(xcb_atom_t)); |
1070 | } |
1071 | |
1072 | if (states & NetWmStateAbove && !atoms.contains(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_ABOVE))) |
1073 | atoms.push_back(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_ABOVE)); |
1074 | if (states & NetWmStateBelow && !atoms.contains(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_BELOW))) |
1075 | atoms.push_back(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_BELOW)); |
1076 | if (states & NetWmStateHidden && !atoms.contains(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_HIDDEN))) |
1077 | atoms.push_back(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_HIDDEN)); |
1078 | if (states & NetWmStateFullScreen && !atoms.contains(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN))) |
1079 | atoms.push_back(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN)); |
1080 | if (states & NetWmStateMaximizedHorz && !atoms.contains(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ))) |
1081 | atoms.push_back(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ)); |
1082 | if (states & NetWmStateMaximizedVert && !atoms.contains(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT))) |
1083 | atoms.push_back(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT)); |
1084 | if (states & NetWmStateModal && !atoms.contains(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MODAL))) |
1085 | atoms.push_back(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MODAL)); |
1086 | if (states & NetWmStateStaysOnTop && !atoms.contains(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_STAYS_ON_TOP))) |
1087 | atoms.push_back(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_STAYS_ON_TOP)); |
1088 | if (states & NetWmStateDemandsAttention && !atoms.contains(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_DEMANDS_ATTENTION))) |
1089 | atoms.push_back(t: atom(atom: QXcbAtom::Atom_NET_WM_STATE_DEMANDS_ATTENTION)); |
1090 | |
1091 | if (atoms.isEmpty()) { |
1092 | xcb_delete_property(c: xcb_connection(), window: m_window, property: atom(atom: QXcbAtom::Atom_NET_WM_STATE)); |
1093 | } else { |
1094 | xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: m_window, |
1095 | property: atom(atom: QXcbAtom::Atom_NET_WM_STATE), type: XCB_ATOM_ATOM, format: 32, |
1096 | data_len: atoms.size(), data: atoms.constData()); |
1097 | } |
1098 | xcb_flush(c: xcb_connection()); |
1099 | } |
1100 | |
1101 | void QXcbWindow::setWindowState(Qt::WindowStates state) |
1102 | { |
1103 | if (state == m_windowState) |
1104 | return; |
1105 | |
1106 | // unset old state |
1107 | if (m_windowState & Qt::WindowMinimized) |
1108 | xcb_map_window(c: xcb_connection(), window: m_window); |
1109 | if (m_windowState & Qt::WindowMaximized) |
1110 | setNetWmState(set: false, |
1111 | one: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ), |
1112 | two: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT)); |
1113 | if (m_windowState & Qt::WindowFullScreen) |
1114 | setNetWmState(set: false, one: atom(atom: QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN)); |
1115 | |
1116 | // set new state |
1117 | if (state & Qt::WindowMinimized) { |
1118 | { |
1119 | xcb_client_message_event_t event; |
1120 | |
1121 | event.response_type = XCB_CLIENT_MESSAGE; |
1122 | event.format = 32; |
1123 | event.sequence = 0; |
1124 | event.window = m_window; |
1125 | event.type = atom(atom: QXcbAtom::AtomWM_CHANGE_STATE); |
1126 | event.data.data32[0] = XCB_ICCCM_WM_STATE_ICONIC; |
1127 | event.data.data32[1] = 0; |
1128 | event.data.data32[2] = 0; |
1129 | event.data.data32[3] = 0; |
1130 | event.data.data32[4] = 0; |
1131 | |
1132 | xcb_send_event(c: xcb_connection(), propagate: 0, destination: xcbScreen()->root(), |
1133 | event_mask: XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, |
1134 | event: (const char *)&event); |
1135 | } |
1136 | m_minimized = true; |
1137 | } |
1138 | if (state & Qt::WindowMaximized) |
1139 | setNetWmState(set: true, |
1140 | one: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ), |
1141 | two: atom(atom: QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT)); |
1142 | if (state & Qt::WindowFullScreen) |
1143 | setNetWmState(set: true, one: atom(atom: QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN)); |
1144 | |
1145 | setNetWmState(state); |
1146 | |
1147 | xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_hints_unchecked(c: xcb_connection(), window: m_window); |
1148 | xcb_icccm_wm_hints_t hints; |
1149 | if (xcb_icccm_get_wm_hints_reply(c: xcb_connection(), cookie, hints: &hints, e: nullptr)) { |
1150 | if (state & Qt::WindowMinimized) |
1151 | xcb_icccm_wm_hints_set_iconic(hints: &hints); |
1152 | else |
1153 | xcb_icccm_wm_hints_set_normal(hints: &hints); |
1154 | xcb_icccm_set_wm_hints(c: xcb_connection(), window: m_window, hints: &hints); |
1155 | } |
1156 | |
1157 | connection()->sync(); |
1158 | m_windowState = state; |
1159 | } |
1160 | |
1161 | void QXcbWindow::updateNetWmUserTime(xcb_timestamp_t timestamp) |
1162 | { |
1163 | xcb_window_t wid = m_window; |
1164 | // If timestamp == 0, then it means that the window should not be |
1165 | // initially activated. Don't update global user time for this |
1166 | // special case. |
1167 | if (timestamp != 0) |
1168 | connection()->setNetWmUserTime(timestamp); |
1169 | |
1170 | const bool isSupportedByWM = connection()->wmSupport()->isSupportedByWM(atom: atom(atom: QXcbAtom::Atom_NET_WM_USER_TIME_WINDOW)); |
1171 | if (m_netWmUserTimeWindow || isSupportedByWM) { |
1172 | if (!m_netWmUserTimeWindow) { |
1173 | m_netWmUserTimeWindow = xcb_generate_id(c: xcb_connection()); |
1174 | xcb_create_window(c: xcb_connection(), |
1175 | XCB_COPY_FROM_PARENT, // depth -- same as root |
1176 | wid: m_netWmUserTimeWindow, // window id |
1177 | parent: m_window, // parent window id |
1178 | x: -1, y: -1, width: 1, height: 1, |
1179 | border_width: 0, // border width |
1180 | class: XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class |
1181 | visual: m_visualId, // visual |
1182 | value_mask: 0, // value mask |
1183 | value_list: nullptr); // value list |
1184 | wid = m_netWmUserTimeWindow; |
1185 | xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: m_window, property: atom(atom: QXcbAtom::Atom_NET_WM_USER_TIME_WINDOW), |
1186 | type: XCB_ATOM_WINDOW, format: 32, data_len: 1, data: &m_netWmUserTimeWindow); |
1187 | xcb_delete_property(c: xcb_connection(), window: m_window, property: atom(atom: QXcbAtom::Atom_NET_WM_USER_TIME)); |
1188 | |
1189 | QXcbWindow::setWindowTitle(conn: connection(), window: m_netWmUserTimeWindow, |
1190 | QStringLiteral("Qt NET_WM User Time Window" )); |
1191 | |
1192 | } else if (!isSupportedByWM) { |
1193 | // WM no longer supports it, then we should remove the |
1194 | // _NET_WM_USER_TIME_WINDOW atom. |
1195 | xcb_delete_property(c: xcb_connection(), window: m_window, property: atom(atom: QXcbAtom::Atom_NET_WM_USER_TIME_WINDOW)); |
1196 | xcb_destroy_window(c: xcb_connection(), window: m_netWmUserTimeWindow); |
1197 | m_netWmUserTimeWindow = XCB_NONE; |
1198 | } else { |
1199 | wid = m_netWmUserTimeWindow; |
1200 | } |
1201 | } |
1202 | xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: wid, property: atom(atom: QXcbAtom::Atom_NET_WM_USER_TIME), |
1203 | type: XCB_ATOM_CARDINAL, format: 32, data_len: 1, data: ×tamp); |
1204 | } |
1205 | |
1206 | void QXcbWindow::setTransparentForMouseEvents(bool transparent) |
1207 | { |
1208 | if (!connection()->hasXFixes() || transparent == m_transparent) |
1209 | return; |
1210 | |
1211 | xcb_rectangle_t rectangle; |
1212 | |
1213 | xcb_rectangle_t *rect = nullptr; |
1214 | int nrect = 0; |
1215 | |
1216 | if (!transparent) { |
1217 | rectangle.x = 0; |
1218 | rectangle.y = 0; |
1219 | rectangle.width = geometry().width(); |
1220 | rectangle.height = geometry().height(); |
1221 | rect = &rectangle; |
1222 | nrect = 1; |
1223 | } |
1224 | |
1225 | xcb_xfixes_region_t region = xcb_generate_id(c: xcb_connection()); |
1226 | xcb_xfixes_create_region(c: xcb_connection(), region, rectangles_len: nrect, rectangles: rect); |
1227 | xcb_xfixes_set_window_shape_region_checked(c: xcb_connection(), dest: m_window, dest_kind: XCB_SHAPE_SK_INPUT, x_offset: 0, y_offset: 0, region); |
1228 | xcb_xfixes_destroy_region(c: xcb_connection(), region); |
1229 | |
1230 | m_transparent = transparent; |
1231 | } |
1232 | |
1233 | void QXcbWindow::updateDoesNotAcceptFocus(bool doesNotAcceptFocus) |
1234 | { |
1235 | xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_hints_unchecked(c: xcb_connection(), window: m_window); |
1236 | |
1237 | xcb_icccm_wm_hints_t hints; |
1238 | if (!xcb_icccm_get_wm_hints_reply(c: xcb_connection(), cookie, hints: &hints, e: nullptr)) |
1239 | return; |
1240 | |
1241 | xcb_icccm_wm_hints_set_input(hints: &hints, input: !doesNotAcceptFocus); |
1242 | xcb_icccm_set_wm_hints(c: xcb_connection(), window: m_window, hints: &hints); |
1243 | } |
1244 | |
1245 | WId QXcbWindow::winId() const |
1246 | { |
1247 | return m_window; |
1248 | } |
1249 | |
1250 | void QXcbWindow::setParent(const QPlatformWindow *parent) |
1251 | { |
1252 | QPoint topLeft = geometry().topLeft(); |
1253 | |
1254 | xcb_window_t xcb_parent_id; |
1255 | if (parent) { |
1256 | const QXcbWindow *qXcbParent = static_cast<const QXcbWindow *>(parent); |
1257 | xcb_parent_id = qXcbParent->xcb_window(); |
1258 | m_embedded = qXcbParent->isForeignWindow(); |
1259 | } else { |
1260 | xcb_parent_id = xcbScreen()->root(); |
1261 | m_embedded = false; |
1262 | } |
1263 | xcb_reparent_window(c: xcb_connection(), window: xcb_window(), parent: xcb_parent_id, x: topLeft.x(), y: topLeft.y()); |
1264 | } |
1265 | |
1266 | void QXcbWindow::setWindowTitle(const QString &title) |
1267 | { |
1268 | setWindowTitle(conn: connection(), window: m_window, title); |
1269 | } |
1270 | |
1271 | void QXcbWindow::setWindowIconText(const QString &title) |
1272 | { |
1273 | const QByteArray ba = title.toUtf8(); |
1274 | xcb_change_property(c: xcb_connection(), |
1275 | mode: XCB_PROP_MODE_REPLACE, |
1276 | window: m_window, |
1277 | property: atom(atom: QXcbAtom::Atom_NET_WM_ICON_NAME), |
1278 | type: atom(atom: QXcbAtom::AtomUTF8_STRING), |
1279 | format: 8, |
1280 | data_len: ba.size(), |
1281 | data: ba.constData()); |
1282 | } |
1283 | |
1284 | void QXcbWindow::setWindowIcon(const QIcon &icon) |
1285 | { |
1286 | QList<quint32> icon_data; |
1287 | if (!icon.isNull()) { |
1288 | QList<QSize> availableSizes = icon.availableSizes(); |
1289 | if (availableSizes.isEmpty()) { |
1290 | // try to use default sizes since the icon can be a scalable image like svg. |
1291 | availableSizes.push_back(t: QSize(16,16)); |
1292 | availableSizes.push_back(t: QSize(32,32)); |
1293 | availableSizes.push_back(t: QSize(64,64)); |
1294 | availableSizes.push_back(t: QSize(128,128)); |
1295 | } |
1296 | for (int i = 0; i < availableSizes.size(); ++i) { |
1297 | QSize size = availableSizes.at(i); |
1298 | QPixmap pixmap = icon.pixmap(size); |
1299 | if (!pixmap.isNull()) { |
1300 | QImage image = pixmap.toImage().convertToFormat(f: QImage::Format_ARGB32); |
1301 | int pos = icon_data.size(); |
1302 | icon_data.resize(size: pos + 2 + image.width()*image.height()); |
1303 | icon_data[pos++] = image.width(); |
1304 | icon_data[pos++] = image.height(); |
1305 | memcpy(dest: icon_data.data() + pos, src: image.bits(), n: image.width()*image.height()*4); |
1306 | } |
1307 | } |
1308 | } |
1309 | |
1310 | if (!icon_data.isEmpty()) { |
1311 | // Ignore icon exceeding maximum xcb request length |
1312 | if (quint64(icon_data.size()) > quint64(xcb_get_maximum_request_length(c: xcb_connection()))) { |
1313 | qWarning() << "Ignoring window icon" << icon_data.size() |
1314 | << "exceeds maximum xcb request length" |
1315 | << xcb_get_maximum_request_length(c: xcb_connection()); |
1316 | return; |
1317 | } |
1318 | xcb_change_property(c: xcb_connection(), |
1319 | mode: XCB_PROP_MODE_REPLACE, |
1320 | window: m_window, |
1321 | property: atom(atom: QXcbAtom::Atom_NET_WM_ICON), |
1322 | type: atom(atom: QXcbAtom::AtomCARDINAL), |
1323 | format: 32, |
1324 | data_len: icon_data.size(), |
1325 | data: (unsigned char *) icon_data.data()); |
1326 | } else { |
1327 | xcb_delete_property(c: xcb_connection(), |
1328 | window: m_window, |
1329 | property: atom(atom: QXcbAtom::Atom_NET_WM_ICON)); |
1330 | } |
1331 | } |
1332 | |
1333 | void QXcbWindow::raise() |
1334 | { |
1335 | const quint32 mask = XCB_CONFIG_WINDOW_STACK_MODE; |
1336 | const quint32 values[] = { XCB_STACK_MODE_ABOVE }; |
1337 | xcb_configure_window(c: xcb_connection(), window: m_window, value_mask: mask, value_list: values); |
1338 | } |
1339 | |
1340 | void QXcbWindow::lower() |
1341 | { |
1342 | const quint32 mask = XCB_CONFIG_WINDOW_STACK_MODE; |
1343 | const quint32 values[] = { XCB_STACK_MODE_BELOW }; |
1344 | xcb_configure_window(c: xcb_connection(), window: m_window, value_mask: mask, value_list: values); |
1345 | } |
1346 | |
1347 | void QXcbWindow::propagateSizeHints() |
1348 | { |
1349 | // update WM_NORMAL_HINTS |
1350 | xcb_size_hints_t hints; |
1351 | memset(s: &hints, c: 0, n: sizeof(hints)); |
1352 | |
1353 | const QRect rect = geometry(); |
1354 | QWindowPrivate *win = qt_window_private(window: window()); |
1355 | |
1356 | if (!win->positionAutomatic) |
1357 | xcb_icccm_size_hints_set_position(hints: &hints, user_specified: true, x: rect.x(), y: rect.y()); |
1358 | if (rect.width() < QWINDOWSIZE_MAX || rect.height() < QWINDOWSIZE_MAX) |
1359 | xcb_icccm_size_hints_set_size(hints: &hints, user_specified: true, width: rect.width(), height: rect.height()); |
1360 | |
1361 | /* Gravity describes how to interpret x and y values the next time |
1362 | window needs to be positioned on a screen. |
1363 | XCB_GRAVITY_STATIC : the left top corner of the client window |
1364 | XCB_GRAVITY_NORTH_WEST : the left top corner of the frame window */ |
1365 | auto gravity = win->positionPolicy == QWindowPrivate::WindowFrameInclusive |
1366 | ? XCB_GRAVITY_NORTH_WEST : XCB_GRAVITY_STATIC; |
1367 | |
1368 | xcb_icccm_size_hints_set_win_gravity(hints: &hints, win_gravity: gravity); |
1369 | |
1370 | QSize minimumSize = windowMinimumSize(); |
1371 | QSize maximumSize = windowMaximumSize(); |
1372 | QSize baseSize = windowBaseSize(); |
1373 | QSize sizeIncrement = windowSizeIncrement(); |
1374 | |
1375 | if (minimumSize.width() > 0 || minimumSize.height() > 0) |
1376 | xcb_icccm_size_hints_set_min_size(hints: &hints, |
1377 | min_width: qMin(XCOORD_MAX,b: minimumSize.width()), |
1378 | min_height: qMin(XCOORD_MAX,b: minimumSize.height())); |
1379 | |
1380 | if (maximumSize.width() < QWINDOWSIZE_MAX || maximumSize.height() < QWINDOWSIZE_MAX) |
1381 | xcb_icccm_size_hints_set_max_size(hints: &hints, |
1382 | max_width: qMin(XCOORD_MAX, b: maximumSize.width()), |
1383 | max_height: qMin(XCOORD_MAX, b: maximumSize.height())); |
1384 | |
1385 | if (sizeIncrement.width() > 0 || sizeIncrement.height() > 0) { |
1386 | xcb_icccm_size_hints_set_base_size(hints: &hints, base_width: baseSize.width(), base_height: baseSize.height()); |
1387 | xcb_icccm_size_hints_set_resize_inc(hints: &hints, width_inc: sizeIncrement.width(), height_inc: sizeIncrement.height()); |
1388 | } |
1389 | |
1390 | xcb_icccm_set_wm_normal_hints(c: xcb_connection(), window: m_window, hints: &hints); |
1391 | |
1392 | m_sizeHintsScaleFactor = QHighDpiScaling::factor(context: screen()); |
1393 | } |
1394 | |
1395 | void QXcbWindow::requestActivateWindow() |
1396 | { |
1397 | /* Never activate embedded windows; doing that would prevent the container |
1398 | * to re-gain the keyboard focus later. */ |
1399 | if (m_embedded) { |
1400 | QPlatformWindow::requestActivateWindow(); |
1401 | return; |
1402 | } |
1403 | |
1404 | if (!m_mapped) { |
1405 | m_deferredActivation = true; |
1406 | return; |
1407 | } |
1408 | m_deferredActivation = false; |
1409 | |
1410 | updateNetWmUserTime(timestamp: connection()->time()); |
1411 | QWindow *focusWindow = QGuiApplication::focusWindow(); |
1412 | |
1413 | if (window()->isTopLevel() |
1414 | && !(window()->flags() & Qt::X11BypassWindowManagerHint) |
1415 | && (!focusWindow || !window()->isAncestorOf(child: focusWindow)) |
1416 | && connection()->wmSupport()->isSupportedByWM(atom: atom(atom: QXcbAtom::Atom_NET_ACTIVE_WINDOW))) { |
1417 | xcb_client_message_event_t event; |
1418 | |
1419 | event.response_type = XCB_CLIENT_MESSAGE; |
1420 | event.format = 32; |
1421 | event.sequence = 0; |
1422 | event.window = m_window; |
1423 | event.type = atom(atom: QXcbAtom::Atom_NET_ACTIVE_WINDOW); |
1424 | event.data.data32[0] = 1; |
1425 | event.data.data32[1] = connection()->time(); |
1426 | event.data.data32[2] = focusWindow ? focusWindow->winId() : XCB_NONE; |
1427 | event.data.data32[3] = 0; |
1428 | event.data.data32[4] = 0; |
1429 | |
1430 | xcb_send_event(c: xcb_connection(), propagate: 0, destination: xcbScreen()->root(), |
1431 | event_mask: XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, |
1432 | event: (const char *)&event); |
1433 | } else { |
1434 | xcb_set_input_focus(c: xcb_connection(), revert_to: XCB_INPUT_FOCUS_PARENT, focus: m_window, time: connection()->time()); |
1435 | } |
1436 | |
1437 | connection()->sync(); |
1438 | } |
1439 | |
1440 | QSurfaceFormat QXcbWindow::format() const |
1441 | { |
1442 | return m_format; |
1443 | } |
1444 | |
1445 | QXcbWindow::WindowTypes QXcbWindow::wmWindowTypes() const |
1446 | { |
1447 | WindowTypes result; |
1448 | |
1449 | auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(), |
1450 | 0, m_window, atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE), |
1451 | XCB_ATOM_ATOM, 0, 1024); |
1452 | if (reply && reply->format == 32 && reply->type == XCB_ATOM_ATOM) { |
1453 | const xcb_atom_t *types = static_cast<const xcb_atom_t *>(xcb_get_property_value(R: reply.get())); |
1454 | const xcb_atom_t *types_end = types + reply->length; |
1455 | for (; types != types_end; types++) { |
1456 | QXcbAtom::Atom type = connection()->qatom(atom: *types); |
1457 | switch (type) { |
1458 | case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NORMAL: |
1459 | result |= WindowType::Normal; |
1460 | break; |
1461 | case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DESKTOP: |
1462 | result |= WindowType::Desktop; |
1463 | break; |
1464 | case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DOCK: |
1465 | result |= WindowType::Dock; |
1466 | break; |
1467 | case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLBAR: |
1468 | result |= WindowType::Toolbar; |
1469 | break; |
1470 | case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_MENU: |
1471 | result |= WindowType::Menu; |
1472 | break; |
1473 | case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_UTILITY: |
1474 | result |= WindowType::Utility; |
1475 | break; |
1476 | case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_SPLASH: |
1477 | result |= WindowType::Splash; |
1478 | break; |
1479 | case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DIALOG: |
1480 | result |= WindowType::Dialog; |
1481 | break; |
1482 | case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DROPDOWN_MENU: |
1483 | result |= WindowType::DropDownMenu; |
1484 | break; |
1485 | case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_POPUP_MENU: |
1486 | result |= WindowType::PopupMenu; |
1487 | break; |
1488 | case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLTIP: |
1489 | result |= WindowType::Tooltip; |
1490 | break; |
1491 | case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NOTIFICATION: |
1492 | result |= WindowType::Notification; |
1493 | break; |
1494 | case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_COMBO: |
1495 | result |= WindowType::Combo; |
1496 | break; |
1497 | case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DND: |
1498 | result |= WindowType::Dnd; |
1499 | break; |
1500 | case QXcbAtom::Atom_KDE_NET_WM_WINDOW_TYPE_OVERRIDE: |
1501 | result |= WindowType::KdeOverride; |
1502 | break; |
1503 | default: |
1504 | break; |
1505 | } |
1506 | } |
1507 | } |
1508 | return result; |
1509 | } |
1510 | |
1511 | void QXcbWindow::setWmWindowType(WindowTypes types, Qt::WindowFlags flags) |
1512 | { |
1513 | QList<xcb_atom_t> atoms; |
1514 | |
1515 | // manual selection 1 (these are never set by Qt and take precedence) |
1516 | if (types & WindowType::Normal) |
1517 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NORMAL)); |
1518 | if (types & WindowType::Desktop) |
1519 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DESKTOP)); |
1520 | if (types & WindowType::Dock) |
1521 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DOCK)); |
1522 | if (types & WindowType::Notification) |
1523 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NOTIFICATION)); |
1524 | |
1525 | // manual selection 2 (Qt uses these during auto selection); |
1526 | if (types & WindowType::Utility) |
1527 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_UTILITY)); |
1528 | if (types & WindowType::Splash) |
1529 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_SPLASH)); |
1530 | if (types & WindowType::Dialog) |
1531 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DIALOG)); |
1532 | if (types & WindowType::Tooltip) |
1533 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLTIP)); |
1534 | if (types & WindowType::KdeOverride) |
1535 | atoms.append(t: atom(atom: QXcbAtom::Atom_KDE_NET_WM_WINDOW_TYPE_OVERRIDE)); |
1536 | |
1537 | // manual selection 3 (these can be set by Qt, but don't have a |
1538 | // corresponding Qt::WindowType). note that order of the *MENU |
1539 | // atoms is important |
1540 | if (types & WindowType::Menu) |
1541 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_MENU)); |
1542 | if (types & WindowType::DropDownMenu) |
1543 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DROPDOWN_MENU)); |
1544 | if (types & WindowType::PopupMenu) |
1545 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_POPUP_MENU)); |
1546 | if (types & WindowType::Toolbar) |
1547 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLBAR)); |
1548 | if (types & WindowType::Combo) |
1549 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_COMBO)); |
1550 | if (types & WindowType::Dnd) |
1551 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DND)); |
1552 | |
1553 | // automatic selection |
1554 | Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask)); |
1555 | switch (type) { |
1556 | case Qt::Dialog: |
1557 | case Qt::Sheet: |
1558 | if (!(types & WindowType::Dialog)) |
1559 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DIALOG)); |
1560 | break; |
1561 | case Qt::Tool: |
1562 | case Qt::Drawer: |
1563 | if (!(types & WindowType::Utility)) |
1564 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_UTILITY)); |
1565 | break; |
1566 | case Qt::ToolTip: |
1567 | if (!(types & WindowType::Tooltip)) |
1568 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLTIP)); |
1569 | break; |
1570 | case Qt::SplashScreen: |
1571 | if (!(types & WindowType::Splash)) |
1572 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_SPLASH)); |
1573 | break; |
1574 | default: |
1575 | break; |
1576 | } |
1577 | |
1578 | if ((flags & Qt::FramelessWindowHint) && !(types & WindowType::KdeOverride)) { |
1579 | // override netwm type - quick and easy for KDE noborder |
1580 | atoms.append(t: atom(atom: QXcbAtom::Atom_KDE_NET_WM_WINDOW_TYPE_OVERRIDE)); |
1581 | } |
1582 | |
1583 | if (atoms.size() == 1 && atoms.first() == atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NORMAL)) |
1584 | atoms.clear(); |
1585 | else |
1586 | atoms.append(t: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NORMAL)); |
1587 | |
1588 | if (atoms.isEmpty()) { |
1589 | xcb_delete_property(c: xcb_connection(), window: m_window, property: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE)); |
1590 | } else { |
1591 | xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: m_window, |
1592 | property: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_TYPE), type: XCB_ATOM_ATOM, format: 32, |
1593 | data_len: atoms.size(), data: atoms.constData()); |
1594 | } |
1595 | xcb_flush(c: xcb_connection()); |
1596 | } |
1597 | |
1598 | void QXcbWindow::setWindowRole(const QString &role) |
1599 | { |
1600 | QByteArray roleData = role.toLatin1(); |
1601 | xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: m_window, |
1602 | property: atom(atom: QXcbAtom::AtomWM_WINDOW_ROLE), type: XCB_ATOM_STRING, format: 8, |
1603 | data_len: roleData.size(), data: roleData.constData()); |
1604 | } |
1605 | |
1606 | void QXcbWindow::setParentRelativeBackPixmap() |
1607 | { |
1608 | const quint32 mask = XCB_CW_BACK_PIXMAP; |
1609 | const quint32 values[] = { XCB_BACK_PIXMAP_PARENT_RELATIVE }; |
1610 | xcb_change_window_attributes(c: xcb_connection(), window: m_window, value_mask: mask, value_list: values); |
1611 | } |
1612 | |
1613 | bool QXcbWindow::requestSystemTrayWindowDock() |
1614 | { |
1615 | if (!connection()->systemTrayTracker()) |
1616 | return false; |
1617 | connection()->systemTrayTracker()->requestSystemTrayWindowDock(window: m_window); |
1618 | return true; |
1619 | } |
1620 | |
1621 | bool QXcbWindow::handleNativeEvent(xcb_generic_event_t *event) |
1622 | { |
1623 | auto eventType = connection()->nativeInterface()->nativeEventType(); |
1624 | qintptr result = 0; // Used only by MS Windows |
1625 | return QWindowSystemInterface::handleNativeEvent(window: window(), eventType, message: event, result: &result); |
1626 | } |
1627 | |
1628 | void QXcbWindow::handleExposeEvent(const xcb_expose_event_t *event) |
1629 | { |
1630 | QRect rect(event->x, event->y, event->width, event->height); |
1631 | m_exposeRegion |= rect; |
1632 | |
1633 | bool pending = true; |
1634 | |
1635 | connection()->eventQueue()->peek(option: QXcbEventQueue::PeekConsumeMatchAndContinue, |
1636 | peeker: [this, &pending](xcb_generic_event_t *event, int type) { |
1637 | if (type != XCB_EXPOSE) |
1638 | return false; |
1639 | auto expose = reinterpret_cast<xcb_expose_event_t *>(event); |
1640 | if (expose->window != m_window) |
1641 | return false; |
1642 | if (expose->count == 0) |
1643 | pending = false; |
1644 | m_exposeRegion |= QRect(expose->x, expose->y, expose->width, expose->height); |
1645 | free(ptr: expose); |
1646 | return true; |
1647 | }); |
1648 | |
1649 | // if count is non-zero there are more expose events pending |
1650 | if (event->count == 0 || !pending) { |
1651 | QWindowSystemInterface::handleExposeEvent(window: window(), region: m_exposeRegion); |
1652 | m_exposeRegion = QRegion(); |
1653 | } |
1654 | } |
1655 | |
1656 | void QXcbWindow::handleClientMessageEvent(const xcb_client_message_event_t *event) |
1657 | { |
1658 | if (event->format != 32) |
1659 | return; |
1660 | |
1661 | if (event->type == atom(atom: QXcbAtom::AtomWM_PROTOCOLS)) { |
1662 | xcb_atom_t protocolAtom = event->data.data32[0]; |
1663 | if (protocolAtom == atom(atom: QXcbAtom::AtomWM_DELETE_WINDOW)) { |
1664 | QWindowSystemInterface::handleCloseEvent(window: window()); |
1665 | } else if (protocolAtom == atom(atom: QXcbAtom::AtomWM_TAKE_FOCUS)) { |
1666 | connection()->setTime(event->data.data32[1]); |
1667 | relayFocusToModalWindow(); |
1668 | return; |
1669 | } else if (protocolAtom == atom(atom: QXcbAtom::Atom_NET_WM_PING)) { |
1670 | if (event->window == xcbScreen()->root()) |
1671 | return; |
1672 | |
1673 | xcb_client_message_event_t reply = *event; |
1674 | |
1675 | reply.response_type = XCB_CLIENT_MESSAGE; |
1676 | reply.window = xcbScreen()->root(); |
1677 | |
1678 | xcb_send_event(c: xcb_connection(), propagate: 0, destination: xcbScreen()->root(), |
1679 | event_mask: XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, |
1680 | event: (const char *)&reply); |
1681 | xcb_flush(c: xcb_connection()); |
1682 | } else if (protocolAtom == atom(atom: QXcbAtom::Atom_NET_WM_SYNC_REQUEST)) { |
1683 | connection()->setTime(event->data.data32[1]); |
1684 | m_syncValue.lo = event->data.data32[2]; |
1685 | m_syncValue.hi = event->data.data32[3]; |
1686 | if (connection()->hasXSync()) |
1687 | m_syncState = SyncReceived; |
1688 | #ifndef QT_NO_WHATSTHIS |
1689 | } else if (protocolAtom == atom(atom: QXcbAtom::Atom_NET_WM_CONTEXT_HELP)) { |
1690 | QWindowSystemInterface::handleEnterWhatsThisEvent(); |
1691 | #endif |
1692 | } else { |
1693 | qCWarning(lcQpaXcb, "Unhandled WM_PROTOCOLS (%s)" , |
1694 | connection()->atomName(protocolAtom).constData()); |
1695 | } |
1696 | #if QT_CONFIG(draganddrop) |
1697 | } else if (event->type == atom(atom: QXcbAtom::AtomXdndEnter)) { |
1698 | connection()->drag()->handleEnter(window: this, event); |
1699 | } else if (event->type == atom(atom: QXcbAtom::AtomXdndPosition)) { |
1700 | connection()->drag()->handlePosition(w: this, event); |
1701 | } else if (event->type == atom(atom: QXcbAtom::AtomXdndLeave)) { |
1702 | connection()->drag()->handleLeave(w: this, event); |
1703 | } else if (event->type == atom(atom: QXcbAtom::AtomXdndDrop)) { |
1704 | connection()->drag()->handleDrop(this, event); |
1705 | #endif |
1706 | } else if (event->type == atom(atom: QXcbAtom::Atom_XEMBED)) { |
1707 | handleXEmbedMessage(event); |
1708 | } else if (event->type == atom(atom: QXcbAtom::Atom_NET_ACTIVE_WINDOW)) { |
1709 | doFocusIn(); |
1710 | } else if (event->type == atom(atom: QXcbAtom::AtomMANAGER) |
1711 | || event->type == atom(atom: QXcbAtom::Atom_NET_WM_STATE) |
1712 | || event->type == atom(atom: QXcbAtom::AtomWM_CHANGE_STATE)) { |
1713 | // Ignore _NET_WM_STATE, MANAGER which are relate to tray icons |
1714 | // and other messages. |
1715 | } else if (event->type == atom(atom: QXcbAtom::Atom_COMPIZ_DECOR_PENDING) |
1716 | || event->type == atom(atom: QXcbAtom::Atom_COMPIZ_DECOR_REQUEST) |
1717 | || event->type == atom(atom: QXcbAtom::Atom_COMPIZ_DECOR_DELETE_PIXMAP) |
1718 | || event->type == atom(atom: QXcbAtom::Atom_COMPIZ_TOOLKIT_ACTION) |
1719 | || event->type == atom(atom: QXcbAtom::Atom_GTK_LOAD_ICONTHEMES)) { |
1720 | //silence the _COMPIZ and _GTK messages for now |
1721 | } else { |
1722 | qCWarning(lcQpaXcb) << "Unhandled client message: " << connection()->atomName(atom: event->type); |
1723 | } |
1724 | } |
1725 | |
1726 | void QXcbWindow::handleConfigureNotifyEvent(const xcb_configure_notify_event_t *event) |
1727 | { |
1728 | bool fromSendEvent = (event->response_type & 0x80); |
1729 | QPoint pos(event->x, event->y); |
1730 | if (!parent() && !fromSendEvent) { |
1731 | // Do not trust the position, query it instead. |
1732 | auto reply = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(), |
1733 | xcb_window(), xcbScreen()->root(), 0, 0); |
1734 | if (reply) { |
1735 | pos.setX(reply->dst_x); |
1736 | pos.setY(reply->dst_y); |
1737 | } |
1738 | } |
1739 | |
1740 | const QRect actualGeometry = QRect(pos, QSize(event->width, event->height)); |
1741 | QPlatformScreen *newScreen = parent() ? parent()->screen() : screenForGeometry(newGeometry: actualGeometry); |
1742 | if (!newScreen) |
1743 | return; |
1744 | |
1745 | QWindowSystemInterface::handleGeometryChange(window: window(), newRect: actualGeometry); |
1746 | |
1747 | // QPlatformScreen::screen() is updated asynchronously, so we can't compare it |
1748 | // with the newScreen. Just send the WindowScreenChanged event and QGuiApplication |
1749 | // will make the comparison later. |
1750 | QWindowSystemInterface::handleWindowScreenChanged(window: window(), newScreen: newScreen->screen()); |
1751 | |
1752 | if (!qFuzzyCompare(p1: QHighDpiScaling::factor(context: newScreen), p2: m_sizeHintsScaleFactor)) |
1753 | propagateSizeHints(); |
1754 | |
1755 | // Send the synthetic expose event on resize only when the window is shrunk, |
1756 | // because the "XCB_GRAVITY_NORTH_WEST" flag doesn't send it automatically. |
1757 | if (!m_oldWindowSize.isEmpty() |
1758 | && (actualGeometry.width() < m_oldWindowSize.width() |
1759 | || actualGeometry.height() < m_oldWindowSize.height())) { |
1760 | QWindowSystemInterface::handleExposeEvent(window: window(), region: QRegion(0, 0, actualGeometry.width(), actualGeometry.height())); |
1761 | } |
1762 | m_oldWindowSize = actualGeometry.size(); |
1763 | |
1764 | if (connection()->hasXSync() && m_syncState == SyncReceived) |
1765 | m_syncState = SyncAndConfigureReceived; |
1766 | |
1767 | m_dirtyFrameMargins = true; |
1768 | } |
1769 | |
1770 | bool QXcbWindow::isExposed() const |
1771 | { |
1772 | return m_mapped; |
1773 | } |
1774 | |
1775 | bool QXcbWindow::isEmbedded() const |
1776 | { |
1777 | return m_embedded; |
1778 | } |
1779 | |
1780 | QPoint QXcbWindow::mapToGlobal(const QPoint &pos) const |
1781 | { |
1782 | if (!m_embedded) |
1783 | return QPlatformWindow::mapToGlobal(pos); |
1784 | |
1785 | QPoint ret; |
1786 | auto reply = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(), |
1787 | xcb_window(), xcbScreen()->root(), |
1788 | pos.x(), pos.y()); |
1789 | if (reply) { |
1790 | ret.setX(reply->dst_x); |
1791 | ret.setY(reply->dst_y); |
1792 | } |
1793 | |
1794 | return ret; |
1795 | } |
1796 | |
1797 | QPoint QXcbWindow::mapFromGlobal(const QPoint &pos) const |
1798 | { |
1799 | if (!m_embedded) |
1800 | return QPlatformWindow::mapFromGlobal(pos); |
1801 | |
1802 | QPoint ret; |
1803 | auto reply = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(), |
1804 | xcbScreen()->root(), xcb_window(), |
1805 | pos.x(), pos.y()); |
1806 | if (reply) { |
1807 | ret.setX(reply->dst_x); |
1808 | ret.setY(reply->dst_y); |
1809 | } |
1810 | |
1811 | return ret; |
1812 | } |
1813 | |
1814 | void QXcbWindow::handleMapNotifyEvent(const xcb_map_notify_event_t *event) |
1815 | { |
1816 | if (event->window == m_window) { |
1817 | m_mapped = true; |
1818 | if (m_deferredActivation) |
1819 | requestActivateWindow(); |
1820 | |
1821 | QWindowSystemInterface::handleExposeEvent(window: window(), region: QRect(QPoint(), geometry().size())); |
1822 | } |
1823 | } |
1824 | |
1825 | void QXcbWindow::handleUnmapNotifyEvent(const xcb_unmap_notify_event_t *event) |
1826 | { |
1827 | if (event->window == m_window) { |
1828 | m_mapped = false; |
1829 | QWindowSystemInterface::handleExposeEvent(window: window(), region: QRegion()); |
1830 | } |
1831 | } |
1832 | |
1833 | void QXcbWindow::handleButtonPressEvent(int event_x, int event_y, int root_x, int root_y, |
1834 | int detail, Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, |
1835 | QEvent::Type type, Qt::MouseEventSource source) |
1836 | { |
1837 | const bool isWheel = detail >= 4 && detail <= 7; |
1838 | if (!isWheel && window() != QGuiApplication::focusWindow()) { |
1839 | QWindow *w = static_cast<QWindowPrivate *>(QObjectPrivate::get(o: window()))->eventReceiver(); |
1840 | if (!(w->flags() & (Qt::WindowDoesNotAcceptFocus | Qt::BypassWindowManagerHint)) |
1841 | && w->type() != Qt::ToolTip |
1842 | && w->type() != Qt::Popup) { |
1843 | w->requestActivate(); |
1844 | } |
1845 | } |
1846 | |
1847 | updateNetWmUserTime(timestamp); |
1848 | |
1849 | if (m_embedded && !m_trayIconWindow) { |
1850 | if (window() != QGuiApplication::focusWindow()) { |
1851 | const QXcbWindow *container = static_cast<const QXcbWindow *>(parent()); |
1852 | Q_ASSERT(container != nullptr); |
1853 | |
1854 | sendXEmbedMessage(window: container->xcb_window(), message: XEMBED_REQUEST_FOCUS); |
1855 | } |
1856 | } |
1857 | QPoint local(event_x, event_y); |
1858 | QPoint global(root_x, root_y); |
1859 | |
1860 | if (isWheel) { |
1861 | if (!connection()->isAtLeastXI21()) { |
1862 | QPoint angleDelta; |
1863 | if (detail == 4) |
1864 | angleDelta.setY(120); |
1865 | else if (detail == 5) |
1866 | angleDelta.setY(-120); |
1867 | else if (detail == 6) |
1868 | angleDelta.setX(120); |
1869 | else if (detail == 7) |
1870 | angleDelta.setX(-120); |
1871 | if (modifiers & Qt::AltModifier) |
1872 | angleDelta = angleDelta.transposed(); |
1873 | QWindowSystemInterface::handleWheelEvent(window: window(), timestamp, local, global, pixelDelta: QPoint(), angleDelta, mods: modifiers); |
1874 | } |
1875 | return; |
1876 | } |
1877 | |
1878 | connection()->setMousePressWindow(this); |
1879 | |
1880 | handleMouseEvent(time: timestamp, local, global, modifiers, type, source); |
1881 | } |
1882 | |
1883 | void QXcbWindow::handleButtonReleaseEvent(int event_x, int event_y, int root_x, int root_y, |
1884 | int detail, Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, |
1885 | QEvent::Type type, Qt::MouseEventSource source) |
1886 | { |
1887 | QPoint local(event_x, event_y); |
1888 | QPoint global(root_x, root_y); |
1889 | |
1890 | if (detail >= 4 && detail <= 7) { |
1891 | // mouse wheel, handled in handleButtonPressEvent() |
1892 | return; |
1893 | } |
1894 | |
1895 | if (connection()->buttonState() == Qt::NoButton) |
1896 | connection()->setMousePressWindow(nullptr); |
1897 | |
1898 | handleMouseEvent(time: timestamp, local, global, modifiers, type, source); |
1899 | } |
1900 | |
1901 | static inline bool doCheckUnGrabAncestor(QXcbConnection *conn) |
1902 | { |
1903 | /* Checking for XCB_NOTIFY_MODE_GRAB and XCB_NOTIFY_DETAIL_ANCESTOR prevents unwanted |
1904 | * enter/leave events on AwesomeWM on mouse button press. It also ignores duplicated |
1905 | * enter/leave events on Alt+Tab switching on some WMs with XInput2 events. |
1906 | * Without XInput2 events the (Un)grabAncestor cannot be checked when mouse button is |
1907 | * not pressed, otherwise (e.g. on Alt+Tab) it can igonre important enter/leave events. |
1908 | */ |
1909 | if (conn) { |
1910 | const bool mouseButtonsPressed = (conn->buttonState() != Qt::NoButton); |
1911 | return mouseButtonsPressed || conn->hasXInput2(); |
1912 | } |
1913 | return true; |
1914 | } |
1915 | |
1916 | static bool ignoreLeaveEvent(quint8 mode, quint8 detail, QXcbConnection *conn = nullptr) |
1917 | { |
1918 | return ((doCheckUnGrabAncestor(conn) |
1919 | && mode == XCB_NOTIFY_MODE_GRAB && detail == XCB_NOTIFY_DETAIL_ANCESTOR) |
1920 | || (mode == XCB_NOTIFY_MODE_UNGRAB && detail == XCB_NOTIFY_DETAIL_INFERIOR) |
1921 | || detail == XCB_NOTIFY_DETAIL_VIRTUAL |
1922 | || detail == XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL); |
1923 | } |
1924 | |
1925 | static bool ignoreEnterEvent(quint8 mode, quint8 detail, QXcbConnection *conn = nullptr) |
1926 | { |
1927 | return ((doCheckUnGrabAncestor(conn) |
1928 | && mode == XCB_NOTIFY_MODE_UNGRAB && detail == XCB_NOTIFY_DETAIL_ANCESTOR) |
1929 | || (mode != XCB_NOTIFY_MODE_NORMAL && mode != XCB_NOTIFY_MODE_UNGRAB) |
1930 | || detail == XCB_NOTIFY_DETAIL_VIRTUAL |
1931 | || detail == XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL); |
1932 | } |
1933 | |
1934 | void QXcbWindow::handleEnterNotifyEvent(int event_x, int event_y, int root_x, int root_y, |
1935 | quint8 mode, quint8 detail, xcb_timestamp_t timestamp) |
1936 | { |
1937 | connection()->setTime(timestamp); |
1938 | |
1939 | const QPoint global = QPoint(root_x, root_y); |
1940 | |
1941 | if (ignoreEnterEvent(mode, detail, conn: connection()) || connection()->mousePressWindow()) |
1942 | return; |
1943 | |
1944 | // Updates scroll valuators, as user might have done some scrolling outside our X client. |
1945 | connection()->xi2UpdateScrollingDevices(); |
1946 | |
1947 | const QPoint local(event_x, event_y); |
1948 | QWindowSystemInterface::handleEnterEvent(window: window(), local, global); |
1949 | } |
1950 | |
1951 | void QXcbWindow::handleLeaveNotifyEvent(int root_x, int root_y, |
1952 | quint8 mode, quint8 detail, xcb_timestamp_t timestamp) |
1953 | { |
1954 | connection()->setTime(timestamp); |
1955 | |
1956 | if (ignoreLeaveEvent(mode, detail, conn: connection()) || connection()->mousePressWindow()) |
1957 | return; |
1958 | |
1959 | // check if enter event is buffered |
1960 | auto event = connection()->eventQueue()->peek(peeker: [](xcb_generic_event_t *event, int type) { |
1961 | if (type != XCB_ENTER_NOTIFY) |
1962 | return false; |
1963 | auto enter = reinterpret_cast<xcb_enter_notify_event_t *>(event); |
1964 | return !ignoreEnterEvent(mode: enter->mode, detail: enter->detail); |
1965 | }); |
1966 | auto enter = reinterpret_cast<xcb_enter_notify_event_t *>(event); |
1967 | QXcbWindow *enterWindow = enter ? connection()->platformWindowFromId(id: enter->event) : nullptr; |
1968 | |
1969 | if (enterWindow) { |
1970 | QPoint local(enter->event_x, enter->event_y); |
1971 | QPoint global = QPoint(root_x, root_y); |
1972 | QWindowSystemInterface::handleEnterLeaveEvent(enter: enterWindow->window(), leave: window(), local, global); |
1973 | } else { |
1974 | QWindowSystemInterface::handleLeaveEvent(window: window()); |
1975 | } |
1976 | |
1977 | free(ptr: enter); |
1978 | } |
1979 | |
1980 | void QXcbWindow::handleMotionNotifyEvent(int event_x, int event_y, int root_x, int root_y, |
1981 | Qt::KeyboardModifiers modifiers, xcb_timestamp_t timestamp, |
1982 | QEvent::Type type, Qt::MouseEventSource source) |
1983 | { |
1984 | QPoint local(event_x, event_y); |
1985 | QPoint global(root_x, root_y); |
1986 | |
1987 | // "mousePressWindow" can be NULL i.e. if a window will be grabbed or unmapped, so set it again here. |
1988 | // Unset "mousePressWindow" when mouse button isn't pressed - in some cases the release event won't arrive. |
1989 | const bool isMouseButtonPressed = (connection()->buttonState() != Qt::NoButton); |
1990 | const bool hasMousePressWindow = (connection()->mousePressWindow() != nullptr); |
1991 | if (isMouseButtonPressed && !hasMousePressWindow) |
1992 | connection()->setMousePressWindow(this); |
1993 | else if (hasMousePressWindow && !isMouseButtonPressed) |
1994 | connection()->setMousePressWindow(nullptr); |
1995 | |
1996 | handleMouseEvent(time: timestamp, local, global, modifiers, type, source); |
1997 | } |
1998 | |
1999 | void QXcbWindow::handleButtonPressEvent(const xcb_button_press_event_t *event) |
2000 | { |
2001 | Qt::KeyboardModifiers modifiers = connection()->keyboard()->translateModifiers(s: event->state); |
2002 | handleButtonPressEvent(event_x: event->event_x, event_y: event->event_y, root_x: event->root_x, root_y: event->root_y, detail: event->detail, |
2003 | modifiers, timestamp: event->time, type: QEvent::MouseButtonPress); |
2004 | } |
2005 | |
2006 | void QXcbWindow::handleButtonReleaseEvent(const xcb_button_release_event_t *event) |
2007 | { |
2008 | Qt::KeyboardModifiers modifiers = connection()->keyboard()->translateModifiers(s: event->state); |
2009 | handleButtonReleaseEvent(event_x: event->event_x, event_y: event->event_y, root_x: event->root_x, root_y: event->root_y, detail: event->detail, |
2010 | modifiers, timestamp: event->time, type: QEvent::MouseButtonRelease); |
2011 | } |
2012 | |
2013 | void QXcbWindow::handleMotionNotifyEvent(const xcb_motion_notify_event_t *event) |
2014 | { |
2015 | Qt::KeyboardModifiers modifiers = connection()->keyboard()->translateModifiers(s: event->state); |
2016 | handleMotionNotifyEvent(event_x: event->event_x, event_y: event->event_y, root_x: event->root_x, root_y: event->root_y, modifiers, |
2017 | timestamp: event->time, type: QEvent::MouseMove); |
2018 | } |
2019 | |
2020 | static inline int fixed1616ToInt(xcb_input_fp1616_t val) |
2021 | { |
2022 | return int(qreal(val) / 0x10000); |
2023 | } |
2024 | |
2025 | #define qt_xcb_mask_is_set(ptr, event) (((unsigned char*)(ptr))[(event)>>3] & (1 << ((event) & 7))) |
2026 | |
2027 | void QXcbWindow::handleXIMouseEvent(xcb_ge_event_t *event, Qt::MouseEventSource source) |
2028 | { |
2029 | QXcbConnection *conn = connection(); |
2030 | auto *ev = reinterpret_cast<xcb_input_button_press_event_t *>(event); |
2031 | |
2032 | if (ev->buttons_len > 0) { |
2033 | unsigned char *buttonMask = (unsigned char *) &ev[1]; |
2034 | // There is a bug in the evdev driver which leads to receiving mouse events without |
2035 | // XIPointerEmulated being set: https://bugs.freedesktop.org/show_bug.cgi?id=98188 |
2036 | // Filter them out by other attributes: when their source device is a touch screen |
2037 | // and the LMB is pressed. |
2038 | if (qt_xcb_mask_is_set(buttonMask, 1) && conn->isTouchScreen(id: ev->sourceid)) { |
2039 | if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) |
2040 | qCDebug(lcQpaXInput, "XI2 mouse event from touch device %d was ignored" , ev->sourceid); |
2041 | return; |
2042 | } |
2043 | for (int i = 1; i <= 15; ++i) |
2044 | conn->setButtonState(button: conn->translateMouseButton(s: i), qt_xcb_mask_is_set(buttonMask, i)); |
2045 | } |
2046 | |
2047 | const Qt::KeyboardModifiers modifiers = conn->keyboard()->translateModifiers(s: ev->mods.effective); |
2048 | const int event_x = fixed1616ToInt(val: ev->event_x); |
2049 | const int event_y = fixed1616ToInt(val: ev->event_y); |
2050 | const int root_x = fixed1616ToInt(val: ev->root_x); |
2051 | const int root_y = fixed1616ToInt(val: ev->root_y); |
2052 | |
2053 | conn->keyboard()->updateXKBStateFromXI(modInfo: &ev->mods, groupInfo: &ev->group); |
2054 | |
2055 | const Qt::MouseButton button = conn->xiToQtMouseButton(b: ev->detail); |
2056 | |
2057 | const char *sourceName = nullptr; |
2058 | if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) { |
2059 | const QMetaObject *metaObject = qt_getEnumMetaObject(source); |
2060 | const QMetaEnum me = metaObject->enumerator(index: metaObject->indexOfEnumerator(name: qt_getEnumName(source))); |
2061 | sourceName = me.valueToKey(value: source); |
2062 | } |
2063 | |
2064 | switch (ev->event_type) { |
2065 | case XCB_INPUT_BUTTON_PRESS: |
2066 | if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) |
2067 | qCDebug(lcQpaXInputEvents, "XI2 mouse press, button %d, time %d, source %s" , button, ev->time, sourceName); |
2068 | conn->setButtonState(button, down: true); |
2069 | handleButtonPressEvent(event_x, event_y, root_x, root_y, detail: ev->detail, modifiers, timestamp: ev->time, type: QEvent::MouseButtonPress, source); |
2070 | break; |
2071 | case XCB_INPUT_BUTTON_RELEASE: |
2072 | if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) |
2073 | qCDebug(lcQpaXInputEvents, "XI2 mouse release, button %d, time %d, source %s" , button, ev->time, sourceName); |
2074 | conn->setButtonState(button, down: false); |
2075 | handleButtonReleaseEvent(event_x, event_y, root_x, root_y, detail: ev->detail, modifiers, timestamp: ev->time, type: QEvent::MouseButtonRelease, source); |
2076 | break; |
2077 | case XCB_INPUT_MOTION: |
2078 | if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) |
2079 | qCDebug(lcQpaXInputEvents, "XI2 mouse motion %d,%d, time %d, source %s" , event_x, event_y, ev->time, sourceName); |
2080 | handleMotionNotifyEvent(event_x, event_y, root_x, root_y, modifiers, timestamp: ev->time, type: QEvent::MouseMove, source); |
2081 | break; |
2082 | default: |
2083 | qWarning() << "Unrecognized XI2 mouse event" << ev->event_type; |
2084 | break; |
2085 | } |
2086 | } |
2087 | |
2088 | void QXcbWindow::handleXIEnterLeave(xcb_ge_event_t *event) |
2089 | { |
2090 | auto *ev = reinterpret_cast<xcb_input_enter_event_t *>(event); |
2091 | |
2092 | // Compare the window with current mouse grabber to prevent deliver events to any other windows. |
2093 | // If leave event occurs and the window is under mouse - allow to deliver the leave event. |
2094 | QXcbWindow *mouseGrabber = connection()->mouseGrabber(); |
2095 | if (mouseGrabber && mouseGrabber != this |
2096 | && (ev->event_type != XCB_INPUT_LEAVE || QGuiApplicationPrivate::currentMouseWindow != window())) { |
2097 | return; |
2098 | } |
2099 | |
2100 | const int root_x = fixed1616ToInt(val: ev->root_x); |
2101 | const int root_y = fixed1616ToInt(val: ev->root_y); |
2102 | |
2103 | switch (ev->event_type) { |
2104 | case XCB_INPUT_ENTER: { |
2105 | const int event_x = fixed1616ToInt(val: ev->event_x); |
2106 | const int event_y = fixed1616ToInt(val: ev->event_y); |
2107 | qCDebug(lcQpaXInputEvents, "XI2 mouse enter %d,%d, mode %d, detail %d, time %d" , |
2108 | event_x, event_y, ev->mode, ev->detail, ev->time); |
2109 | handleEnterNotifyEvent(event_x, event_y, root_x, root_y, mode: ev->mode, detail: ev->detail, timestamp: ev->time); |
2110 | break; |
2111 | } |
2112 | case XCB_INPUT_LEAVE: |
2113 | qCDebug(lcQpaXInputEvents, "XI2 mouse leave, mode %d, detail %d, time %d" , |
2114 | ev->mode, ev->detail, ev->time); |
2115 | connection()->keyboard()->updateXKBStateFromXI(modInfo: &ev->mods, groupInfo: &ev->group); |
2116 | handleLeaveNotifyEvent(root_x, root_y, mode: ev->mode, detail: ev->detail, timestamp: ev->time); |
2117 | break; |
2118 | } |
2119 | } |
2120 | |
2121 | QXcbWindow *QXcbWindow::toWindow() { return this; } |
2122 | |
2123 | void QXcbWindow::handleMouseEvent(xcb_timestamp_t time, const QPoint &local, const QPoint &global, |
2124 | Qt::KeyboardModifiers modifiers, QEvent::Type type, Qt::MouseEventSource source) |
2125 | { |
2126 | m_lastPointerPosition = local; |
2127 | m_lastPointerGlobalPosition = global; |
2128 | connection()->setTime(time); |
2129 | Qt::MouseButton button = type == QEvent::MouseMove ? Qt::NoButton : connection()->button(); |
2130 | QWindowSystemInterface::handleMouseEvent(window: window(), timestamp: time, local, global, |
2131 | state: connection()->buttonState(), button, |
2132 | type, mods: modifiers, source); |
2133 | } |
2134 | |
2135 | void QXcbWindow::handleEnterNotifyEvent(const xcb_enter_notify_event_t *event) |
2136 | { |
2137 | handleEnterNotifyEvent(event_x: event->event_x, event_y: event->event_y, root_x: event->root_x, root_y: event->root_y, mode: event->mode, detail: event->detail, timestamp: event->time); |
2138 | } |
2139 | |
2140 | void QXcbWindow::handleLeaveNotifyEvent(const xcb_leave_notify_event_t *event) |
2141 | { |
2142 | handleLeaveNotifyEvent(root_x: event->root_x, root_y: event->root_y, mode: event->mode, detail: event->detail, timestamp: event->time); |
2143 | } |
2144 | |
2145 | void QXcbWindow::handlePropertyNotifyEvent(const xcb_property_notify_event_t *event) |
2146 | { |
2147 | connection()->setTime(event->time); |
2148 | |
2149 | const bool propertyDeleted = event->state == XCB_PROPERTY_DELETE; |
2150 | |
2151 | if (event->atom == atom(atom: QXcbAtom::Atom_NET_WM_STATE) || event->atom == atom(atom: QXcbAtom::AtomWM_STATE)) { |
2152 | if (propertyDeleted) |
2153 | return; |
2154 | |
2155 | Qt::WindowStates newState = Qt::WindowNoState; |
2156 | |
2157 | if (event->atom == atom(atom: QXcbAtom::AtomWM_STATE)) { // WM_STATE: Quick check for 'Minimize'. |
2158 | auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), |
2159 | 0, m_window, atom(QXcbAtom::AtomWM_STATE), |
2160 | XCB_ATOM_ANY, 0, 1024); |
2161 | if (reply && reply->format == 32 && reply->type == atom(atom: QXcbAtom::AtomWM_STATE)) { |
2162 | const quint32 *data = (const quint32 *)xcb_get_property_value(R: reply.get()); |
2163 | if (reply->length != 0) |
2164 | m_minimized = (data[0] == XCB_ICCCM_WM_STATE_ICONIC |
2165 | || (data[0] == XCB_ICCCM_WM_STATE_WITHDRAWN && m_minimized)); |
2166 | } |
2167 | } |
2168 | |
2169 | const NetWmStates states = netWmStates(); |
2170 | // _NET_WM_STATE_HIDDEN should be set by the Window Manager to indicate that a window would |
2171 | // not be visible on the screen if its desktop/viewport were active and its coordinates were |
2172 | // within the screen bounds. The canonical example is that minimized windows should be in |
2173 | // the _NET_WM_STATE_HIDDEN state. |
2174 | if (m_minimized && (!connection()->wmSupport()->isSupportedByWM(atom: NetWmStateHidden) |
2175 | || states.testFlag(flag: NetWmStateHidden))) |
2176 | newState = Qt::WindowMinimized; |
2177 | |
2178 | if (states & NetWmStateFullScreen) |
2179 | newState |= Qt::WindowFullScreen; |
2180 | if ((states & NetWmStateMaximizedHorz) && (states & NetWmStateMaximizedVert)) |
2181 | newState |= Qt::WindowMaximized; |
2182 | // Send Window state, compress events in case other flags (modality, etc) are changed. |
2183 | if (m_lastWindowStateEvent != newState) { |
2184 | QWindowSystemInterface::handleWindowStateChanged(window: window(), newState); |
2185 | m_lastWindowStateEvent = newState; |
2186 | m_windowState = newState; |
2187 | if ((m_windowState & Qt::WindowMinimized) && connection()->mouseGrabber() == this) |
2188 | connection()->setMouseGrabber(nullptr); |
2189 | } |
2190 | return; |
2191 | } else if (event->atom == atom(atom: QXcbAtom::Atom_NET_FRAME_EXTENTS)) { |
2192 | m_dirtyFrameMargins = true; |
2193 | } |
2194 | } |
2195 | |
2196 | void QXcbWindow::handleFocusInEvent(const xcb_focus_in_event_t *event) |
2197 | { |
2198 | // Ignore focus events that are being sent only because the pointer is over |
2199 | // our window, even if the input focus is in a different window. |
2200 | if (event->detail == XCB_NOTIFY_DETAIL_POINTER) |
2201 | return; |
2202 | |
2203 | connection()->focusInTimer().stop(); |
2204 | doFocusIn(); |
2205 | } |
2206 | |
2207 | |
2208 | void QXcbWindow::handleFocusOutEvent(const xcb_focus_out_event_t *event) |
2209 | { |
2210 | // Ignore focus events that are being sent only because the pointer is over |
2211 | // our window, even if the input focus is in a different window. |
2212 | if (event->detail == XCB_NOTIFY_DETAIL_POINTER) |
2213 | return; |
2214 | doFocusOut(); |
2215 | } |
2216 | |
2217 | void QXcbWindow::updateSyncRequestCounter() |
2218 | { |
2219 | if (m_syncState != SyncAndConfigureReceived) { |
2220 | // window manager does not expect a sync event yet. |
2221 | return; |
2222 | } |
2223 | if (connection()->hasXSync() && (m_syncValue.lo != 0 || m_syncValue.hi != 0)) { |
2224 | xcb_sync_set_counter(c: xcb_connection(), counter: m_syncCounter, value: m_syncValue); |
2225 | xcb_flush(c: xcb_connection()); |
2226 | |
2227 | m_syncValue.lo = 0; |
2228 | m_syncValue.hi = 0; |
2229 | m_syncState = NoSyncNeeded; |
2230 | } |
2231 | } |
2232 | |
2233 | const xcb_visualtype_t *QXcbWindow::createVisual() |
2234 | { |
2235 | return xcbScreen() ? xcbScreen()->visualForFormat(format: m_format) |
2236 | : nullptr; |
2237 | } |
2238 | |
2239 | bool QXcbWindow::setKeyboardGrabEnabled(bool grab) |
2240 | { |
2241 | if (grab && !connection()->canGrab()) |
2242 | return false; |
2243 | |
2244 | if (!grab) { |
2245 | xcb_ungrab_keyboard(c: xcb_connection(), time: XCB_TIME_CURRENT_TIME); |
2246 | return true; |
2247 | } |
2248 | |
2249 | auto reply = Q_XCB_REPLY(xcb_grab_keyboard, xcb_connection(), false, |
2250 | m_window, XCB_TIME_CURRENT_TIME, |
2251 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); |
2252 | return reply && reply->status == XCB_GRAB_STATUS_SUCCESS; |
2253 | } |
2254 | |
2255 | bool QXcbWindow::setMouseGrabEnabled(bool grab) |
2256 | { |
2257 | if (!grab && connection()->mouseGrabber() == this) |
2258 | connection()->setMouseGrabber(nullptr); |
2259 | |
2260 | if (grab && !connection()->canGrab()) |
2261 | return false; |
2262 | |
2263 | if (connection()->hasXInput2()) { |
2264 | bool result = connection()->xi2SetMouseGrabEnabled(w: m_window, grab); |
2265 | if (grab && result) |
2266 | connection()->setMouseGrabber(this); |
2267 | return result; |
2268 | } |
2269 | |
2270 | if (!grab) { |
2271 | xcb_ungrab_pointer(c: xcb_connection(), time: XCB_TIME_CURRENT_TIME); |
2272 | return true; |
2273 | } |
2274 | |
2275 | auto reply = Q_XCB_REPLY(xcb_grab_pointer, xcb_connection(), |
2276 | false, m_window, |
2277 | (XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
2278 | | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_ENTER_WINDOW |
2279 | | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_POINTER_MOTION), |
2280 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, |
2281 | XCB_WINDOW_NONE, XCB_CURSOR_NONE, |
2282 | XCB_TIME_CURRENT_TIME); |
2283 | bool result = reply && reply->status == XCB_GRAB_STATUS_SUCCESS; |
2284 | if (result) |
2285 | connection()->setMouseGrabber(this); |
2286 | return result; |
2287 | } |
2288 | |
2289 | bool QXcbWindow::windowEvent(QEvent *event) |
2290 | { |
2291 | switch (event->type()) { |
2292 | case QEvent::FocusIn: |
2293 | if (m_embedded && !m_trayIconWindow && !event->spontaneous()) { |
2294 | QFocusEvent *focusEvent = static_cast<QFocusEvent *>(event); |
2295 | switch (focusEvent->reason()) { |
2296 | case Qt::TabFocusReason: |
2297 | case Qt::BacktabFocusReason: |
2298 | { |
2299 | const QXcbWindow *container = |
2300 | static_cast<const QXcbWindow *>(parent()); |
2301 | sendXEmbedMessage(window: container->xcb_window(), |
2302 | message: focusEvent->reason() == Qt::TabFocusReason ? |
2303 | XEMBED_FOCUS_NEXT : XEMBED_FOCUS_PREV); |
2304 | event->accept(); |
2305 | } |
2306 | break; |
2307 | default: |
2308 | break; |
2309 | } |
2310 | } |
2311 | break; |
2312 | default: |
2313 | break; |
2314 | } |
2315 | return QPlatformWindow::windowEvent(event); |
2316 | } |
2317 | |
2318 | bool QXcbWindow::startSystemResize(Qt::Edges edges) |
2319 | { |
2320 | return startSystemMoveResize(pos: m_lastPointerPosition, edges); |
2321 | } |
2322 | |
2323 | bool QXcbWindow::startSystemMove() |
2324 | { |
2325 | return startSystemMoveResize(pos: m_lastPointerPosition, edges: 16); |
2326 | } |
2327 | |
2328 | bool QXcbWindow::startSystemMoveResize(const QPoint &pos, int edges) |
2329 | { |
2330 | const xcb_atom_t moveResize = connection()->atom(qatom: QXcbAtom::Atom_NET_WM_MOVERESIZE); |
2331 | if (!connection()->wmSupport()->isSupportedByWM(atom: moveResize)) |
2332 | return false; |
2333 | |
2334 | // ### FIXME QTBUG-53389 |
2335 | bool startedByTouch = connection()->startSystemMoveResizeForTouch(window: m_window, edges); |
2336 | if (startedByTouch) { |
2337 | const QString wmname = connection()->windowManagerName(); |
2338 | if (wmname != "kwin"_L1 && wmname != "openbox"_L1 ) { |
2339 | qCDebug(lcQpaXInputDevices) << "only KDE and OpenBox support startSystemMove/Resize which is triggered from touch events: XDG_CURRENT_DESKTOP=" |
2340 | << qgetenv(varName: "XDG_CURRENT_DESKTOP" ); |
2341 | connection()->abortSystemMoveResize(window: m_window); |
2342 | return false; |
2343 | } |
2344 | // KWin, Openbox, AwesomeWM and Gnome have been tested to work with _NET_WM_MOVERESIZE. |
2345 | } else { // Started by mouse press. |
2346 | doStartSystemMoveResize(globalPos: mapToGlobal(pos), edges); |
2347 | } |
2348 | |
2349 | return true; |
2350 | } |
2351 | |
2352 | static uint qtEdgesToXcbMoveResizeDirection(Qt::Edges edges) |
2353 | { |
2354 | if (edges == (Qt::TopEdge | Qt::LeftEdge)) |
2355 | return 0; |
2356 | if (edges == Qt::TopEdge) |
2357 | return 1; |
2358 | if (edges == (Qt::TopEdge | Qt::RightEdge)) |
2359 | return 2; |
2360 | if (edges == Qt::RightEdge) |
2361 | return 3; |
2362 | if (edges == (Qt::RightEdge | Qt::BottomEdge)) |
2363 | return 4; |
2364 | if (edges == Qt::BottomEdge) |
2365 | return 5; |
2366 | if (edges == (Qt::BottomEdge | Qt::LeftEdge)) |
2367 | return 6; |
2368 | if (edges == Qt::LeftEdge) |
2369 | return 7; |
2370 | |
2371 | qWarning() << "Cannot convert " << edges << "to _NET_WM_MOVERESIZE direction." ; |
2372 | return 0; |
2373 | } |
2374 | |
2375 | void QXcbWindow::doStartSystemMoveResize(const QPoint &globalPos, int edges) |
2376 | { |
2377 | qCDebug(lcQpaXInputDevices) << "triggered system move or resize via sending _NET_WM_MOVERESIZE client message" ; |
2378 | const xcb_atom_t moveResize = connection()->atom(qatom: QXcbAtom::Atom_NET_WM_MOVERESIZE); |
2379 | xcb_client_message_event_t xev; |
2380 | xev.response_type = XCB_CLIENT_MESSAGE; |
2381 | xev.type = moveResize; |
2382 | xev.sequence = 0; |
2383 | xev.window = xcb_window(); |
2384 | xev.format = 32; |
2385 | xev.data.data32[0] = globalPos.x(); |
2386 | xev.data.data32[1] = globalPos.y(); |
2387 | if (edges == 16) |
2388 | xev.data.data32[2] = 8; // move |
2389 | else |
2390 | xev.data.data32[2] = qtEdgesToXcbMoveResizeDirection(edges: Qt::Edges(edges)); |
2391 | xev.data.data32[3] = XCB_BUTTON_INDEX_1; |
2392 | xev.data.data32[4] = 0; |
2393 | xcb_ungrab_pointer(c: connection()->xcb_connection(), XCB_CURRENT_TIME); |
2394 | xcb_send_event(c: connection()->xcb_connection(), propagate: false, destination: xcbScreen()->root(), |
2395 | event_mask: XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, |
2396 | event: (const char *)&xev); |
2397 | |
2398 | connection()->setDuringSystemMoveResize(true); |
2399 | } |
2400 | |
2401 | // Sends an XEmbed message. |
2402 | void QXcbWindow::sendXEmbedMessage(xcb_window_t window, quint32 message, |
2403 | quint32 detail, quint32 data1, quint32 data2) |
2404 | { |
2405 | xcb_client_message_event_t event; |
2406 | |
2407 | event.response_type = XCB_CLIENT_MESSAGE; |
2408 | event.format = 32; |
2409 | event.sequence = 0; |
2410 | event.window = window; |
2411 | event.type = atom(atom: QXcbAtom::Atom_XEMBED); |
2412 | event.data.data32[0] = connection()->time(); |
2413 | event.data.data32[1] = message; |
2414 | event.data.data32[2] = detail; |
2415 | event.data.data32[3] = data1; |
2416 | event.data.data32[4] = data2; |
2417 | xcb_send_event(c: xcb_connection(), propagate: false, destination: window, event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&event); |
2418 | } |
2419 | |
2420 | static bool activeWindowChangeQueued(const QWindow *window) |
2421 | { |
2422 | /* Check from window system event queue if the next queued activation |
2423 | * targets a window other than @window. |
2424 | */ |
2425 | QWindowSystemInterfacePrivate::ActivatedWindowEvent *systemEvent = |
2426 | static_cast<QWindowSystemInterfacePrivate::ActivatedWindowEvent *> |
2427 | (QWindowSystemInterfacePrivate::peekWindowSystemEvent(t: QWindowSystemInterfacePrivate::ActivatedWindow)); |
2428 | return systemEvent && systemEvent->activated != window; |
2429 | } |
2430 | |
2431 | void QXcbWindow::handleXEmbedMessage(const xcb_client_message_event_t *event) |
2432 | { |
2433 | connection()->setTime(event->data.data32[0]); |
2434 | switch (event->data.data32[1]) { |
2435 | case XEMBED_WINDOW_ACTIVATE: |
2436 | case XEMBED_WINDOW_DEACTIVATE: |
2437 | break; |
2438 | case XEMBED_EMBEDDED_NOTIFY: |
2439 | xcb_map_window(c: xcb_connection(), window: m_window); |
2440 | xcbScreen()->windowShown(window: this); |
2441 | break; |
2442 | case XEMBED_FOCUS_IN: |
2443 | connection()->focusInTimer().stop(); |
2444 | Qt::FocusReason reason; |
2445 | switch (event->data.data32[2]) { |
2446 | case XEMBED_FOCUS_FIRST: |
2447 | reason = Qt::TabFocusReason; |
2448 | break; |
2449 | case XEMBED_FOCUS_LAST: |
2450 | reason = Qt::BacktabFocusReason; |
2451 | break; |
2452 | case XEMBED_FOCUS_CURRENT: |
2453 | default: |
2454 | reason = Qt::OtherFocusReason; |
2455 | break; |
2456 | } |
2457 | connection()->setFocusWindow(window()); |
2458 | QWindowSystemInterface::handleWindowActivated(window: window(), r: reason); |
2459 | break; |
2460 | case XEMBED_FOCUS_OUT: |
2461 | if (window() == QGuiApplication::focusWindow() |
2462 | && !activeWindowChangeQueued(window: window())) { |
2463 | connection()->setFocusWindow(nullptr); |
2464 | QWindowSystemInterface::handleWindowActivated(window: nullptr); |
2465 | } |
2466 | break; |
2467 | } |
2468 | } |
2469 | |
2470 | static inline xcb_rectangle_t qRectToXCBRectangle(const QRect &r) |
2471 | { |
2472 | xcb_rectangle_t result; |
2473 | result.x = qMax(SHRT_MIN, b: r.x()); |
2474 | result.y = qMax(SHRT_MIN, b: r.y()); |
2475 | result.width = qMin(a: (int)USHRT_MAX, b: r.width()); |
2476 | result.height = qMin(a: (int)USHRT_MAX, b: r.height()); |
2477 | return result; |
2478 | } |
2479 | |
2480 | void QXcbWindow::setOpacity(qreal level) |
2481 | { |
2482 | if (!m_window) |
2483 | return; |
2484 | |
2485 | quint32 value = qRound64(d: qBound(min: qreal(0), val: level, max: qreal(1)) * 0xffffffff); |
2486 | |
2487 | xcb_change_property(c: xcb_connection(), |
2488 | mode: XCB_PROP_MODE_REPLACE, |
2489 | window: m_window, |
2490 | property: atom(atom: QXcbAtom::Atom_NET_WM_WINDOW_OPACITY), |
2491 | type: XCB_ATOM_CARDINAL, |
2492 | format: 32, |
2493 | data_len: 1, |
2494 | data: (uchar *)&value); |
2495 | } |
2496 | |
2497 | QList<xcb_rectangle_t> qRegionToXcbRectangleList(const QRegion ®ion) |
2498 | { |
2499 | QList<xcb_rectangle_t> rects; |
2500 | rects.reserve(asize: region.rectCount()); |
2501 | for (const QRect &r : region) |
2502 | rects.push_back(t: qRectToXCBRectangle(r)); |
2503 | return rects; |
2504 | } |
2505 | |
2506 | void QXcbWindow::setMask(const QRegion ®ion) |
2507 | { |
2508 | if (!connection()->hasXShape()) |
2509 | return; |
2510 | if (region.isEmpty()) { |
2511 | xcb_shape_mask(c: connection()->xcb_connection(), operation: XCB_SHAPE_SO_SET, |
2512 | destination_kind: XCB_SHAPE_SK_BOUNDING, destination_window: xcb_window(), x_offset: 0, y_offset: 0, XCB_NONE); |
2513 | } else { |
2514 | const auto rects = qRegionToXcbRectangleList(region); |
2515 | xcb_shape_rectangles(c: connection()->xcb_connection(), operation: XCB_SHAPE_SO_SET, |
2516 | destination_kind: XCB_SHAPE_SK_BOUNDING, ordering: XCB_CLIP_ORDERING_UNSORTED, |
2517 | destination_window: xcb_window(), x_offset: 0, y_offset: 0, rectangles_len: rects.size(), rectangles: &rects[0]); |
2518 | } |
2519 | } |
2520 | |
2521 | void QXcbWindow::setAlertState(bool enabled) |
2522 | { |
2523 | if (m_alertState == enabled) |
2524 | return; |
2525 | |
2526 | m_alertState = enabled; |
2527 | |
2528 | setNetWmState(set: enabled, one: atom(atom: QXcbAtom::Atom_NET_WM_STATE_DEMANDS_ATTENTION)); |
2529 | } |
2530 | |
2531 | uint QXcbWindow::visualId() const |
2532 | { |
2533 | return m_visualId; |
2534 | } |
2535 | |
2536 | bool QXcbWindow::needsSync() const |
2537 | { |
2538 | return m_syncState == SyncAndConfigureReceived; |
2539 | } |
2540 | |
2541 | void QXcbWindow::postSyncWindowRequest() |
2542 | { |
2543 | if (!m_pendingSyncRequest) { |
2544 | QXcbSyncWindowRequest *e = new QXcbSyncWindowRequest(this); |
2545 | m_pendingSyncRequest = e; |
2546 | QCoreApplication::postEvent(receiver: xcbScreen()->connection(), event: e); |
2547 | } |
2548 | } |
2549 | |
2550 | QXcbScreen *QXcbWindow::xcbScreen() const |
2551 | { |
2552 | return static_cast<QXcbScreen *>(screen()); |
2553 | } |
2554 | |
2555 | void QXcbWindow::setWindowTitle(const QXcbConnection *conn, xcb_window_t window, const QString &title) |
2556 | { |
2557 | QString fullTitle = formatWindowTitle(title, separator: QString::fromUtf8(utf8: " \xe2\x80\x94 " )); // unicode character U+2014, EM DASH |
2558 | const QByteArray ba = std::move(fullTitle).toUtf8(); |
2559 | xcb_change_property(c: conn->xcb_connection(), |
2560 | mode: XCB_PROP_MODE_REPLACE, |
2561 | window, |
2562 | property: conn->atom(qatom: QXcbAtom::Atom_NET_WM_NAME), |
2563 | type: conn->atom(qatom: QXcbAtom::AtomUTF8_STRING), |
2564 | format: 8, |
2565 | data_len: ba.size(), |
2566 | data: ba.constData()); |
2567 | |
2568 | #if QT_CONFIG(xcb_xlib) |
2569 | Display *dpy = static_cast<Display *>(conn->xlib_display()); |
2570 | XTextProperty *text = qstringToXTP(dpy, s: title); |
2571 | if (text) |
2572 | XSetWMName(dpy, window, text); |
2573 | #endif |
2574 | xcb_flush(c: conn->xcb_connection()); |
2575 | } |
2576 | |
2577 | QString QXcbWindow::windowTitle(const QXcbConnection *conn, xcb_window_t window) |
2578 | { |
2579 | const xcb_atom_t utf8Atom = conn->atom(qatom: QXcbAtom::AtomUTF8_STRING); |
2580 | auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, conn->xcb_connection(), |
2581 | false, window, conn->atom(QXcbAtom::Atom_NET_WM_NAME), |
2582 | utf8Atom, 0, 1024); |
2583 | if (reply && reply->format == 8 && reply->type == utf8Atom) { |
2584 | const char *name = reinterpret_cast<const char *>(xcb_get_property_value(R: reply.get())); |
2585 | return QString::fromUtf8(utf8: name, size: xcb_get_property_value_length(R: reply.get())); |
2586 | } |
2587 | |
2588 | reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, conn->xcb_connection(), |
2589 | false, window, conn->atom(QXcbAtom::AtomWM_NAME), |
2590 | XCB_ATOM_STRING, 0, 1024); |
2591 | if (reply && reply->format == 8 && reply->type == XCB_ATOM_STRING) { |
2592 | const char *name = reinterpret_cast<const char *>(xcb_get_property_value(R: reply.get())); |
2593 | return QString::fromLatin1(str: name, size: xcb_get_property_value_length(R: reply.get())); |
2594 | } |
2595 | |
2596 | return QString(); |
2597 | } |
2598 | |
2599 | QT_END_NAMESPACE |
2600 | |
2601 | |