1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qxcbscreen.h"
5#include "qxcbwindow.h"
6#include "qxcbcursor.h"
7#include "qxcbimage.h"
8#include "qnamespace.h"
9#include "qxcbxsettings.h"
10
11#include <stdio.h>
12
13#include <QDebug>
14#include <QtAlgorithms>
15
16#include <qpa/qwindowsysteminterface.h>
17#include <private/qmath_p.h>
18#include <QtGui/private/qhighdpiscaling_p.h>
19
20QT_BEGIN_NAMESPACE
21
22QXcbVirtualDesktop::QXcbVirtualDesktop(QXcbConnection *connection, xcb_screen_t *screen, int number)
23 : QXcbObject(connection)
24 , m_screen(screen)
25 , m_number(number)
26 , m_xSettings(new QXcbXSettings(this))
27{
28 const QByteArray cmAtomName = "_NET_WM_CM_S" + QByteArray::number(m_number);
29 m_net_wm_cm_atom = connection->internAtom(name: cmAtomName.constData());
30 m_compositingActive = connection->selectionOwner(atom: m_net_wm_cm_atom);
31
32 m_workArea = getWorkArea();
33
34 readXResources();
35
36 auto rootAttribs = Q_XCB_REPLY_UNCHECKED(xcb_get_window_attributes, xcb_connection(),
37 screen->root);
38 const quint32 existingEventMask = !rootAttribs ? 0 : rootAttribs->your_event_mask;
39
40 const quint32 mask = XCB_CW_EVENT_MASK;
41 const quint32 values[] = {
42 // XCB_CW_EVENT_MASK
43 XCB_EVENT_MASK_ENTER_WINDOW
44 | XCB_EVENT_MASK_LEAVE_WINDOW
45 | XCB_EVENT_MASK_PROPERTY_CHANGE
46 | XCB_EVENT_MASK_STRUCTURE_NOTIFY // for the "MANAGER" atom (system tray notification).
47 | existingEventMask // don't overwrite the event mask on the root window
48 };
49
50 xcb_change_window_attributes(c: xcb_connection(), window: screen->root, value_mask: mask, value_list: values);
51
52 auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(),
53 false, screen->root,
54 atom(QXcbAtom::Atom_NET_SUPPORTING_WM_CHECK),
55 XCB_ATOM_WINDOW, 0, 1024);
56 if (reply && reply->format == 32 && reply->type == XCB_ATOM_WINDOW) {
57 xcb_window_t windowManager = *((xcb_window_t *)xcb_get_property_value(R: reply.get()));
58
59 if (windowManager != XCB_WINDOW_NONE)
60 m_windowManagerName = QXcbWindow::windowTitle(conn: connection, window: windowManager);
61 }
62
63 xcb_depth_iterator_t depth_iterator =
64 xcb_screen_allowed_depths_iterator(R: screen);
65
66 while (depth_iterator.rem) {
67 xcb_depth_t *depth = depth_iterator.data;
68 xcb_visualtype_iterator_t visualtype_iterator =
69 xcb_depth_visuals_iterator(R: depth);
70
71 while (visualtype_iterator.rem) {
72 xcb_visualtype_t *visualtype = visualtype_iterator.data;
73 m_visuals.insert(key: visualtype->visual_id, value: *visualtype);
74 m_visualDepths.insert(key: visualtype->visual_id, value: depth->depth);
75 xcb_visualtype_next(i: &visualtype_iterator);
76 }
77
78 xcb_depth_next(i: &depth_iterator);
79 }
80
81 auto dpiChangedCallback = [](QXcbVirtualDesktop *desktop, const QByteArray &, const QVariant &property, void *) {
82 if (!desktop->setDpiFromXSettings(property))
83 return;
84 const auto dpi = desktop->forcedDpi();
85 for (QXcbScreen *screen : desktop->connection()->screens())
86 QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen: screen->QPlatformScreen::screen(), newDpiX: dpi, newDpiY: dpi);
87 };
88 setDpiFromXSettings(xSettings()->setting("Xft/DPI"));
89 xSettings()->registerCallbackForProperty(property: "Xft/DPI", func: dpiChangedCallback, handle: nullptr);
90}
91
92QXcbVirtualDesktop::~QXcbVirtualDesktop()
93{
94 delete m_xSettings;
95
96 for (auto cmap : std::as_const(t&: m_visualColormaps))
97 xcb_free_colormap(c: xcb_connection(), cmap);
98}
99
100QDpi QXcbVirtualDesktop::dpi() const
101{
102 const QSize virtualSize = size();
103 const QSize virtualSizeMillimeters = physicalSize();
104
105 return QDpi(Q_MM_PER_INCH * virtualSize.width() / virtualSizeMillimeters.width(),
106 Q_MM_PER_INCH * virtualSize.height() / virtualSizeMillimeters.height());
107}
108
109QXcbScreen *QXcbVirtualDesktop::screenAt(const QPoint &pos) const
110{
111 const auto screens = connection()->screens();
112 for (QXcbScreen *screen : screens) {
113 if (screen->virtualDesktop() == this && screen->geometry().contains(p: pos))
114 return screen;
115 }
116 return nullptr;
117}
118
119void QXcbVirtualDesktop::addScreen(QPlatformScreen *s)
120{
121 ((QXcbScreen *) s)->isPrimary() ? m_screens.prepend(t: s) : m_screens.append(t: s);
122}
123
124void QXcbVirtualDesktop::setPrimaryScreen(QPlatformScreen *s)
125{
126 const int idx = m_screens.indexOf(t: s);
127 Q_ASSERT(idx > -1);
128 m_screens.swapItemsAt(i: 0, j: idx);
129}
130
131QXcbXSettings *QXcbVirtualDesktop::xSettings() const
132{
133 return m_xSettings;
134}
135
136bool QXcbVirtualDesktop::compositingActive() const
137{
138 if (connection()->hasXFixes())
139 return m_compositingActive;
140 else
141 return connection()->selectionOwner(atom: m_net_wm_cm_atom);
142}
143
144void QXcbVirtualDesktop::handleXFixesSelectionNotify(xcb_xfixes_selection_notify_event_t *notify_event)
145{
146 if (notify_event->selection == m_net_wm_cm_atom)
147 m_compositingActive = notify_event->owner;
148}
149
150void QXcbVirtualDesktop::subscribeToXFixesSelectionNotify()
151{
152 if (connection()->hasXFixes()) {
153 const uint32_t mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER |
154 XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY |
155 XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE;
156 xcb_xfixes_select_selection_input_checked(c: xcb_connection(), window: connection()->qtSelectionOwner(), selection: m_net_wm_cm_atom, event_mask: mask);
157 }
158}
159
160/*!
161 \brief handle the XCB screen change event and update properties
162
163 On a mobile device, the ideal use case is that the accelerometer would
164 drive the orientation. This could be achieved by using QSensors to read the
165 accelerometer and adjusting the rotation in QML, or by reading the
166 orientation from the QScreen object and doing the same, or in many other
167 ways. However, on X we have the XRandR extension, which makes it possible
168 to have the whole screen rotated, so that individual apps DO NOT have to
169 rotate themselves. Apps could optionally use the
170 QScreen::primaryOrientation property to optimize layout though.
171 Furthermore, there is no support in X for accelerometer events anyway. So
172 it makes more sense on a Linux system running X to just run a daemon which
173 monitors the accelerometer and runs xrandr automatically to do the rotation,
174 then apps do not have to be aware of it (but probably the window manager
175 would resize them accordingly). updateGeometry() is written with this
176 design in mind. Therefore the physical geometry, available geometry,
177 virtual geometry, orientation and primaryOrientation should all change at
178 the same time. On a system which cannot rotate the whole screen, it would
179 be correct for only the orientation (not the primary orientation) to
180 change.
181*/
182void QXcbVirtualDesktop::handleScreenChange(xcb_randr_screen_change_notify_event_t *change_event)
183{
184 // No need to do anything when screen rotation did not change - if any
185 // xcb output geometry has changed, we will get RRCrtcChangeNotify and
186 // RROutputChangeNotify events next
187 if (change_event->rotation == m_rotation)
188 return;
189
190 m_rotation = change_event->rotation;
191 switch (m_rotation) {
192 case XCB_RANDR_ROTATION_ROTATE_0: // xrandr --rotate normal
193 m_screen->width_in_pixels = change_event->width;
194 m_screen->height_in_pixels = change_event->height;
195 m_screen->width_in_millimeters = change_event->mwidth;
196 m_screen->height_in_millimeters = change_event->mheight;
197 break;
198 case XCB_RANDR_ROTATION_ROTATE_90: // xrandr --rotate left
199 m_screen->width_in_pixels = change_event->height;
200 m_screen->height_in_pixels = change_event->width;
201 m_screen->width_in_millimeters = change_event->mheight;
202 m_screen->height_in_millimeters = change_event->mwidth;
203 break;
204 case XCB_RANDR_ROTATION_ROTATE_180: // xrandr --rotate inverted
205 m_screen->width_in_pixels = change_event->width;
206 m_screen->height_in_pixels = change_event->height;
207 m_screen->width_in_millimeters = change_event->mwidth;
208 m_screen->height_in_millimeters = change_event->mheight;
209 break;
210 case XCB_RANDR_ROTATION_ROTATE_270: // xrandr --rotate right
211 m_screen->width_in_pixels = change_event->height;
212 m_screen->height_in_pixels = change_event->width;
213 m_screen->width_in_millimeters = change_event->mheight;
214 m_screen->height_in_millimeters = change_event->mwidth;
215 break;
216 // We don't need to do anything with these, since QScreen doesn't store reflection state,
217 // and Qt-based applications probably don't need to care about it anyway.
218 case XCB_RANDR_ROTATION_REFLECT_X: break;
219 case XCB_RANDR_ROTATION_REFLECT_Y: break;
220 }
221
222 for (QPlatformScreen *platformScreen : std::as_const(t&: m_screens)) {
223 QDpi ldpi = platformScreen->logicalDpi();
224 QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen: platformScreen->screen(), newDpiX: ldpi.first, newDpiY: ldpi.second);
225 }
226}
227
228/*! \internal
229
230 Using _NET_WORKAREA to calculate the available desktop geometry on multi-head systems (systems
231 with more than one monitor) is unreliable. Different WMs have different interpretations of what
232 _NET_WORKAREA means with multiple attached monitors. This gets worse when monitors have
233 different dimensions and/or screens are not virtually aligned. In Qt we want the available
234 geometry per monitor (QScreen), not desktop (represented by _NET_WORKAREA). WM specification
235 does not have an atom for this. Thus, QScreen is limited by the lack of support from the
236 underlying system.
237
238 One option could be that Qt does WM's job of calculating this by subtracting geometries of
239 _NET_WM_STRUT_PARTIAL and windows where _NET_WM_WINDOW_TYPE(ATOM) = _NET_WM_WINDOW_TYPE_DOCK.
240 But this won't work on Gnome 3 shell as it seems that on this desktop environment the tool panel
241 is painted directly on the root window. Maybe there is some Gnome/GTK API that could be used
242 to get height of the panel, but I did not find one. Maybe other WMs have their own tricks, so
243 the reliability of this approach is questionable.
244 */
245QRect QXcbVirtualDesktop::getWorkArea() const
246{
247 QRect r;
248 auto workArea = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(), false, screen()->root,
249 atom(QXcbAtom::Atom_NET_WORKAREA),
250 XCB_ATOM_CARDINAL, 0, 1024);
251 if (workArea && workArea->type == XCB_ATOM_CARDINAL && workArea->format == 32 && workArea->value_len >= 4) {
252 // If workArea->value_len > 4, the remaining ones seem to be for WM's virtual desktops
253 // (don't mess with QXcbVirtualDesktop which represents an X screen).
254 // But QScreen doesn't know about that concept. In reality there could be a
255 // "docked" panel (with _NET_WM_STRUT_PARTIAL atom set) on just one desktop.
256 // But for now just assume the first 4 values give us the geometry of the
257 // "work area", AKA "available geometry"
258 uint32_t *geom = (uint32_t*)xcb_get_property_value(R: workArea.get());
259 r = QRect(geom[0], geom[1], geom[2], geom[3]);
260 } else {
261 r.setWidth(-1);
262 }
263 return r;
264}
265
266void QXcbVirtualDesktop::updateWorkArea()
267{
268 QRect workArea = getWorkArea();
269 if (m_workArea != workArea) {
270 m_workArea = workArea;
271 for (QPlatformScreen *screen : std::as_const(t&: m_screens))
272 ((QXcbScreen *)screen)->updateAvailableGeometry();
273 }
274}
275
276QRect QXcbVirtualDesktop::availableGeometry(const QRect &screenGeometry) const
277{
278 return m_workArea.width() >= 0 ? screenGeometry & m_workArea : screenGeometry;
279}
280
281static inline QSizeF sizeInMillimeters(const QSize &size, const QDpi &dpi)
282{
283 return QSizeF(Q_MM_PER_INCH * size.width() / dpi.first,
284 Q_MM_PER_INCH * size.height() / dpi.second);
285}
286
287bool QXcbVirtualDesktop::xResource(const QByteArray &identifier,
288 const QByteArray &expectedIdentifier,
289 QByteArray& stringValue)
290{
291 if (identifier.startsWith(bv: expectedIdentifier)) {
292 stringValue = identifier.mid(index: expectedIdentifier.size());
293 return true;
294 }
295 return false;
296}
297
298static bool parseXftInt(const QByteArray& stringValue, int *value)
299{
300 Q_ASSERT(value);
301 bool ok;
302 *value = stringValue.toInt(ok: &ok);
303 return ok;
304}
305
306static bool parseXftDpi(const QByteArray& stringValue, int *value)
307{
308 Q_ASSERT(value);
309 bool ok = parseXftInt(stringValue, value);
310 // Support GNOME 3 bug that wrote DPI with fraction:
311 if (!ok)
312 *value = qRound(d: stringValue.toDouble(ok: &ok));
313 return ok;
314}
315
316static QFontEngine::HintStyle parseXftHintStyle(const QByteArray& stringValue)
317{
318 if (stringValue == "hintfull")
319 return QFontEngine::HintFull;
320 else if (stringValue == "hintnone")
321 return QFontEngine::HintNone;
322 else if (stringValue == "hintmedium")
323 return QFontEngine::HintMedium;
324 else if (stringValue == "hintslight")
325 return QFontEngine::HintLight;
326
327 return QFontEngine::HintStyle(-1);
328}
329
330static QFontEngine::SubpixelAntialiasingType parseXftRgba(const QByteArray& stringValue)
331{
332 if (stringValue == "none")
333 return QFontEngine::Subpixel_None;
334 else if (stringValue == "rgb")
335 return QFontEngine::Subpixel_RGB;
336 else if (stringValue == "bgr")
337 return QFontEngine::Subpixel_BGR;
338 else if (stringValue == "vrgb")
339 return QFontEngine::Subpixel_VRGB;
340 else if (stringValue == "vbgr")
341 return QFontEngine::Subpixel_VBGR;
342
343 return QFontEngine::SubpixelAntialiasingType(-1);
344}
345
346void QXcbVirtualDesktop::readXResources()
347{
348 int offset = 0;
349 QByteArray resources;
350 while (true) {
351 auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(),
352 false, screen()->root,
353 XCB_ATOM_RESOURCE_MANAGER,
354 XCB_ATOM_STRING, offset/4, 8192);
355 bool more = false;
356 if (reply && reply->format == 8 && reply->type == XCB_ATOM_STRING) {
357 resources += QByteArray((const char *)xcb_get_property_value(R: reply.get()), xcb_get_property_value_length(R: reply.get()));
358 offset += xcb_get_property_value_length(R: reply.get());
359 more = reply->bytes_after != 0;
360 }
361
362 if (!more)
363 break;
364 }
365
366 QList<QByteArray> split = resources.split(sep: '\n');
367 for (int i = 0; i < split.size(); ++i) {
368 const QByteArray &r = split.at(i);
369 int value;
370 QByteArray stringValue;
371 if (xResource(identifier: r, expectedIdentifier: "Xft.dpi:\t", stringValue)) {
372 if (parseXftDpi(stringValue, value: &value))
373 m_forcedDpi = value;
374 } else if (xResource(identifier: r, expectedIdentifier: "Xft.hintstyle:\t", stringValue)) {
375 m_hintStyle = parseXftHintStyle(stringValue);
376 } else if (xResource(identifier: r, expectedIdentifier: "Xft.antialias:\t", stringValue)) {
377 if (parseXftInt(stringValue, value: &value))
378 m_antialiasingEnabled = value;
379 } else if (xResource(identifier: r, expectedIdentifier: "Xft.rgba:\t", stringValue)) {
380 m_subpixelType = parseXftRgba(stringValue);
381 }
382 }
383}
384
385bool QXcbVirtualDesktop::setDpiFromXSettings(const QVariant &property)
386{
387 bool ok;
388 int dpiTimes1k = property.toInt(ok: &ok);
389 if (!ok)
390 return false;
391 int dpi = dpiTimes1k / 1024;
392 if (m_forcedDpi == dpi)
393 return false;
394 m_forcedDpi = dpi;
395 return true;
396}
397
398QSurfaceFormat QXcbVirtualDesktop::surfaceFormatFor(const QSurfaceFormat &format) const
399{
400 const xcb_visualid_t xcb_visualid = connection()->hasDefaultVisualId() ? connection()->defaultVisualId()
401 : screen()->root_visual;
402 const xcb_visualtype_t *xcb_visualtype = visualForId(xcb_visualid);
403
404 const int redSize = qPopulationCount(v: xcb_visualtype->red_mask);
405 const int greenSize = qPopulationCount(v: xcb_visualtype->green_mask);
406 const int blueSize = qPopulationCount(v: xcb_visualtype->blue_mask);
407
408 QSurfaceFormat result = format;
409
410 if (result.redBufferSize() < 0)
411 result.setRedBufferSize(redSize);
412
413 if (result.greenBufferSize() < 0)
414 result.setGreenBufferSize(greenSize);
415
416 if (result.blueBufferSize() < 0)
417 result.setBlueBufferSize(blueSize);
418
419 return result;
420}
421
422const xcb_visualtype_t *QXcbVirtualDesktop::visualForFormat(const QSurfaceFormat &format) const
423{
424 const xcb_visualtype_t *candidate = nullptr;
425
426 for (const xcb_visualtype_t &xcb_visualtype : m_visuals) {
427
428 const int redSize = qPopulationCount(v: xcb_visualtype.red_mask);
429 const int greenSize = qPopulationCount(v: xcb_visualtype.green_mask);
430 const int blueSize = qPopulationCount(v: xcb_visualtype.blue_mask);
431 const int alphaSize = depthOfVisual(xcb_visualtype.visual_id) - redSize - greenSize - blueSize;
432
433 if (format.redBufferSize() != -1 && redSize != format.redBufferSize())
434 continue;
435
436 if (format.greenBufferSize() != -1 && greenSize != format.greenBufferSize())
437 continue;
438
439 if (format.blueBufferSize() != -1 && blueSize != format.blueBufferSize())
440 continue;
441
442 if (format.alphaBufferSize() != -1 && alphaSize != format.alphaBufferSize())
443 continue;
444
445 // Try to find a RGB visual rather than e.g. BGR or GBR
446 if (qCountTrailingZeroBits(v: xcb_visualtype.blue_mask) == 0)
447 return &xcb_visualtype;
448
449 // In case we do not find anything we like, just remember the first one
450 // and hope for the best:
451 if (!candidate)
452 candidate = &xcb_visualtype;
453 }
454
455 return candidate;
456}
457
458const xcb_visualtype_t *QXcbVirtualDesktop::visualForId(xcb_visualid_t visualid) const
459{
460 QMap<xcb_visualid_t, xcb_visualtype_t>::const_iterator it = m_visuals.find(key: visualid);
461 if (it == m_visuals.constEnd())
462 return nullptr;
463 return &*it;
464}
465
466quint8 QXcbVirtualDesktop::depthOfVisual(xcb_visualid_t visualid) const
467{
468 QMap<xcb_visualid_t, quint8>::const_iterator it = m_visualDepths.find(key: visualid);
469 if (it == m_visualDepths.constEnd())
470 return 0;
471 return *it;
472}
473
474xcb_colormap_t QXcbVirtualDesktop::colormapForVisual(xcb_visualid_t visualid) const
475{
476 auto it = m_visualColormaps.constFind(key: visualid);
477 if (it != m_visualColormaps.constEnd())
478 return *it;
479
480 auto cmap = xcb_generate_id(c: xcb_connection());
481 xcb_create_colormap(c: xcb_connection(),
482 alloc: XCB_COLORMAP_ALLOC_NONE,
483 mid: cmap,
484 window: screen()->root,
485 visual: visualid);
486 m_visualColormaps.insert(key: visualid, value: cmap);
487 return cmap;
488}
489
490QXcbScreen::QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDesktop,
491 xcb_randr_output_t outputId, xcb_randr_get_output_info_reply_t *output)
492 : QXcbObject(connection)
493 , m_virtualDesktop(virtualDesktop)
494 , m_monitor(nullptr)
495 , m_output(outputId)
496 , m_crtc(output ? output->crtc : XCB_NONE)
497 , m_outputName(getOutputName(outputInfo: output))
498 , m_outputSizeMillimeters(output ? QSize(output->mm_width, output->mm_height) : QSize())
499 , m_cursor(std::make_unique<QXcbCursor>(args&: connection, args: this))
500{
501 if (connection->isAtLeastXRandR12()) {
502 auto crtc = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_crtc_info, xcb_connection(),
503 m_crtc, output ? output->timestamp : 0);
504 if (crtc) {
505 updateGeometry(geometry: QRect(crtc->x, crtc->y, crtc->width, crtc->height), rotation: crtc->rotation);
506 updateRefreshRate(mode: crtc->mode);
507 }
508 }
509
510 if (m_geometry.isEmpty())
511 m_geometry = QRect(QPoint(), virtualDesktop->size());
512
513 if (m_availableGeometry.isEmpty())
514 m_availableGeometry = m_virtualDesktop->availableGeometry(screenGeometry: m_geometry);
515
516 if (m_sizeMillimeters.isEmpty())
517 m_sizeMillimeters = virtualDesktop->physicalSize();
518
519 updateColorSpaceAndEdid();
520}
521
522void QXcbScreen::updateColorSpaceAndEdid()
523{
524 {
525 // Read colord ICC data (from GNOME settings)
526 auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(),
527 false, screen()->root,
528 connection()->atom(QXcbAtom::Atom_ICC_PROFILE),
529 XCB_ATOM_CARDINAL, 0, 8192);
530 if (reply->format == 8 && reply->type == XCB_ATOM_CARDINAL) {
531 QByteArray data(reinterpret_cast<const char *>(xcb_get_property_value(R: reply.get())), reply->value_len);
532 m_colorSpace = QColorSpace::fromIccProfile(iccProfile: data);
533 }
534 }
535 if (connection()->isAtLeastXRandR12()) { // Parse EDID
536 QByteArray edid = getEdid();
537 if (m_edid.parse(blob: edid)) {
538 qCDebug(lcQpaScreen, "EDID data for output \"%s\": identifier '%s', manufacturer '%s',"
539 "model '%s', serial '%s', physical size: %.2fx%.2f",
540 name().toLatin1().constData(),
541 m_edid.identifier.toLatin1().constData(),
542 m_edid.manufacturer.toLatin1().constData(),
543 m_edid.model.toLatin1().constData(),
544 m_edid.serialNumber.toLatin1().constData(),
545 m_edid.physicalSize.width(), m_edid.physicalSize.height());
546 if (!m_colorSpace.isValid()) {
547 if (m_edid.sRgb)
548 m_colorSpace = QColorSpace::SRgb;
549 else {
550 if (!m_edid.useTables) {
551 m_colorSpace = QColorSpace(m_edid.whiteChromaticity, m_edid.redChromaticity,
552 m_edid.greenChromaticity, m_edid.blueChromaticity,
553 QColorSpace::TransferFunction::Gamma, m_edid.gamma);
554 } else {
555 if (m_edid.tables.size() == 1) {
556 m_colorSpace = QColorSpace(m_edid.whiteChromaticity, m_edid.redChromaticity,
557 m_edid.greenChromaticity, m_edid.blueChromaticity,
558 m_edid.tables[0]);
559 } else if (m_edid.tables.size() == 3) {
560 m_colorSpace = QColorSpace(m_edid.whiteChromaticity, m_edid.redChromaticity,
561 m_edid.greenChromaticity, m_edid.blueChromaticity,
562 m_edid.tables[0], m_edid.tables[1], m_edid.tables[2]);
563 }
564 }
565 }
566 }
567 } else {
568 // This property is defined by the xrandr spec. Parsing failure indicates a valid error,
569 // but keep this as debug, for details see 4f515815efc318ddc909a0399b71b8a684962f38.
570 qCDebug(lcQpaScreen) << "Failed to parse EDID data for output" << name() <<
571 "edid data: " << edid;
572 }
573 }
574 if (!m_colorSpace.isValid())
575 m_colorSpace = QColorSpace::SRgb;
576}
577
578QXcbScreen::QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDesktop,
579 xcb_randr_monitor_info_t *monitorInfo, xcb_timestamp_t timestamp)
580 : QXcbObject(connection)
581 , m_virtualDesktop(virtualDesktop)
582 , m_monitor(monitorInfo)
583 , m_cursor(std::make_unique<QXcbCursor>(args&: connection, args: this))
584{
585 setMonitor(monitorInfo, timestamp);
586}
587
588void QXcbScreen::setMonitor(xcb_randr_monitor_info_t *monitorInfo, xcb_timestamp_t timestamp)
589{
590 if (!connection()->isAtLeastXRandR15())
591 return;
592
593 m_outputs.clear();
594 m_crtcs.clear();
595 m_output = XCB_NONE;
596 m_crtc = XCB_NONE;
597 m_singlescreen = false;
598
599 if (!monitorInfo) {
600 m_monitor = nullptr;
601 m_mode = XCB_NONE;
602 m_outputName = defaultName();
603 // TODO: Send an event to the QScreen instance that the screen changed its name
604 return;
605 }
606
607 m_monitor = monitorInfo;
608 qCDebug(lcQpaScreen) << "xcb_randr_monitor_info_t: primary=" << m_monitor->primary << ", x=" << m_monitor->x << ", y=" << m_monitor->y
609 << ", width=" << m_monitor->width << ", height=" << m_monitor->height
610 << ", width_in_millimeters=" << m_monitor->width_in_millimeters << ", height_in_millimeters=" << m_monitor->height_in_millimeters;
611 QRect monitorGeometry = QRect(m_monitor->x, m_monitor->y,
612 m_monitor->width, m_monitor->height);
613 m_sizeMillimeters = QSize(m_monitor->width_in_millimeters, m_monitor->height_in_millimeters);
614
615 int outputCount = xcb_randr_monitor_info_outputs_length(R: m_monitor);
616 xcb_randr_output_t *outputs = nullptr;
617 if (outputCount) {
618 outputs = xcb_randr_monitor_info_outputs(R: m_monitor);
619 for (int i = 0; i < outputCount; i++) {
620 auto output = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_output_info,
621 xcb_connection(), outputs[i], timestamp);
622 // Invalid, disconnected or disabled output
623 if (!output)
624 continue;
625
626 if (output->connection != XCB_RANDR_CONNECTION_CONNECTED) {
627 qCDebug(lcQpaScreen, "Output %s is not connected", qPrintable(
628 QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.get()),
629 xcb_randr_get_output_info_name_length(output.get()))));
630 continue;
631 }
632
633 if (output->crtc == XCB_NONE) {
634 qCDebug(lcQpaScreen, "Output %s is not enabled", qPrintable(
635 QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.get()),
636 xcb_randr_get_output_info_name_length(output.get()))));
637 continue;
638 }
639
640 m_outputs << outputs[i];
641 if (m_output == XCB_NONE) {
642 m_output = outputs[i];
643 m_outputSizeMillimeters = QSize(output->mm_width, output->mm_height);
644 }
645 m_crtcs << output->crtc;
646 if (m_crtc == XCB_NONE)
647 m_crtc = output->crtc;
648 }
649 }
650
651 if (m_crtcs.size() == 1) {
652 auto crtc = Q_XCB_REPLY(xcb_randr_get_crtc_info,
653 xcb_connection(), m_crtcs[0], timestamp);
654 if (crtc == XCB_NONE) {
655 qCDebug(lcQpaScreen, "Didn't get crtc info when m_crtcs.size() == 1");
656 } else {
657 m_singlescreen = (monitorGeometry == (QRect(crtc->x, crtc->y, crtc->width, crtc->height)));
658 if (m_singlescreen) {
659 if (crtc->mode) {
660 updateGeometry(geometry: QRect(crtc->x, crtc->y, crtc->width, crtc->height), rotation: crtc->rotation);
661 if (mode() != crtc->mode)
662 updateRefreshRate(mode: crtc->mode);
663 }
664 }
665 }
666 }
667
668 if (!m_singlescreen)
669 m_geometry = monitorGeometry;
670 m_availableGeometry = m_virtualDesktop->availableGeometry(screenGeometry: m_geometry);
671 if (m_geometry.isEmpty())
672 m_geometry = QRect(QPoint(), virtualDesktop()->size());
673 if (m_availableGeometry.isEmpty())
674 m_availableGeometry = m_virtualDesktop->availableGeometry(screenGeometry: m_geometry);
675
676 if (m_sizeMillimeters.isEmpty())
677 m_sizeMillimeters = virtualDesktop()->physicalSize();
678
679 m_outputName = getName(monitorInfo);
680 m_primary = false;
681 if (connection()->primaryScreenNumber() == virtualDesktop()->number()) {
682 if (monitorInfo->primary || isPrimaryInXScreen())
683 m_primary = true;
684 }
685
686 updateColorSpaceAndEdid();
687}
688
689QString QXcbScreen::defaultName()
690{
691 QString name;
692 QByteArray displayName = connection()->displayName();
693 int dotPos = displayName.lastIndexOf(ch: '.');
694 if (dotPos != -1)
695 displayName.truncate(pos: dotPos);
696 name = QString::fromLocal8Bit(ba: displayName) + u'.'
697 + QString::number(m_virtualDesktop->number());
698 return name;
699}
700
701bool QXcbScreen::isPrimaryInXScreen()
702{
703 auto primary = Q_XCB_REPLY(xcb_randr_get_output_primary, connection()->xcb_connection(), root());
704 if (!primary)
705 qWarning(msg: "failed to get the primary output of the screen");
706
707 const bool isPrimary = primary ? (m_monitor ? m_outputs.contains(t: primary->output) : m_output == primary->output) : false;
708
709 return isPrimary;
710}
711
712QXcbScreen::~QXcbScreen()
713{
714}
715
716QString QXcbScreen::getOutputName(xcb_randr_get_output_info_reply_t *outputInfo)
717{
718 QString name;
719 if (outputInfo) {
720 name = QString::fromUtf8(utf8: (const char*)xcb_randr_get_output_info_name(R: outputInfo),
721 size: xcb_randr_get_output_info_name_length(R: outputInfo));
722 } else {
723 name = defaultName();
724 }
725 return name;
726}
727
728QString QXcbScreen::getName(xcb_randr_monitor_info_t *monitorInfo)
729{
730 QString name;
731 QByteArray ba = connection()->atomName(atom: monitorInfo->name);
732 if (!ba.isEmpty()) {
733 name = QString::fromLatin1(ba: ba.constData());
734 } else {
735 QByteArray displayName = connection()->displayName();
736 int dotPos = displayName.lastIndexOf(ch: '.');
737 if (dotPos != -1)
738 displayName.truncate(pos: dotPos);
739 name = QString::fromLocal8Bit(ba: displayName) + u'.'
740 + QString::number(m_virtualDesktop->number());
741 }
742 return name;
743}
744
745QString QXcbScreen::manufacturer() const
746{
747 return m_edid.manufacturer;
748}
749
750QString QXcbScreen::model() const
751{
752 return m_edid.model;
753}
754
755QString QXcbScreen::serialNumber() const
756{
757 return m_edid.serialNumber;
758}
759
760QWindow *QXcbScreen::topLevelAt(const QPoint &p) const
761{
762 xcb_window_t root = screen()->root;
763
764 int x = p.x();
765 int y = p.y();
766
767 xcb_window_t parent = root;
768 xcb_window_t child = root;
769
770 do {
771 auto translate_reply = Q_XCB_REPLY_UNCHECKED(xcb_translate_coordinates, xcb_connection(), parent, child, x, y);
772 if (!translate_reply) {
773 return nullptr;
774 }
775
776 parent = child;
777 child = translate_reply->child;
778 x = translate_reply->dst_x;
779 y = translate_reply->dst_y;
780
781 if (!child || child == root)
782 return nullptr;
783
784 QPlatformWindow *platformWindow = connection()->platformWindowFromId(id: child);
785 if (platformWindow)
786 return platformWindow->window();
787 } while (parent != child);
788
789 return nullptr;
790}
791
792void QXcbScreen::windowShown(QXcbWindow *window)
793{
794 // Freedesktop.org Startup Notification
795 if (!connection()->startupId().isEmpty() && window->window()->isTopLevel()) {
796 sendStartupMessage(QByteArrayLiteral("remove: ID=") + connection()->startupId());
797 connection()->setStartupId({});
798 }
799}
800
801QSurfaceFormat QXcbScreen::surfaceFormatFor(const QSurfaceFormat &format) const
802{
803 return m_virtualDesktop->surfaceFormatFor(format);
804}
805
806const xcb_visualtype_t *QXcbScreen::visualForId(xcb_visualid_t visualid) const
807{
808 return m_virtualDesktop->visualForId(visualid);
809}
810
811void QXcbScreen::sendStartupMessage(const QByteArray &message) const
812{
813 xcb_window_t rootWindow = root();
814
815 xcb_client_message_event_t ev;
816 ev.response_type = XCB_CLIENT_MESSAGE;
817 ev.format = 8;
818 ev.type = connection()->atom(qatom: QXcbAtom::Atom_NET_STARTUP_INFO_BEGIN);
819 ev.sequence = 0;
820 ev.window = rootWindow;
821 int sent = 0;
822 int length = message.size() + 1; // include NUL byte
823 const char *data = message.constData();
824 do {
825 if (sent == 20)
826 ev.type = connection()->atom(qatom: QXcbAtom::Atom_NET_STARTUP_INFO);
827
828 const int start = sent;
829 const int numBytes = qMin(a: length - start, b: 20);
830 memcpy(dest: ev.data.data8, src: data + start, n: numBytes);
831 xcb_send_event(c: connection()->xcb_connection(), propagate: false, destination: rootWindow, event_mask: XCB_EVENT_MASK_PROPERTY_CHANGE, event: (const char *) &ev);
832
833 sent += numBytes;
834 } while (sent < length);
835}
836
837QRect QXcbScreen::availableGeometry() const
838{
839 static bool enforceNetWorkarea = !qEnvironmentVariableIsEmpty(varName: "QT_RELY_ON_NET_WORKAREA_ATOM");
840 bool isMultiHeadSystem = virtualSiblings().size() > 1;
841 bool useScreenGeometry = isMultiHeadSystem && !enforceNetWorkarea;
842 return useScreenGeometry ? m_geometry : m_availableGeometry;
843}
844
845QImage::Format QXcbScreen::format() const
846{
847 QImage::Format format;
848 bool needsRgbSwap;
849 qt_xcb_imageFormatForVisual(connection: connection(), depth: screen()->root_depth, visual: visualForId(visualid: screen()->root_visual), imageFormat: &format, needsRgbSwap: &needsRgbSwap);
850 // We are ignoring needsRgbSwap here and just assumes the backing-store will handle it.
851 if (format != QImage::Format_Invalid)
852 return format;
853 return QImage::Format_RGB32;
854}
855
856int QXcbScreen::forcedDpi() const
857{
858 const int forcedDpi = m_virtualDesktop->forcedDpi();
859 if (forcedDpi > 0)
860 return forcedDpi;
861 return 0;
862}
863
864QDpi QXcbScreen::logicalDpi() const
865{
866 const int forcedDpi = this->forcedDpi();
867 if (forcedDpi > 0)
868 return QDpi(forcedDpi, forcedDpi);
869
870 // Fall back to 96 DPI in case no logical DPI is set. We don't want to
871 // return physical DPI here, since that is a different type of DPI: Logical
872 // DPI typically accounts for user preference and viewing distance, and is
873 // quantized into DPI classes (96, 144, 192, etc); pysical DPI is an exact
874 // physical measure.
875 return QDpi(96, 96);
876}
877
878QPlatformCursor *QXcbScreen::cursor() const
879{
880 return m_cursor.get();
881}
882
883void QXcbScreen::setOutput(xcb_randr_output_t outputId,
884 xcb_randr_get_output_info_reply_t *outputInfo)
885{
886 m_monitor = nullptr;
887 m_output = outputId;
888 m_crtc = outputInfo ? outputInfo->crtc : XCB_NONE;
889 m_mode = XCB_NONE;
890 m_outputName = getOutputName(outputInfo);
891 // TODO: Send an event to the QScreen instance that the screen changed its name
892}
893
894void QXcbScreen::updateGeometry(xcb_timestamp_t timestamp)
895{
896 if (!connection()->isAtLeastXRandR12())
897 return;
898
899 auto crtc = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_crtc_info, xcb_connection(),
900 m_crtc, timestamp);
901 if (crtc)
902 updateGeometry(geometry: QRect(crtc->x, crtc->y, crtc->width, crtc->height), rotation: crtc->rotation);
903}
904
905void QXcbScreen::updateGeometry(const QRect &geometry, uint8_t rotation)
906{
907 const Qt::ScreenOrientation oldOrientation = m_orientation;
908
909 switch (rotation) {
910 case XCB_RANDR_ROTATION_ROTATE_0: // xrandr --rotate normal
911 m_orientation = Qt::LandscapeOrientation;
912 if (!m_monitor)
913 m_sizeMillimeters = m_outputSizeMillimeters;
914 break;
915 case XCB_RANDR_ROTATION_ROTATE_90: // xrandr --rotate left
916 m_orientation = Qt::PortraitOrientation;
917 if (!m_monitor)
918 m_sizeMillimeters = m_outputSizeMillimeters.transposed();
919 break;
920 case XCB_RANDR_ROTATION_ROTATE_180: // xrandr --rotate inverted
921 m_orientation = Qt::InvertedLandscapeOrientation;
922 if (!m_monitor)
923 m_sizeMillimeters = m_outputSizeMillimeters;
924 break;
925 case XCB_RANDR_ROTATION_ROTATE_270: // xrandr --rotate right
926 m_orientation = Qt::InvertedPortraitOrientation;
927 if (!m_monitor)
928 m_sizeMillimeters = m_outputSizeMillimeters.transposed();
929 break;
930 }
931
932 // It can be that physical size is unknown while virtual size
933 // is known (probably back-calculated from DPI and resolution),
934 // e.g. on VNC or with some hardware.
935 if (m_sizeMillimeters.isEmpty())
936 m_sizeMillimeters = sizeInMillimeters(size: geometry.size(), dpi: m_virtualDesktop->dpi());
937
938 m_geometry = geometry;
939 m_availableGeometry = m_virtualDesktop->availableGeometry(screenGeometry: m_geometry);
940 QWindowSystemInterface::handleScreenGeometryChange(screen: QPlatformScreen::screen(), newGeometry: m_geometry, newAvailableGeometry: m_availableGeometry);
941 if (m_orientation != oldOrientation)
942 QWindowSystemInterface::handleScreenOrientationChange(screen: QPlatformScreen::screen(), newOrientation: m_orientation);
943}
944
945void QXcbScreen::updateAvailableGeometry()
946{
947 QRect availableGeometry = m_virtualDesktop->availableGeometry(screenGeometry: m_geometry);
948 if (m_availableGeometry != availableGeometry) {
949 m_availableGeometry = availableGeometry;
950 QWindowSystemInterface::handleScreenGeometryChange(screen: QPlatformScreen::screen(), newGeometry: m_geometry, newAvailableGeometry: m_availableGeometry);
951 }
952}
953
954void QXcbScreen::updateRefreshRate(xcb_randr_mode_t mode)
955{
956 if (!connection()->isAtLeastXRandR12())
957 return;
958
959 if (m_mode == mode)
960 return;
961
962 // we can safely use get_screen_resources_current here, because in order to
963 // get here, we must have called get_screen_resources before
964 auto resources = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_screen_resources_current,
965 xcb_connection(), screen()->root);
966 if (resources) {
967 xcb_randr_mode_info_iterator_t modesIter =
968 xcb_randr_get_screen_resources_current_modes_iterator(R: resources.get());
969 for (; modesIter.rem; xcb_randr_mode_info_next(i: &modesIter)) {
970 xcb_randr_mode_info_t *modeInfo = modesIter.data;
971 if (modeInfo->id == mode) {
972 const uint32_t dotCount = modeInfo->htotal * modeInfo->vtotal;
973 m_refreshRate = (dotCount != 0) ? modeInfo->dot_clock / qreal(dotCount) : 0;
974 m_mode = mode;
975 break;
976 }
977 }
978
979 QWindowSystemInterface::handleScreenRefreshRateChange(screen: QPlatformScreen::screen(), newRefreshRate: m_refreshRate);
980 }
981}
982
983static inline bool translate(xcb_connection_t *connection, xcb_window_t child, xcb_window_t parent,
984 int *x, int *y)
985{
986 auto translate_reply = Q_XCB_REPLY_UNCHECKED(xcb_translate_coordinates,
987 connection, child, parent, *x, *y);
988 if (!translate_reply)
989 return false;
990 *x = translate_reply->dst_x;
991 *y = translate_reply->dst_y;
992 return true;
993}
994
995QPixmap QXcbScreen::grabWindow(WId window, int xIn, int yIn, int width, int height) const
996{
997 if (width == 0 || height == 0)
998 return QPixmap();
999
1000 int x = xIn;
1001 int y = yIn;
1002 QXcbScreen *screen = const_cast<QXcbScreen *>(this);
1003 xcb_window_t root = screen->root();
1004
1005 auto rootReply = Q_XCB_REPLY_UNCHECKED(xcb_get_geometry, xcb_connection(), root);
1006 if (!rootReply)
1007 return QPixmap();
1008
1009 const quint8 rootDepth = rootReply->depth;
1010
1011 QSize windowSize;
1012 quint8 effectiveDepth = 0;
1013 if (window) {
1014 auto windowReply = Q_XCB_REPLY_UNCHECKED(xcb_get_geometry, xcb_connection(), window);
1015 if (!windowReply)
1016 return QPixmap();
1017 windowSize = QSize(windowReply->width, windowReply->height);
1018 effectiveDepth = windowReply->depth;
1019 if (effectiveDepth == rootDepth) {
1020 // if the depth of the specified window and the root window are the
1021 // same, grab pixels from the root window (so that we get the any
1022 // overlapping windows and window manager frames)
1023
1024 // map x and y to the root window
1025 if (!translate(connection: xcb_connection(), child: window, parent: root, x: &x, y: &y))
1026 return QPixmap();
1027
1028 window = root;
1029 }
1030 } else {
1031 window = root;
1032 effectiveDepth = rootDepth;
1033 windowSize = m_geometry.size();
1034 x += m_geometry.x();
1035 y += m_geometry.y();
1036 }
1037
1038 if (width < 0)
1039 width = windowSize.width() - xIn;
1040 if (height < 0)
1041 height = windowSize.height() - yIn;
1042
1043 auto attributes_reply = Q_XCB_REPLY_UNCHECKED(xcb_get_window_attributes, xcb_connection(), window);
1044
1045 if (!attributes_reply)
1046 return QPixmap();
1047
1048 const xcb_visualtype_t *visual = screen->visualForId(visualid: attributes_reply->visual);
1049
1050 xcb_pixmap_t pixmap = xcb_generate_id(c: xcb_connection());
1051 xcb_create_pixmap(c: xcb_connection(), depth: effectiveDepth, pid: pixmap, drawable: window, width, height);
1052
1053 uint32_t gc_value_mask = XCB_GC_SUBWINDOW_MODE;
1054 uint32_t gc_value_list[] = { XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS };
1055
1056 xcb_gcontext_t gc = xcb_generate_id(c: xcb_connection());
1057 xcb_create_gc(c: xcb_connection(), cid: gc, drawable: pixmap, value_mask: gc_value_mask, value_list: gc_value_list);
1058
1059 xcb_copy_area(c: xcb_connection(), src_drawable: window, dst_drawable: pixmap, gc, src_x: x, src_y: y, dst_x: 0, dst_y: 0, width, height);
1060
1061 QPixmap result = qt_xcb_pixmapFromXPixmap(connection: connection(), pixmap, width, height, depth: effectiveDepth, visual);
1062 xcb_free_gc(c: xcb_connection(), gc);
1063 xcb_free_pixmap(c: xcb_connection(), pixmap);
1064
1065 return result;
1066}
1067
1068QXcbXSettings *QXcbScreen::xSettings() const
1069{
1070 return m_virtualDesktop->xSettings();
1071}
1072
1073QByteArray QXcbScreen::getOutputProperty(xcb_atom_t atom) const
1074{
1075 QByteArray result;
1076
1077 auto reply = Q_XCB_REPLY(xcb_randr_get_output_property, xcb_connection(),
1078 m_output, atom, XCB_ATOM_ANY, 0, 100, false, false);
1079 if (reply && reply->type == XCB_ATOM_INTEGER && reply->format == 8) {
1080 quint8 *data = new quint8[reply->num_items];
1081 memcpy(dest: data, src: xcb_randr_get_output_property_data(R: reply.get()), n: reply->num_items);
1082 result = QByteArray(reinterpret_cast<const char *>(data), reply->num_items);
1083 delete[] data;
1084 }
1085
1086 return result;
1087}
1088
1089QByteArray QXcbScreen::getEdid() const
1090{
1091 QByteArray result;
1092 if (!connection()->isAtLeastXRandR12())
1093 return result;
1094
1095 // Try a bunch of atoms
1096 result = getOutputProperty(atom: atom(atom: QXcbAtom::AtomEDID));
1097 if (result.isEmpty())
1098 result = getOutputProperty(atom: atom(atom: QXcbAtom::AtomEDID_DATA));
1099 if (result.isEmpty())
1100 result = getOutputProperty(atom: atom(atom: QXcbAtom::AtomXFree86_DDC_EDID1_RAWDATA));
1101
1102 return result;
1103}
1104
1105static inline void formatRect(QDebug &debug, const QRect r)
1106{
1107 debug << r.width() << 'x' << r.height()
1108 << Qt::forcesign << r.x() << r.y() << Qt::noforcesign;
1109}
1110
1111static inline void formatSizeF(QDebug &debug, const QSizeF s)
1112{
1113 debug << s.width() << 'x' << s.height() << "mm";
1114}
1115
1116QDebug operator<<(QDebug debug, const QXcbScreen *screen)
1117{
1118 const QDebugStateSaver saver(debug);
1119 debug.nospace();
1120 debug << "QXcbScreen(" << (const void *)screen;
1121 if (screen) {
1122 debug << Qt::fixed << qSetRealNumberPrecision(precision: 1);
1123 debug << ", name=" << screen->name();
1124 debug << ", geometry=";
1125 formatRect(debug, r: screen->geometry());
1126 debug << ", availableGeometry=";
1127 formatRect(debug, r: screen->availableGeometry());
1128 debug << ", devicePixelRatio=" << screen->devicePixelRatio();
1129 debug << ", logicalDpi=" << screen->logicalDpi();
1130 debug << ", physicalSize=";
1131 formatSizeF(debug, s: screen->physicalSize());
1132 // TODO 5.6 if (debug.verbosity() > 2) {
1133 debug << ", screenNumber=" << screen->screenNumber();
1134 const QSize virtualSize = screen->virtualDesktop()->size();
1135 debug << ", virtualSize=" << virtualSize.width() << 'x' << virtualSize.height() << " (";
1136 formatSizeF(debug, s: virtualSize);
1137 debug << "), orientation=" << screen->orientation();
1138 debug << ", depth=" << screen->depth();
1139 debug << ", refreshRate=" << screen->refreshRate();
1140 debug << ", root=" << Qt::hex << screen->root();
1141 debug << ", windowManagerName=" << screen->windowManagerName();
1142 }
1143 debug << ')';
1144 return debug;
1145}
1146
1147QT_END_NAMESPACE
1148

source code of qtbase/src/plugins/platforms/xcb/qxcbscreen.cpp