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