1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module 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#include "qxcbconnection.h"
40#include "qxcbscreen.h"
41#include "qxcbintegration.h"
42
43#include <QtGui/private/qhighdpiscaling_p.h>
44#include <QtCore/QString>
45#include <QtCore/QList>
46
47#include <qpa/qwindowsysteminterface.h>
48
49#include <xcb/xinerama.h>
50
51void QXcbConnection::xrandrSelectEvents()
52{
53 xcb_screen_iterator_t rootIter = xcb_setup_roots_iterator(R: setup());
54 for (; rootIter.rem; xcb_screen_next(i: &rootIter)) {
55 xcb_randr_select_input(c: xcb_connection(),
56 window: rootIter.data->root,
57 enable: XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
58 XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
59 XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
60 XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY
61 );
62 }
63}
64
65QXcbScreen* QXcbConnection::findScreenForCrtc(xcb_window_t rootWindow, xcb_randr_crtc_t crtc) const
66{
67 for (QXcbScreen *screen : m_screens) {
68 if (screen->root() == rootWindow && screen->crtc() == crtc)
69 return screen;
70 }
71
72 return nullptr;
73}
74
75QXcbScreen* QXcbConnection::findScreenForOutput(xcb_window_t rootWindow, xcb_randr_output_t output) const
76{
77 for (QXcbScreen *screen : m_screens) {
78 if (screen->root() == rootWindow && screen->output() == output)
79 return screen;
80 }
81
82 return nullptr;
83}
84
85QXcbVirtualDesktop* QXcbConnection::virtualDesktopForRootWindow(xcb_window_t rootWindow) const
86{
87 for (QXcbVirtualDesktop *virtualDesktop : m_virtualDesktops) {
88 if (virtualDesktop->screen()->root == rootWindow)
89 return virtualDesktop;
90 }
91
92 return nullptr;
93}
94
95/*!
96 \brief Synchronizes the screen list, adds new screens, removes deleted ones
97*/
98void QXcbConnection::updateScreens(const xcb_randr_notify_event_t *event)
99{
100 if (event->subCode == XCB_RANDR_NOTIFY_CRTC_CHANGE) {
101 xcb_randr_crtc_change_t crtc = event->u.cc;
102 QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(rootWindow: crtc.window);
103 if (!virtualDesktop)
104 // Not for us
105 return;
106
107 QXcbScreen *screen = findScreenForCrtc(rootWindow: crtc.window, crtc: crtc.crtc);
108 qCDebug(lcQpaScreen) << "QXcbConnection: XCB_RANDR_NOTIFY_CRTC_CHANGE:" << crtc.crtc
109 << "mode" << crtc.mode << "relevant screen" << screen;
110 // Only update geometry when there's a valid mode on the CRTC
111 // CRTC with node mode could mean that output has been disabled, and we'll
112 // get RRNotifyOutputChange notification for that.
113 if (screen && crtc.mode) {
114 if (crtc.rotation == XCB_RANDR_ROTATION_ROTATE_90 ||
115 crtc.rotation == XCB_RANDR_ROTATION_ROTATE_270)
116 std::swap(a&: crtc.width, b&: crtc.height);
117 screen->updateGeometry(geometry: QRect(crtc.x, crtc.y, crtc.width, crtc.height), rotation: crtc.rotation);
118 if (screen->mode() != crtc.mode)
119 screen->updateRefreshRate(mode: crtc.mode);
120 }
121
122 } else if (event->subCode == XCB_RANDR_NOTIFY_OUTPUT_CHANGE) {
123 xcb_randr_output_change_t output = event->u.oc;
124 QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(rootWindow: output.window);
125 if (!virtualDesktop)
126 // Not for us
127 return;
128
129 QXcbScreen *screen = findScreenForOutput(rootWindow: output.window, output: output.output);
130 qCDebug(lcQpaScreen) << "QXcbConnection: XCB_RANDR_NOTIFY_OUTPUT_CHANGE:" << output.output;
131
132 if (screen && output.connection == XCB_RANDR_CONNECTION_DISCONNECTED) {
133 qCDebug(lcQpaScreen) << "screen" << screen->name() << "has been disconnected";
134 destroyScreen(screen);
135 } else if (!screen && output.connection == XCB_RANDR_CONNECTION_CONNECTED) {
136 // New XRandR output is available and it's enabled
137 if (output.crtc != XCB_NONE && output.mode != XCB_NONE) {
138 auto outputInfo = Q_XCB_REPLY(xcb_randr_get_output_info, xcb_connection(),
139 output.output, output.config_timestamp);
140 // Find a fake screen
141 const auto scrs = virtualDesktop->screens();
142 for (QPlatformScreen *scr : scrs) {
143 QXcbScreen *xcbScreen = static_cast<QXcbScreen *>(scr);
144 if (xcbScreen->output() == XCB_NONE) {
145 screen = xcbScreen;
146 break;
147 }
148 }
149
150 if (screen) {
151 QString nameWas = screen->name();
152 // Transform the fake screen into a physical screen
153 screen->setOutput(outputId: output.output, outputInfo: outputInfo.get());
154 updateScreen(screen, outputChange: output);
155 qCDebug(lcQpaScreen) << "output" << screen->name()
156 << "is connected and enabled; was fake:" << nameWas;
157 } else {
158 screen = createScreen(virtualDesktop, outputChange: output, outputInfo: outputInfo.get());
159 qCDebug(lcQpaScreen) << "output" << screen->name() << "is connected and enabled";
160 }
161 QHighDpiScaling::updateHighDpiScaling();
162 }
163 } else if (screen) {
164 if (output.crtc == XCB_NONE && output.mode == XCB_NONE) {
165 // Screen has been disabled
166 auto outputInfo = Q_XCB_REPLY(xcb_randr_get_output_info, xcb_connection(),
167 output.output, output.config_timestamp);
168 if (outputInfo->crtc == XCB_NONE) {
169 qCDebug(lcQpaScreen) << "output" << screen->name() << "has been disabled";
170 destroyScreen(screen);
171 } else {
172 qCDebug(lcQpaScreen) << "output" << screen->name() << "has been temporarily disabled for the mode switch";
173 // Reset crtc to skip RRCrtcChangeNotify events,
174 // because they may be invalid in the middle of the mode switch
175 screen->setCrtc(XCB_NONE);
176 }
177 } else {
178 updateScreen(screen, outputChange: output);
179 qCDebug(lcQpaScreen) << "output has changed" << screen;
180 }
181 }
182
183 qCDebug(lcQpaScreen) << "primary output is" << qAsConst(t&: m_screens).first()->name();
184 }
185}
186
187bool QXcbConnection::checkOutputIsPrimary(xcb_window_t rootWindow, xcb_randr_output_t output)
188{
189 auto primary = Q_XCB_REPLY(xcb_randr_get_output_primary, xcb_connection(), rootWindow);
190 if (!primary)
191 qWarning(msg: "failed to get the primary output of the screen");
192
193 const bool isPrimary = primary ? (primary->output == output) : false;
194
195 return isPrimary;
196}
197
198void QXcbConnection::updateScreen(QXcbScreen *screen, const xcb_randr_output_change_t &outputChange)
199{
200 screen->setCrtc(outputChange.crtc); // Set the new crtc, because it can be invalid
201 screen->updateGeometry(timestamp: outputChange.config_timestamp);
202 if (screen->mode() != outputChange.mode)
203 screen->updateRefreshRate(mode: outputChange.mode);
204 // Only screen which belongs to the primary virtual desktop can be a primary screen
205 if (screen->screenNumber() == primaryScreenNumber()) {
206 if (!screen->isPrimary() && checkOutputIsPrimary(rootWindow: outputChange.window, output: outputChange.output)) {
207 screen->setPrimary(true);
208
209 // If the screen became primary, reshuffle the order in QGuiApplicationPrivate
210 const int idx = m_screens.indexOf(t: screen);
211 if (idx > 0) {
212 qAsConst(t&: m_screens).first()->setPrimary(false);
213 m_screens.swapItemsAt(i: 0, j: idx);
214 }
215 screen->virtualDesktop()->setPrimaryScreen(screen);
216 QWindowSystemInterface::handlePrimaryScreenChanged(newPrimary: screen);
217 }
218 }
219}
220
221QXcbScreen *QXcbConnection::createScreen(QXcbVirtualDesktop *virtualDesktop,
222 const xcb_randr_output_change_t &outputChange,
223 xcb_randr_get_output_info_reply_t *outputInfo)
224{
225 QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, outputChange.output, outputInfo);
226 // Only screen which belongs to the primary virtual desktop can be a primary screen
227 if (screen->screenNumber() == primaryScreenNumber())
228 screen->setPrimary(checkOutputIsPrimary(rootWindow: outputChange.window, output: outputChange.output));
229
230 if (screen->isPrimary()) {
231 if (!m_screens.isEmpty())
232 qAsConst(t&: m_screens).first()->setPrimary(false);
233
234 m_screens.prepend(t: screen);
235 } else {
236 m_screens.append(t: screen);
237 }
238 virtualDesktop->addScreen(s: screen);
239 QWindowSystemInterface::handleScreenAdded(screen, isPrimary: screen->isPrimary());
240
241 return screen;
242}
243
244void QXcbConnection::destroyScreen(QXcbScreen *screen)
245{
246 QXcbVirtualDesktop *virtualDesktop = screen->virtualDesktop();
247 if (virtualDesktop->screens().count() == 1) {
248 // If there are no other screens on the same virtual desktop,
249 // then transform the physical screen into a fake screen.
250 const QString nameWas = screen->name();
251 screen->setOutput(XCB_NONE, outputInfo: nullptr);
252 qCDebug(lcQpaScreen) << "transformed" << nameWas << "to fake" << screen;
253 } else {
254 // There is more than one screen on the same virtual desktop, remove the screen
255 m_screens.removeOne(t: screen);
256 virtualDesktop->removeScreen(s: screen);
257
258 // When primary screen is removed, set the new primary screen
259 // which belongs to the primary virtual desktop.
260 if (screen->isPrimary()) {
261 QXcbScreen *newPrimary = static_cast<QXcbScreen *>(virtualDesktop->screens().at(i: 0));
262 newPrimary->setPrimary(true);
263 const int idx = m_screens.indexOf(t: newPrimary);
264 if (idx > 0)
265 m_screens.swapItemsAt(i: 0, j: idx);
266 QWindowSystemInterface::handlePrimaryScreenChanged(newPrimary);
267 }
268
269 QWindowSystemInterface::handleScreenRemoved(screen);
270 }
271}
272
273void QXcbConnection::initializeScreens()
274{
275 xcb_screen_iterator_t it = xcb_setup_roots_iterator(R: setup());
276 int xcbScreenNumber = 0; // screen number in the xcb sense
277 QXcbScreen *primaryScreen = nullptr;
278 while (it.rem) {
279 // Each "screen" in xcb terminology is a virtual desktop,
280 // potentially a collection of separate juxtaposed monitors.
281 // But we want a separate QScreen for each output (e.g. DVI-I-1, VGA-1, etc.)
282 // which will become virtual siblings.
283 xcb_screen_t *xcbScreen = it.data;
284 QXcbVirtualDesktop *virtualDesktop = new QXcbVirtualDesktop(this, xcbScreen, xcbScreenNumber);
285 m_virtualDesktops.append(t: virtualDesktop);
286 QList<QPlatformScreen *> siblings;
287 if (hasXRandr()) {
288 // RRGetScreenResourcesCurrent is fast but it may return nothing if the
289 // configuration is not initialized wrt to the hardware. We should call
290 // RRGetScreenResources in this case.
291 auto resources_current = Q_XCB_REPLY(xcb_randr_get_screen_resources_current,
292 xcb_connection(), xcbScreen->root);
293 decltype(Q_XCB_REPLY(xcb_randr_get_screen_resources,
294 xcb_connection(), xcbScreen->root)) resources;
295 if (!resources_current) {
296 qWarning(msg: "failed to get the current screen resources");
297 } else {
298 xcb_timestamp_t timestamp = 0;
299 xcb_randr_output_t *outputs = nullptr;
300 int outputCount = xcb_randr_get_screen_resources_current_outputs_length(R: resources_current.get());
301 if (outputCount) {
302 timestamp = resources_current->config_timestamp;
303 outputs = xcb_randr_get_screen_resources_current_outputs(R: resources_current.get());
304 } else {
305 resources = Q_XCB_REPLY(xcb_randr_get_screen_resources,
306 xcb_connection(), xcbScreen->root);
307 if (!resources) {
308 qWarning(msg: "failed to get the screen resources");
309 } else {
310 timestamp = resources->config_timestamp;
311 outputCount = xcb_randr_get_screen_resources_outputs_length(R: resources.get());
312 outputs = xcb_randr_get_screen_resources_outputs(R: resources.get());
313 }
314 }
315
316 if (outputCount) {
317 auto primary = Q_XCB_REPLY(xcb_randr_get_output_primary, xcb_connection(), xcbScreen->root);
318 if (!primary) {
319 qWarning(msg: "failed to get the primary output of the screen");
320 } else {
321 for (int i = 0; i < outputCount; i++) {
322 auto output = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_output_info,
323 xcb_connection(), outputs[i], timestamp);
324 // Invalid, disconnected or disabled output
325 if (!output)
326 continue;
327
328 if (output->connection != XCB_RANDR_CONNECTION_CONNECTED) {
329 qCDebug(lcQpaScreen, "Output %s is not connected", qPrintable(
330 QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.get()),
331 xcb_randr_get_output_info_name_length(output.get()))));
332 continue;
333 }
334
335 if (output->crtc == XCB_NONE) {
336 qCDebug(lcQpaScreen, "Output %s is not enabled", qPrintable(
337 QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.get()),
338 xcb_randr_get_output_info_name_length(output.get()))));
339 continue;
340 }
341
342 QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, outputs[i], output.get());
343 siblings << screen;
344 m_screens << screen;
345
346 // There can be multiple outputs per screen, use either
347 // the first or an exact match. An exact match isn't
348 // always available if primary->output is XCB_NONE
349 // or currently disconnected output.
350 if (primaryScreenNumber() == xcbScreenNumber) {
351 if (!primaryScreen || (primary && outputs[i] == primary->output)) {
352 if (primaryScreen)
353 primaryScreen->setPrimary(false);
354 primaryScreen = screen;
355 primaryScreen->setPrimary(true);
356 siblings.prepend(t: siblings.takeLast());
357 }
358 }
359 }
360 }
361 }
362 }
363 } else if (hasXinerama()) {
364 // Xinerama is available
365 auto screens = Q_XCB_REPLY(xcb_xinerama_query_screens, xcb_connection());
366 if (screens) {
367 xcb_xinerama_screen_info_iterator_t it = xcb_xinerama_query_screens_screen_info_iterator(R: screens.get());
368 while (it.rem) {
369 xcb_xinerama_screen_info_t *screen_info = it.data;
370 QXcbScreen *screen = new QXcbScreen(this, virtualDesktop,
371 XCB_NONE, nullptr,
372 screen_info, it.index);
373 siblings << screen;
374 m_screens << screen;
375 xcb_xinerama_screen_info_next(i: &it);
376 }
377 }
378 }
379 if (siblings.isEmpty()) {
380 // If there are no XRandR outputs or XRandR extension is missing,
381 // then create a fake/legacy screen.
382 QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, XCB_NONE, nullptr);
383 qCDebug(lcQpaScreen) << "created fake screen" << screen;
384 m_screens << screen;
385 if (primaryScreenNumber() == xcbScreenNumber) {
386 primaryScreen = screen;
387 primaryScreen->setPrimary(true);
388 }
389 siblings << screen;
390 }
391 virtualDesktop->setScreens(std::move(siblings));
392 xcb_screen_next(i: &it);
393 ++xcbScreenNumber;
394 } // for each xcb screen
395
396 for (QXcbVirtualDesktop *virtualDesktop : qAsConst(t&: m_virtualDesktops))
397 virtualDesktop->subscribeToXFixesSelectionNotify();
398
399 if (m_virtualDesktops.isEmpty()) {
400 qFatal(msg: "QXcbConnection: no screens available");
401 } else {
402 // Ensure the primary screen is first on the list
403 if (primaryScreen) {
404 if (qAsConst(t&: m_screens).first() != primaryScreen) {
405 m_screens.removeOne(t: primaryScreen);
406 m_screens.prepend(t: primaryScreen);
407 }
408 }
409
410 // Push the screens to QGuiApplication
411 for (QXcbScreen *screen : qAsConst(t&: m_screens)) {
412 qCDebug(lcQpaScreen) << "adding" << screen << "(Primary:" << screen->isPrimary() << ")";
413 QWindowSystemInterface::handleScreenAdded(screen, isPrimary: screen->isPrimary());
414 }
415
416 qCDebug(lcQpaScreen) << "primary output is" << qAsConst(t&: m_screens).first()->name();
417 }
418}
419

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