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

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