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

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