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