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 xcb_randr_select_input(c: xcb_connection(), window: screen()->root, enable: true);
503 auto crtc = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_crtc_info, xcb_connection(),
504 m_crtc, output ? output->timestamp : 0);
505 if (crtc) {
506 updateGeometry(geometry: QRect(crtc->x, crtc->y, crtc->width, crtc->height), rotation: crtc->rotation);
507 updateRefreshRate(mode: crtc->mode);
508 }
509 }
510
511 if (m_geometry.isEmpty())
512 m_geometry = QRect(QPoint(), virtualDesktop->size());
513
514 if (m_availableGeometry.isEmpty())
515 m_availableGeometry = m_virtualDesktop->availableGeometry(screenGeometry: m_geometry);
516
517 if (m_sizeMillimeters.isEmpty())
518 m_sizeMillimeters = virtualDesktop->physicalSize();
519
520 updateColorSpaceAndEdid();
521}
522
523void QXcbScreen::updateColorSpaceAndEdid()
524{
525 {
526 // Read colord ICC data (from GNOME settings)
527 auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(),
528 false, screen()->root,
529 connection()->atom(QXcbAtom::Atom_ICC_PROFILE),
530 XCB_ATOM_CARDINAL, 0, 8192);
531 if (reply->format == 8 && reply->type == XCB_ATOM_CARDINAL) {
532 QByteArray data(reinterpret_cast<const char *>(xcb_get_property_value(R: reply.get())), reply->value_len);
533 m_colorSpace = QColorSpace::fromIccProfile(iccProfile: data);
534 }
535 }
536 if (connection()->isAtLeastXRandR12()) { // Parse EDID
537 QByteArray edid = getEdid();
538 if (m_edid.parse(blob: edid)) {
539 qCDebug(lcQpaScreen, "EDID data for output \"%s\": identifier '%s', manufacturer '%s',"
540 "model '%s', serial '%s', physical size: %.2fx%.2f",
541 name().toLatin1().constData(),
542 m_edid.identifier.toLatin1().constData(),
543 m_edid.manufacturer.toLatin1().constData(),
544 m_edid.model.toLatin1().constData(),
545 m_edid.serialNumber.toLatin1().constData(),
546 m_edid.physicalSize.width(), m_edid.physicalSize.height());
547 if (!m_colorSpace.isValid()) {
548 if (m_edid.sRgb)
549 m_colorSpace = QColorSpace::SRgb;
550 else {
551 if (!m_edid.useTables) {
552 m_colorSpace = QColorSpace(m_edid.whiteChromaticity, m_edid.redChromaticity,
553 m_edid.greenChromaticity, m_edid.blueChromaticity,
554 QColorSpace::TransferFunction::Gamma, m_edid.gamma);
555 } else {
556 if (m_edid.tables.size() == 1) {
557 m_colorSpace = QColorSpace(m_edid.whiteChromaticity, m_edid.redChromaticity,
558 m_edid.greenChromaticity, m_edid.blueChromaticity,
559 m_edid.tables[0]);
560 } else if (m_edid.tables.size() == 3) {
561 m_colorSpace = QColorSpace(m_edid.whiteChromaticity, m_edid.redChromaticity,
562 m_edid.greenChromaticity, m_edid.blueChromaticity,
563 m_edid.tables[0], m_edid.tables[1], m_edid.tables[2]);
564 }
565 }
566 }
567 }
568 } else {
569 // This property is defined by the xrandr spec. Parsing failure indicates a valid error,
570 // but keep this as debug, for details see 4f515815efc318ddc909a0399b71b8a684962f38.
571 qCDebug(lcQpaScreen) << "Failed to parse EDID data for output" << name() <<
572 "edid data: " << edid;
573 }
574 }
575 if (!m_colorSpace.isValid())
576 m_colorSpace = QColorSpace::SRgb;
577}
578
579QXcbScreen::QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDesktop,
580 xcb_randr_monitor_info_t *monitorInfo, xcb_timestamp_t timestamp)
581 : QXcbObject(connection)
582 , m_virtualDesktop(virtualDesktop)
583 , m_monitor(monitorInfo)
584 , m_cursor(std::make_unique<QXcbCursor>(args&: connection, args: this))
585{
586 setMonitor(monitorInfo, timestamp);
587}
588
589void QXcbScreen::setMonitor(xcb_randr_monitor_info_t *monitorInfo, xcb_timestamp_t timestamp)
590{
591 if (!connection()->isAtLeastXRandR15())
592 return;
593
594 m_outputs.clear();
595 m_crtcs.clear();
596 m_output = XCB_NONE;
597 m_crtc = XCB_NONE;
598 m_singlescreen = false;
599
600 if (!monitorInfo) {
601 m_monitor = nullptr;
602 m_mode = XCB_NONE;
603 m_outputName = defaultName();
604 // TODO: Send an event to the QScreen instance that the screen changed its name
605 return;
606 }
607
608 xcb_randr_select_input(c: xcb_connection(), window: screen()->root, enable: true);
609
610 m_monitor = monitorInfo;
611 qCDebug(lcQpaScreen) << "xcb_randr_monitor_info_t: primary=" << m_monitor->primary << ", x=" << m_monitor->x << ", y=" << m_monitor->y
612 << ", width=" << m_monitor->width << ", height=" << m_monitor->height
613 << ", width_in_millimeters=" << m_monitor->width_in_millimeters << ", height_in_millimeters=" << m_monitor->height_in_millimeters;
614 QRect monitorGeometry = QRect(m_monitor->x, m_monitor->y,
615 m_monitor->width, m_monitor->height);
616 m_sizeMillimeters = QSize(m_monitor->width_in_millimeters, m_monitor->height_in_millimeters);
617
618 int outputCount = xcb_randr_monitor_info_outputs_length(R: m_monitor);
619 xcb_randr_output_t *outputs = nullptr;
620 if (outputCount) {
621 outputs = xcb_randr_monitor_info_outputs(R: m_monitor);
622 for (int i = 0; i < outputCount; i++) {
623 auto output = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_output_info,
624 xcb_connection(), outputs[i], timestamp);
625 // Invalid, disconnected or disabled output
626 if (!output)
627 continue;
628
629 if (output->connection != XCB_RANDR_CONNECTION_CONNECTED) {
630 qCDebug(lcQpaScreen, "Output %s is not connected", qPrintable(
631 QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.get()),
632 xcb_randr_get_output_info_name_length(output.get()))));
633 continue;
634 }
635
636 if (output->crtc == XCB_NONE) {
637 qCDebug(lcQpaScreen, "Output %s is not enabled", qPrintable(
638 QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.get()),
639 xcb_randr_get_output_info_name_length(output.get()))));
640 continue;
641 }
642
643 m_outputs << outputs[i];
644 if (m_output == XCB_NONE) {
645 m_output = outputs[i];
646 m_outputSizeMillimeters = QSize(output->mm_width, output->mm_height);
647 }
648 m_crtcs << output->crtc;
649 if (m_crtc == XCB_NONE)
650 m_crtc = output->crtc;
651 }
652 }
653
654 if (m_crtcs.size() == 1) {
655 auto crtc = Q_XCB_REPLY(xcb_randr_get_crtc_info,
656 xcb_connection(), m_crtcs[0], timestamp);
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 if (!m_singlescreen)
668 m_geometry = monitorGeometry;
669 m_availableGeometry = m_virtualDesktop->availableGeometry(screenGeometry: m_geometry);
670 if (m_geometry.isEmpty())
671 m_geometry = QRect(QPoint(), virtualDesktop()->size());
672 if (m_availableGeometry.isEmpty())
673 m_availableGeometry = m_virtualDesktop->availableGeometry(screenGeometry: m_geometry);
674
675 if (m_sizeMillimeters.isEmpty())
676 m_sizeMillimeters = virtualDesktop()->physicalSize();
677
678 m_outputName = getName(monitorInfo);
679 m_primary = false;
680 if (connection()->primaryScreenNumber() == virtualDesktop()->number()) {
681 if (monitorInfo->primary || isPrimaryInXScreen())
682 m_primary = true;
683 }
684
685 updateColorSpaceAndEdid();
686}
687
688QString QXcbScreen::defaultName()
689{
690 QString name;
691 QByteArray displayName = connection()->displayName();
692 int dotPos = displayName.lastIndexOf(ch: '.');
693 if (dotPos != -1)
694 displayName.truncate(pos: dotPos);
695 name = QString::fromLocal8Bit(ba: displayName) + u'.'
696 + QString::number(m_virtualDesktop->number());
697 return name;
698}
699
700bool QXcbScreen::isPrimaryInXScreen()
701{
702 auto primary = Q_XCB_REPLY(xcb_randr_get_output_primary, connection()->xcb_connection(), root());
703 if (!primary)
704 qWarning(msg: "failed to get the primary output of the screen");
705
706 const bool isPrimary = primary ? (m_monitor ? m_outputs.contains(t: primary->output) : m_output == primary->output) : false;
707
708 return isPrimary;
709}
710
711QXcbScreen::~QXcbScreen()
712{
713}
714
715QString QXcbScreen::getOutputName(xcb_randr_get_output_info_reply_t *outputInfo)
716{
717 QString name;
718 if (outputInfo) {
719 name = QString::fromUtf8(utf8: (const char*)xcb_randr_get_output_info_name(R: outputInfo),
720 size: xcb_randr_get_output_info_name_length(R: outputInfo));
721 } else {
722 name = defaultName();
723 }
724 return name;
725}
726
727QString QXcbScreen::getName(xcb_randr_monitor_info_t *monitorInfo)
728{
729 QString name;
730 QByteArray ba = connection()->atomName(atom: monitorInfo->name);
731 if (!ba.isEmpty()) {
732 name = QString::fromLatin1(ba: ba.constData());
733 } else {
734 QByteArray displayName = connection()->displayName();
735 int dotPos = displayName.lastIndexOf(ch: '.');
736 if (dotPos != -1)
737 displayName.truncate(pos: dotPos);
738 name = QString::fromLocal8Bit(ba: displayName) + u'.'
739 + QString::number(m_virtualDesktop->number());
740 }
741 return name;
742}
743
744QString QXcbScreen::manufacturer() const
745{
746 return m_edid.manufacturer;
747}
748
749QString QXcbScreen::model() const
750{
751 return m_edid.model;
752}
753
754QString QXcbScreen::serialNumber() const
755{
756 return m_edid.serialNumber;
757}
758
759QWindow *QXcbScreen::topLevelAt(const QPoint &p) const
760{
761 xcb_window_t root = screen()->root;
762
763 int x = p.x();
764 int y = p.y();
765
766 xcb_window_t parent = root;
767 xcb_window_t child = root;
768
769 do {
770 auto translate_reply = Q_XCB_REPLY_UNCHECKED(xcb_translate_coordinates, xcb_connection(), parent, child, x, y);
771 if (!translate_reply) {
772 return nullptr;
773 }
774
775 parent = child;
776 child = translate_reply->child;
777 x = translate_reply->dst_x;
778 y = translate_reply->dst_y;
779
780 if (!child || child == root)
781 return nullptr;
782
783 QPlatformWindow *platformWindow = connection()->platformWindowFromId(id: child);
784 if (platformWindow)
785 return platformWindow->window();
786 } while (parent != child);
787
788 return nullptr;
789}
790
791void QXcbScreen::windowShown(QXcbWindow *window)
792{
793 // Freedesktop.org Startup Notification
794 if (!connection()->startupId().isEmpty() && window->window()->isTopLevel()) {
795 sendStartupMessage(QByteArrayLiteral("remove: ID=") + connection()->startupId());
796 connection()->setStartupId({});
797 }
798}
799
800QSurfaceFormat QXcbScreen::surfaceFormatFor(const QSurfaceFormat &format) const
801{
802 return m_virtualDesktop->surfaceFormatFor(format);
803}
804
805const xcb_visualtype_t *QXcbScreen::visualForId(xcb_visualid_t visualid) const
806{
807 return m_virtualDesktop->visualForId(visualid);
808}
809
810void QXcbScreen::sendStartupMessage(const QByteArray &message) const
811{
812 xcb_window_t rootWindow = root();
813
814 xcb_client_message_event_t ev;
815 ev.response_type = XCB_CLIENT_MESSAGE;
816 ev.format = 8;
817 ev.type = connection()->atom(qatom: QXcbAtom::Atom_NET_STARTUP_INFO_BEGIN);
818 ev.sequence = 0;
819 ev.window = rootWindow;
820 int sent = 0;
821 int length = message.size() + 1; // include NUL byte
822 const char *data = message.constData();
823 do {
824 if (sent == 20)
825 ev.type = connection()->atom(qatom: QXcbAtom::Atom_NET_STARTUP_INFO);
826
827 const int start = sent;
828 const int numBytes = qMin(a: length - start, b: 20);
829 memcpy(dest: ev.data.data8, src: data + start, n: numBytes);
830 xcb_send_event(c: connection()->xcb_connection(), propagate: false, destination: rootWindow, event_mask: XCB_EVENT_MASK_PROPERTY_CHANGE, event: (const char *) &ev);
831
832 sent += numBytes;
833 } while (sent < length);
834}
835
836QRect QXcbScreen::availableGeometry() const
837{
838 static bool enforceNetWorkarea = !qEnvironmentVariableIsEmpty(varName: "QT_RELY_ON_NET_WORKAREA_ATOM");
839 bool isMultiHeadSystem = virtualSiblings().size() > 1;
840 bool useScreenGeometry = isMultiHeadSystem && !enforceNetWorkarea;
841 return useScreenGeometry ? m_geometry : m_availableGeometry;
842}
843
844QImage::Format QXcbScreen::format() const
845{
846 QImage::Format format;
847 bool needsRgbSwap;
848 qt_xcb_imageFormatForVisual(connection: connection(), depth: screen()->root_depth, visual: visualForId(visualid: screen()->root_visual), imageFormat: &format, needsRgbSwap: &needsRgbSwap);
849 // We are ignoring needsRgbSwap here and just assumes the backing-store will handle it.
850 if (format != QImage::Format_Invalid)
851 return format;
852 return QImage::Format_RGB32;
853}
854
855int QXcbScreen::forcedDpi() const
856{
857 const int forcedDpi = m_virtualDesktop->forcedDpi();
858 if (forcedDpi > 0)
859 return forcedDpi;
860 return 0;
861}
862
863QDpi QXcbScreen::logicalDpi() const
864{
865 const int forcedDpi = this->forcedDpi();
866 if (forcedDpi > 0)
867 return QDpi(forcedDpi, forcedDpi);
868
869 // Fall back to 96 DPI in case no logical DPI is set. We don't want to
870 // return physical DPI here, since that is a different type of DPI: Logical
871 // DPI typically accounts for user preference and viewing distance, and is
872 // quantized into DPI classes (96, 144, 192, etc); pysical DPI is an exact
873 // physical measure.
874 return QDpi(96, 96);
875}
876
877QPlatformCursor *QXcbScreen::cursor() const
878{
879 return m_cursor.get();
880}
881
882void QXcbScreen::setOutput(xcb_randr_output_t outputId,
883 xcb_randr_get_output_info_reply_t *outputInfo)
884{
885 m_monitor = nullptr;
886 m_output = outputId;
887 m_crtc = outputInfo ? outputInfo->crtc : XCB_NONE;
888 m_mode = XCB_NONE;
889 m_outputName = getOutputName(outputInfo);
890 // TODO: Send an event to the QScreen instance that the screen changed its name
891}
892
893void QXcbScreen::updateGeometry(xcb_timestamp_t timestamp)
894{
895 if (!connection()->isAtLeastXRandR12())
896 return;
897
898 auto crtc = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_crtc_info, xcb_connection(),
899 m_crtc, timestamp);
900 if (crtc)
901 updateGeometry(geometry: QRect(crtc->x, crtc->y, crtc->width, crtc->height), rotation: crtc->rotation);
902}
903
904void QXcbScreen::updateGeometry(const QRect &geometry, uint8_t rotation)
905{
906 const Qt::ScreenOrientation oldOrientation = m_orientation;
907
908 switch (rotation) {
909 case XCB_RANDR_ROTATION_ROTATE_0: // xrandr --rotate normal
910 m_orientation = Qt::LandscapeOrientation;
911 if (!m_monitor)
912 m_sizeMillimeters = m_outputSizeMillimeters;
913 break;
914 case XCB_RANDR_ROTATION_ROTATE_90: // xrandr --rotate left
915 m_orientation = Qt::PortraitOrientation;
916 if (!m_monitor)
917 m_sizeMillimeters = m_outputSizeMillimeters.transposed();
918 break;
919 case XCB_RANDR_ROTATION_ROTATE_180: // xrandr --rotate inverted
920 m_orientation = Qt::InvertedLandscapeOrientation;
921 if (!m_monitor)
922 m_sizeMillimeters = m_outputSizeMillimeters;
923 break;
924 case XCB_RANDR_ROTATION_ROTATE_270: // xrandr --rotate right
925 m_orientation = Qt::InvertedPortraitOrientation;
926 if (!m_monitor)
927 m_sizeMillimeters = m_outputSizeMillimeters.transposed();
928 break;
929 }
930
931 // It can be that physical size is unknown while virtual size
932 // is known (probably back-calculated from DPI and resolution),
933 // e.g. on VNC or with some hardware.
934 if (m_sizeMillimeters.isEmpty())
935 m_sizeMillimeters = sizeInMillimeters(size: geometry.size(), dpi: m_virtualDesktop->dpi());
936
937 m_geometry = geometry;
938 m_availableGeometry = m_virtualDesktop->availableGeometry(screenGeometry: m_geometry);
939 QWindowSystemInterface::handleScreenGeometryChange(screen: QPlatformScreen::screen(), newGeometry: m_geometry, newAvailableGeometry: m_availableGeometry);
940 if (m_orientation != oldOrientation)
941 QWindowSystemInterface::handleScreenOrientationChange(screen: QPlatformScreen::screen(), newOrientation: m_orientation);
942}
943
944void QXcbScreen::updateAvailableGeometry()
945{
946 QRect availableGeometry = m_virtualDesktop->availableGeometry(screenGeometry: m_geometry);
947 if (m_availableGeometry != availableGeometry) {
948 m_availableGeometry = availableGeometry;
949 QWindowSystemInterface::handleScreenGeometryChange(screen: QPlatformScreen::screen(), newGeometry: m_geometry, newAvailableGeometry: m_availableGeometry);
950 }
951}
952
953void QXcbScreen::updateRefreshRate(xcb_randr_mode_t mode)
954{
955 if (!connection()->isAtLeastXRandR12())
956 return;
957
958 if (m_mode == mode)
959 return;
960
961 // we can safely use get_screen_resources_current here, because in order to
962 // get here, we must have called get_screen_resources before
963 auto resources = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_screen_resources_current,
964 xcb_connection(), screen()->root);
965 if (resources) {
966 xcb_randr_mode_info_iterator_t modesIter =
967 xcb_randr_get_screen_resources_current_modes_iterator(R: resources.get());
968 for (; modesIter.rem; xcb_randr_mode_info_next(i: &modesIter)) {
969 xcb_randr_mode_info_t *modeInfo = modesIter.data;
970 if (modeInfo->id == mode) {
971 const uint32_t dotCount = modeInfo->htotal * modeInfo->vtotal;
972 m_refreshRate = (dotCount != 0) ? modeInfo->dot_clock / qreal(dotCount) : 0;
973 m_mode = mode;
974 break;
975 }
976 }
977
978 QWindowSystemInterface::handleScreenRefreshRateChange(screen: QPlatformScreen::screen(), newRefreshRate: m_refreshRate);
979 }
980}
981
982static inline bool translate(xcb_connection_t *connection, xcb_window_t child, xcb_window_t parent,
983 int *x, int *y)
984{
985 auto translate_reply = Q_XCB_REPLY_UNCHECKED(xcb_translate_coordinates,
986 connection, child, parent, *x, *y);
987 if (!translate_reply)
988 return false;
989 *x = translate_reply->dst_x;
990 *y = translate_reply->dst_y;
991 return true;
992}
993
994QPixmap QXcbScreen::grabWindow(WId window, int xIn, int yIn, int width, int height) const
995{
996 if (width == 0 || height == 0)
997 return QPixmap();
998
999 int x = xIn;
1000 int y = yIn;
1001 QXcbScreen *screen = const_cast<QXcbScreen *>(this);
1002 xcb_window_t root = screen->root();
1003
1004 auto rootReply = Q_XCB_REPLY_UNCHECKED(xcb_get_geometry, xcb_connection(), root);
1005 if (!rootReply)
1006 return QPixmap();
1007
1008 const quint8 rootDepth = rootReply->depth;
1009
1010 QSize windowSize;
1011 quint8 effectiveDepth = 0;
1012 if (window) {
1013 auto windowReply = Q_XCB_REPLY_UNCHECKED(xcb_get_geometry, xcb_connection(), window);
1014 if (!windowReply)
1015 return QPixmap();
1016 windowSize = QSize(windowReply->width, windowReply->height);
1017 effectiveDepth = windowReply->depth;
1018 if (effectiveDepth == rootDepth) {
1019 // if the depth of the specified window and the root window are the
1020 // same, grab pixels from the root window (so that we get the any
1021 // overlapping windows and window manager frames)
1022
1023 // map x and y to the root window
1024 if (!translate(connection: xcb_connection(), child: window, parent: root, x: &x, y: &y))
1025 return QPixmap();
1026
1027 window = root;
1028 }
1029 } else {
1030 window = root;
1031 effectiveDepth = rootDepth;
1032 windowSize = m_geometry.size();
1033 x += m_geometry.x();
1034 y += m_geometry.y();
1035 }
1036
1037 if (width < 0)
1038 width = windowSize.width() - xIn;
1039 if (height < 0)
1040 height = windowSize.height() - yIn;
1041
1042 auto attributes_reply = Q_XCB_REPLY_UNCHECKED(xcb_get_window_attributes, xcb_connection(), window);
1043
1044 if (!attributes_reply)
1045 return QPixmap();
1046
1047 const xcb_visualtype_t *visual = screen->visualForId(visualid: attributes_reply->visual);
1048
1049 xcb_pixmap_t pixmap = xcb_generate_id(c: xcb_connection());
1050 xcb_create_pixmap(c: xcb_connection(), depth: effectiveDepth, pid: pixmap, drawable: window, width, height);
1051
1052 uint32_t gc_value_mask = XCB_GC_SUBWINDOW_MODE;
1053 uint32_t gc_value_list[] = { XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS };
1054
1055 xcb_gcontext_t gc = xcb_generate_id(c: xcb_connection());
1056 xcb_create_gc(c: xcb_connection(), cid: gc, drawable: pixmap, value_mask: gc_value_mask, value_list: gc_value_list);
1057
1058 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);
1059
1060 QPixmap result = qt_xcb_pixmapFromXPixmap(connection: connection(), pixmap, width, height, depth: effectiveDepth, visual);
1061 xcb_free_gc(c: xcb_connection(), gc);
1062 xcb_free_pixmap(c: xcb_connection(), pixmap);
1063
1064 return result;
1065}
1066
1067QXcbXSettings *QXcbScreen::xSettings() const
1068{
1069 return m_virtualDesktop->xSettings();
1070}
1071
1072QByteArray QXcbScreen::getOutputProperty(xcb_atom_t atom) const
1073{
1074 QByteArray result;
1075
1076 auto reply = Q_XCB_REPLY(xcb_randr_get_output_property, xcb_connection(),
1077 m_output, atom, XCB_ATOM_ANY, 0, 100, false, false);
1078 if (reply && reply->type == XCB_ATOM_INTEGER && reply->format == 8) {
1079 quint8 *data = new quint8[reply->num_items];
1080 memcpy(dest: data, src: xcb_randr_get_output_property_data(R: reply.get()), n: reply->num_items);
1081 result = QByteArray(reinterpret_cast<const char *>(data), reply->num_items);
1082 delete[] data;
1083 }
1084
1085 return result;
1086}
1087
1088QByteArray QXcbScreen::getEdid() const
1089{
1090 QByteArray result;
1091 if (!connection()->isAtLeastXRandR12())
1092 return result;
1093
1094 // Try a bunch of atoms
1095 result = getOutputProperty(atom: atom(atom: QXcbAtom::AtomEDID));
1096 if (result.isEmpty())
1097 result = getOutputProperty(atom: atom(atom: QXcbAtom::AtomEDID_DATA));
1098 if (result.isEmpty())
1099 result = getOutputProperty(atom: atom(atom: QXcbAtom::AtomXFree86_DDC_EDID1_RAWDATA));
1100
1101 return result;
1102}
1103
1104static inline void formatRect(QDebug &debug, const QRect r)
1105{
1106 debug << r.width() << 'x' << r.height()
1107 << Qt::forcesign << r.x() << r.y() << Qt::noforcesign;
1108}
1109
1110static inline void formatSizeF(QDebug &debug, const QSizeF s)
1111{
1112 debug << s.width() << 'x' << s.height() << "mm";
1113}
1114
1115QDebug operator<<(QDebug debug, const QXcbScreen *screen)
1116{
1117 const QDebugStateSaver saver(debug);
1118 debug.nospace();
1119 debug << "QXcbScreen(" << (const void *)screen;
1120 if (screen) {
1121 debug << Qt::fixed << qSetRealNumberPrecision(precision: 1);
1122 debug << ", name=" << screen->name();
1123 debug << ", geometry=";
1124 formatRect(debug, r: screen->geometry());
1125 debug << ", availableGeometry=";
1126 formatRect(debug, r: screen->availableGeometry());
1127 debug << ", devicePixelRatio=" << screen->devicePixelRatio();
1128 debug << ", logicalDpi=" << screen->logicalDpi();
1129 debug << ", physicalSize=";
1130 formatSizeF(debug, s: screen->physicalSize());
1131 // TODO 5.6 if (debug.verbosity() > 2) {
1132 debug << ", screenNumber=" << screen->screenNumber();
1133 const QSize virtualSize = screen->virtualDesktop()->size();
1134 debug << ", virtualSize=" << virtualSize.width() << 'x' << virtualSize.height() << " (";
1135 formatSizeF(debug, s: virtualSize);
1136 debug << "), orientation=" << screen->orientation();
1137 debug << ", depth=" << screen->depth();
1138 debug << ", refreshRate=" << screen->refreshRate();
1139 debug << ", root=" << Qt::hex << screen->root();
1140 debug << ", windowManagerName=" << screen->windowManagerName();
1141 }
1142 debug << ')';
1143 return debug;
1144}
1145
1146QT_END_NAMESPACE
1147

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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