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