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