xcb: qxcbconnection_screens
Moved all screen handling method implementations into qxcbconnection_screens, the same way we are doing with xinput2 code in qxcbconnection_xi.cpp. The goal was to reduce the size of qxcbconnection.h/cpp. Change-Id: I9bad55ca4b0874171b7313d923b13c66034c3b3e Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>bb10
parent
d67214302f
commit
67c3c0f240
|
|
@ -38,12 +38,10 @@
|
|||
****************************************************************************/
|
||||
|
||||
#include <QtGui/private/qguiapplication_p.h>
|
||||
#include <QtGui/private/qhighdpiscaling_p.h>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include "qxcbconnection.h"
|
||||
#include "qxcbkeyboard.h"
|
||||
#include "qxcbscreen.h"
|
||||
#include "qxcbwindow.h"
|
||||
#include "qxcbclipboard.h"
|
||||
#if QT_CONFIG(draganddrop)
|
||||
|
|
@ -67,7 +65,6 @@
|
|||
#include <errno.h>
|
||||
|
||||
#include <xcb/xfixes.h>
|
||||
#include <xcb/xinerama.h>
|
||||
#if QT_CONFIG(xkb)
|
||||
#define explicit dont_use_cxx_explicit
|
||||
#include <xcb/xkb.h>
|
||||
|
|
@ -95,373 +92,6 @@ Q_LOGGING_CATEGORY(lcQpaXDnd, "qt.qpa.xdnd")
|
|||
#define XCB_GE_GENERIC 35
|
||||
#endif
|
||||
|
||||
void QXcbConnection::selectXRandrEvents()
|
||||
{
|
||||
xcb_screen_iterator_t rootIter = xcb_setup_roots_iterator(setup());
|
||||
for (; rootIter.rem; xcb_screen_next(&rootIter)) {
|
||||
xcb_randr_select_input(xcb_connection(),
|
||||
rootIter.data->root,
|
||||
XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
|
||||
XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
|
||||
XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
|
||||
XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
QXcbScreen* QXcbConnection::findScreenForCrtc(xcb_window_t rootWindow, xcb_randr_crtc_t crtc) const
|
||||
{
|
||||
for (QXcbScreen *screen : m_screens) {
|
||||
if (screen->root() == rootWindow && screen->crtc() == crtc)
|
||||
return screen;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
QXcbScreen* QXcbConnection::findScreenForOutput(xcb_window_t rootWindow, xcb_randr_output_t output) const
|
||||
{
|
||||
for (QXcbScreen *screen : m_screens) {
|
||||
if (screen->root() == rootWindow && screen->output() == output)
|
||||
return screen;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
QXcbVirtualDesktop* QXcbConnection::virtualDesktopForRootWindow(xcb_window_t rootWindow) const
|
||||
{
|
||||
for (QXcbVirtualDesktop *virtualDesktop : m_virtualDesktops) {
|
||||
if (virtualDesktop->screen()->root == rootWindow)
|
||||
return virtualDesktop;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Synchronizes the screen list, adds new screens, removes deleted ones
|
||||
*/
|
||||
void QXcbConnection::updateScreens(const xcb_randr_notify_event_t *event)
|
||||
{
|
||||
if (event->subCode == XCB_RANDR_NOTIFY_CRTC_CHANGE) {
|
||||
xcb_randr_crtc_change_t crtc = event->u.cc;
|
||||
QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(crtc.window);
|
||||
if (!virtualDesktop)
|
||||
// Not for us
|
||||
return;
|
||||
|
||||
QXcbScreen *screen = findScreenForCrtc(crtc.window, crtc.crtc);
|
||||
qCDebug(lcQpaScreen) << "QXcbConnection: XCB_RANDR_NOTIFY_CRTC_CHANGE:" << crtc.crtc
|
||||
<< "mode" << crtc.mode << "relevant screen" << screen;
|
||||
// Only update geometry when there's a valid mode on the CRTC
|
||||
// CRTC with node mode could mean that output has been disabled, and we'll
|
||||
// get RRNotifyOutputChange notification for that.
|
||||
if (screen && crtc.mode) {
|
||||
if (crtc.rotation == XCB_RANDR_ROTATION_ROTATE_90 ||
|
||||
crtc.rotation == XCB_RANDR_ROTATION_ROTATE_270)
|
||||
std::swap(crtc.width, crtc.height);
|
||||
screen->updateGeometry(QRect(crtc.x, crtc.y, crtc.width, crtc.height), crtc.rotation);
|
||||
if (screen->mode() != crtc.mode)
|
||||
screen->updateRefreshRate(crtc.mode);
|
||||
}
|
||||
|
||||
} else if (event->subCode == XCB_RANDR_NOTIFY_OUTPUT_CHANGE) {
|
||||
xcb_randr_output_change_t output = event->u.oc;
|
||||
QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(output.window);
|
||||
if (!virtualDesktop)
|
||||
// Not for us
|
||||
return;
|
||||
|
||||
QXcbScreen *screen = findScreenForOutput(output.window, output.output);
|
||||
qCDebug(lcQpaScreen) << "QXcbConnection: XCB_RANDR_NOTIFY_OUTPUT_CHANGE:" << output.output;
|
||||
|
||||
if (screen && output.connection == XCB_RANDR_CONNECTION_DISCONNECTED) {
|
||||
qCDebug(lcQpaScreen) << "screen" << screen->name() << "has been disconnected";
|
||||
destroyScreen(screen);
|
||||
} else if (!screen && output.connection == XCB_RANDR_CONNECTION_CONNECTED) {
|
||||
// New XRandR output is available and it's enabled
|
||||
if (output.crtc != XCB_NONE && output.mode != XCB_NONE) {
|
||||
auto outputInfo = Q_XCB_REPLY(xcb_randr_get_output_info, xcb_connection(),
|
||||
output.output, output.config_timestamp);
|
||||
// Find a fake screen
|
||||
const auto scrs = virtualDesktop->screens();
|
||||
for (QPlatformScreen *scr : scrs) {
|
||||
QXcbScreen *xcbScreen = static_cast<QXcbScreen *>(scr);
|
||||
if (xcbScreen->output() == XCB_NONE) {
|
||||
screen = xcbScreen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (screen) {
|
||||
QString nameWas = screen->name();
|
||||
// Transform the fake screen into a physical screen
|
||||
screen->setOutput(output.output, outputInfo.get());
|
||||
updateScreen(screen, output);
|
||||
qCDebug(lcQpaScreen) << "output" << screen->name()
|
||||
<< "is connected and enabled; was fake:" << nameWas;
|
||||
} else {
|
||||
screen = createScreen(virtualDesktop, output, outputInfo.get());
|
||||
qCDebug(lcQpaScreen) << "output" << screen->name() << "is connected and enabled";
|
||||
}
|
||||
QHighDpiScaling::updateHighDpiScaling();
|
||||
}
|
||||
} else if (screen) {
|
||||
if (output.crtc == XCB_NONE && output.mode == XCB_NONE) {
|
||||
// Screen has been disabled
|
||||
auto outputInfo = Q_XCB_REPLY(xcb_randr_get_output_info, xcb_connection(),
|
||||
output.output, output.config_timestamp);
|
||||
if (outputInfo->crtc == XCB_NONE) {
|
||||
qCDebug(lcQpaScreen) << "output" << screen->name() << "has been disabled";
|
||||
destroyScreen(screen);
|
||||
} else {
|
||||
qCDebug(lcQpaScreen) << "output" << screen->name() << "has been temporarily disabled for the mode switch";
|
||||
// Reset crtc to skip RRCrtcChangeNotify events,
|
||||
// because they may be invalid in the middle of the mode switch
|
||||
screen->setCrtc(XCB_NONE);
|
||||
}
|
||||
} else {
|
||||
updateScreen(screen, output);
|
||||
qCDebug(lcQpaScreen) << "output has changed" << screen;
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(lcQpaScreen) << "primary output is" << qAsConst(m_screens).first()->name();
|
||||
}
|
||||
}
|
||||
|
||||
bool QXcbConnection::checkOutputIsPrimary(xcb_window_t rootWindow, xcb_randr_output_t output)
|
||||
{
|
||||
auto primary = Q_XCB_REPLY(xcb_randr_get_output_primary, xcb_connection(), rootWindow);
|
||||
if (!primary)
|
||||
qWarning("failed to get the primary output of the screen");
|
||||
|
||||
const bool isPrimary = primary ? (primary->output == output) : false;
|
||||
|
||||
return isPrimary;
|
||||
}
|
||||
|
||||
void QXcbConnection::updateScreen(QXcbScreen *screen, const xcb_randr_output_change_t &outputChange)
|
||||
{
|
||||
screen->setCrtc(outputChange.crtc); // Set the new crtc, because it can be invalid
|
||||
screen->updateGeometry(outputChange.config_timestamp);
|
||||
if (screen->mode() != outputChange.mode)
|
||||
screen->updateRefreshRate(outputChange.mode);
|
||||
// Only screen which belongs to the primary virtual desktop can be a primary screen
|
||||
if (screen->screenNumber() == primaryScreenNumber()) {
|
||||
if (!screen->isPrimary() && checkOutputIsPrimary(outputChange.window, outputChange.output)) {
|
||||
screen->setPrimary(true);
|
||||
|
||||
// If the screen became primary, reshuffle the order in QGuiApplicationPrivate
|
||||
const int idx = m_screens.indexOf(screen);
|
||||
if (idx > 0) {
|
||||
qAsConst(m_screens).first()->setPrimary(false);
|
||||
m_screens.swap(0, idx);
|
||||
}
|
||||
screen->virtualDesktop()->setPrimaryScreen(screen);
|
||||
QXcbIntegration::instance()->setPrimaryScreen(screen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QXcbScreen *QXcbConnection::createScreen(QXcbVirtualDesktop *virtualDesktop,
|
||||
const xcb_randr_output_change_t &outputChange,
|
||||
xcb_randr_get_output_info_reply_t *outputInfo)
|
||||
{
|
||||
QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, outputChange.output, outputInfo);
|
||||
// Only screen which belongs to the primary virtual desktop can be a primary screen
|
||||
if (screen->screenNumber() == primaryScreenNumber())
|
||||
screen->setPrimary(checkOutputIsPrimary(outputChange.window, outputChange.output));
|
||||
|
||||
if (screen->isPrimary()) {
|
||||
if (!m_screens.isEmpty())
|
||||
qAsConst(m_screens).first()->setPrimary(false);
|
||||
|
||||
m_screens.prepend(screen);
|
||||
} else {
|
||||
m_screens.append(screen);
|
||||
}
|
||||
virtualDesktop->addScreen(screen);
|
||||
QXcbIntegration::instance()->screenAdded(screen, screen->isPrimary());
|
||||
|
||||
return screen;
|
||||
}
|
||||
|
||||
void QXcbConnection::destroyScreen(QXcbScreen *screen)
|
||||
{
|
||||
QXcbVirtualDesktop *virtualDesktop = screen->virtualDesktop();
|
||||
if (virtualDesktop->screens().count() == 1) {
|
||||
// If there are no other screens on the same virtual desktop,
|
||||
// then transform the physical screen into a fake screen.
|
||||
const QString nameWas = screen->name();
|
||||
screen->setOutput(XCB_NONE, nullptr);
|
||||
qCDebug(lcQpaScreen) << "transformed" << nameWas << "to fake" << screen;
|
||||
} else {
|
||||
// There is more than one screen on the same virtual desktop, remove the screen
|
||||
m_screens.removeOne(screen);
|
||||
virtualDesktop->removeScreen(screen);
|
||||
|
||||
// When primary screen is removed, set the new primary screen
|
||||
// which belongs to the primary virtual desktop.
|
||||
if (screen->isPrimary()) {
|
||||
QXcbScreen *newPrimary = static_cast<QXcbScreen *>(virtualDesktop->screens().at(0));
|
||||
newPrimary->setPrimary(true);
|
||||
const int idx = m_screens.indexOf(newPrimary);
|
||||
if (idx > 0)
|
||||
m_screens.swap(0, idx);
|
||||
QXcbIntegration::instance()->setPrimaryScreen(newPrimary);
|
||||
}
|
||||
|
||||
QXcbIntegration::instance()->destroyScreen(screen);
|
||||
}
|
||||
}
|
||||
|
||||
void QXcbConnection::initializeScreens()
|
||||
{
|
||||
xcb_screen_iterator_t it = xcb_setup_roots_iterator(setup());
|
||||
int xcbScreenNumber = 0; // screen number in the xcb sense
|
||||
QXcbScreen *primaryScreen = nullptr;
|
||||
while (it.rem) {
|
||||
// Each "screen" in xcb terminology is a virtual desktop,
|
||||
// potentially a collection of separate juxtaposed monitors.
|
||||
// But we want a separate QScreen for each output (e.g. DVI-I-1, VGA-1, etc.)
|
||||
// which will become virtual siblings.
|
||||
xcb_screen_t *xcbScreen = it.data;
|
||||
QXcbVirtualDesktop *virtualDesktop = new QXcbVirtualDesktop(this, xcbScreen, xcbScreenNumber);
|
||||
m_virtualDesktops.append(virtualDesktop);
|
||||
QList<QPlatformScreen *> siblings;
|
||||
if (hasXRender()) {
|
||||
// RRGetScreenResourcesCurrent is fast but it may return nothing if the
|
||||
// configuration is not initialized wrt to the hardware. We should call
|
||||
// RRGetScreenResources in this case.
|
||||
auto resources_current = Q_XCB_REPLY(xcb_randr_get_screen_resources_current,
|
||||
xcb_connection(), xcbScreen->root);
|
||||
if (!resources_current) {
|
||||
qWarning("failed to get the current screen resources");
|
||||
} else {
|
||||
xcb_timestamp_t timestamp = 0;
|
||||
xcb_randr_output_t *outputs = nullptr;
|
||||
int outputCount = xcb_randr_get_screen_resources_current_outputs_length(resources_current.get());
|
||||
if (outputCount) {
|
||||
timestamp = resources_current->config_timestamp;
|
||||
outputs = xcb_randr_get_screen_resources_current_outputs(resources_current.get());
|
||||
} else {
|
||||
auto resources = Q_XCB_REPLY(xcb_randr_get_screen_resources,
|
||||
xcb_connection(), xcbScreen->root);
|
||||
if (!resources) {
|
||||
qWarning("failed to get the screen resources");
|
||||
} else {
|
||||
timestamp = resources->config_timestamp;
|
||||
outputCount = xcb_randr_get_screen_resources_outputs_length(resources.get());
|
||||
outputs = xcb_randr_get_screen_resources_outputs(resources.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (outputCount) {
|
||||
auto primary = Q_XCB_REPLY(xcb_randr_get_output_primary, xcb_connection(), xcbScreen->root);
|
||||
if (!primary) {
|
||||
qWarning("failed to get the primary output of the screen");
|
||||
} else {
|
||||
for (int i = 0; i < outputCount; i++) {
|
||||
auto output = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_output_info,
|
||||
xcb_connection(), outputs[i], timestamp);
|
||||
// Invalid, disconnected or disabled output
|
||||
if (!output)
|
||||
continue;
|
||||
|
||||
if (output->connection != XCB_RANDR_CONNECTION_CONNECTED) {
|
||||
qCDebug(lcQpaScreen, "Output %s is not connected", qPrintable(
|
||||
QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.get()),
|
||||
xcb_randr_get_output_info_name_length(output.get()))));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (output->crtc == XCB_NONE) {
|
||||
qCDebug(lcQpaScreen, "Output %s is not enabled", qPrintable(
|
||||
QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.get()),
|
||||
xcb_randr_get_output_info_name_length(output.get()))));
|
||||
continue;
|
||||
}
|
||||
|
||||
QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, outputs[i], output.get());
|
||||
siblings << screen;
|
||||
m_screens << screen;
|
||||
|
||||
// There can be multiple outputs per screen, use either
|
||||
// the first or an exact match. An exact match isn't
|
||||
// always available if primary->output is XCB_NONE
|
||||
// or currently disconnected output.
|
||||
if (primaryScreenNumber() == xcbScreenNumber) {
|
||||
if (!primaryScreen || (primary && outputs[i] == primary->output)) {
|
||||
if (primaryScreen)
|
||||
primaryScreen->setPrimary(false);
|
||||
primaryScreen = screen;
|
||||
primaryScreen->setPrimary(true);
|
||||
siblings.prepend(siblings.takeLast());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (hasXinerama()) {
|
||||
// Xinerama is available
|
||||
auto screens = Q_XCB_REPLY(xcb_xinerama_query_screens, xcb_connection());
|
||||
if (screens) {
|
||||
xcb_xinerama_screen_info_iterator_t it = xcb_xinerama_query_screens_screen_info_iterator(screens.get());
|
||||
while (it.rem) {
|
||||
xcb_xinerama_screen_info_t *screen_info = it.data;
|
||||
QXcbScreen *screen = new QXcbScreen(this, virtualDesktop,
|
||||
XCB_NONE, nullptr,
|
||||
screen_info, it.index);
|
||||
siblings << screen;
|
||||
m_screens << screen;
|
||||
xcb_xinerama_screen_info_next(&it);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (siblings.isEmpty()) {
|
||||
// If there are no XRandR outputs or XRandR extension is missing,
|
||||
// then create a fake/legacy screen.
|
||||
QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, XCB_NONE, nullptr);
|
||||
qCDebug(lcQpaScreen) << "created fake screen" << screen;
|
||||
m_screens << screen;
|
||||
if (primaryScreenNumber() == xcbScreenNumber) {
|
||||
primaryScreen = screen;
|
||||
primaryScreen->setPrimary(true);
|
||||
}
|
||||
siblings << screen;
|
||||
}
|
||||
virtualDesktop->setScreens(siblings);
|
||||
xcb_screen_next(&it);
|
||||
++xcbScreenNumber;
|
||||
} // for each xcb screen
|
||||
|
||||
for (QXcbVirtualDesktop *virtualDesktop : qAsConst(m_virtualDesktops))
|
||||
virtualDesktop->subscribeToXFixesSelectionNotify();
|
||||
|
||||
if (m_virtualDesktops.isEmpty()) {
|
||||
qFatal("QXcbConnection: no screens available");
|
||||
} else {
|
||||
// Ensure the primary screen is first on the list
|
||||
if (primaryScreen) {
|
||||
if (qAsConst(m_screens).first() != primaryScreen) {
|
||||
m_screens.removeOne(primaryScreen);
|
||||
m_screens.prepend(primaryScreen);
|
||||
}
|
||||
}
|
||||
|
||||
// Push the screens to QGuiApplication
|
||||
for (QXcbScreen *screen : qAsConst(m_screens)) {
|
||||
qCDebug(lcQpaScreen) << "adding" << screen << "(Primary:" << screen->isPrimary() << ")";
|
||||
QXcbIntegration::instance()->screenAdded(screen, screen->isPrimary());
|
||||
}
|
||||
|
||||
qCDebug(lcQpaScreen) << "primary output is" << qAsConst(m_screens).first()->name();
|
||||
}
|
||||
}
|
||||
|
||||
QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGrabServer, xcb_visualid_t defaultVisualId, const char *displayName)
|
||||
: QXcbBasicConnection(displayName)
|
||||
, m_canGrabServer(canGrabServer)
|
||||
|
|
@ -475,8 +105,6 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGra
|
|||
|
||||
m_xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP").toLower();
|
||||
|
||||
selectXRandrEvents();
|
||||
|
||||
initializeScreens();
|
||||
|
||||
#if QT_CONFIG(xcb_xinput)
|
||||
|
|
|
|||
|
|
@ -330,7 +330,15 @@ private:
|
|||
ScrollingDevice *scrollingDeviceForId(int id);
|
||||
|
||||
static bool xi2GetValuatorValueIfSet(const void *event, int valuatorNum, double *value);
|
||||
#endif
|
||||
|
||||
QHash<int, TouchDeviceData> m_touchDevices;
|
||||
struct StartSystemMoveResizeInfo {
|
||||
xcb_window_t window = XCB_NONE;
|
||||
uint16_t deviceid;
|
||||
uint32_t pointid;
|
||||
int corner;
|
||||
} m_startSystemMoveResizeInfo;
|
||||
#endif // QT_CONFIG(xcb_xinput)
|
||||
|
||||
const bool m_canGrabServer;
|
||||
const xcb_visualid_t m_defaultVisualId;
|
||||
|
|
@ -353,15 +361,6 @@ private:
|
|||
|
||||
QXcbEventQueue *m_eventQueue = nullptr;
|
||||
|
||||
#if QT_CONFIG(xcb_xinput)
|
||||
QHash<int, TouchDeviceData> m_touchDevices;
|
||||
struct StartSystemMoveResizeInfo {
|
||||
xcb_window_t window = XCB_NONE;
|
||||
uint16_t deviceid;
|
||||
uint32_t pointid;
|
||||
int corner;
|
||||
} m_startSystemMoveResizeInfo;
|
||||
#endif
|
||||
WindowMapper m_mapper;
|
||||
|
||||
QVector<PeekFunc> m_peekFuncs;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,416 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
#include "qxcbconnection.h"
|
||||
#include "qxcbscreen.h"
|
||||
#include "qxcbintegration.h"
|
||||
|
||||
#include <QtGui/private/qhighdpiscaling_p.h>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QList>
|
||||
|
||||
#include <xcb/xinerama.h>
|
||||
|
||||
void QXcbConnection::selectXRandrEvents()
|
||||
{
|
||||
xcb_screen_iterator_t rootIter = xcb_setup_roots_iterator(setup());
|
||||
for (; rootIter.rem; xcb_screen_next(&rootIter)) {
|
||||
xcb_randr_select_input(xcb_connection(),
|
||||
rootIter.data->root,
|
||||
XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
|
||||
XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
|
||||
XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
|
||||
XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
QXcbScreen* QXcbConnection::findScreenForCrtc(xcb_window_t rootWindow, xcb_randr_crtc_t crtc) const
|
||||
{
|
||||
for (QXcbScreen *screen : m_screens) {
|
||||
if (screen->root() == rootWindow && screen->crtc() == crtc)
|
||||
return screen;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QXcbScreen* QXcbConnection::findScreenForOutput(xcb_window_t rootWindow, xcb_randr_output_t output) const
|
||||
{
|
||||
for (QXcbScreen *screen : m_screens) {
|
||||
if (screen->root() == rootWindow && screen->output() == output)
|
||||
return screen;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QXcbVirtualDesktop* QXcbConnection::virtualDesktopForRootWindow(xcb_window_t rootWindow) const
|
||||
{
|
||||
for (QXcbVirtualDesktop *virtualDesktop : m_virtualDesktops) {
|
||||
if (virtualDesktop->screen()->root == rootWindow)
|
||||
return virtualDesktop;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Synchronizes the screen list, adds new screens, removes deleted ones
|
||||
*/
|
||||
void QXcbConnection::updateScreens(const xcb_randr_notify_event_t *event)
|
||||
{
|
||||
if (event->subCode == XCB_RANDR_NOTIFY_CRTC_CHANGE) {
|
||||
xcb_randr_crtc_change_t crtc = event->u.cc;
|
||||
QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(crtc.window);
|
||||
if (!virtualDesktop)
|
||||
// Not for us
|
||||
return;
|
||||
|
||||
QXcbScreen *screen = findScreenForCrtc(crtc.window, crtc.crtc);
|
||||
qCDebug(lcQpaScreen) << "QXcbConnection: XCB_RANDR_NOTIFY_CRTC_CHANGE:" << crtc.crtc
|
||||
<< "mode" << crtc.mode << "relevant screen" << screen;
|
||||
// Only update geometry when there's a valid mode on the CRTC
|
||||
// CRTC with node mode could mean that output has been disabled, and we'll
|
||||
// get RRNotifyOutputChange notification for that.
|
||||
if (screen && crtc.mode) {
|
||||
if (crtc.rotation == XCB_RANDR_ROTATION_ROTATE_90 ||
|
||||
crtc.rotation == XCB_RANDR_ROTATION_ROTATE_270)
|
||||
std::swap(crtc.width, crtc.height);
|
||||
screen->updateGeometry(QRect(crtc.x, crtc.y, crtc.width, crtc.height), crtc.rotation);
|
||||
if (screen->mode() != crtc.mode)
|
||||
screen->updateRefreshRate(crtc.mode);
|
||||
}
|
||||
|
||||
} else if (event->subCode == XCB_RANDR_NOTIFY_OUTPUT_CHANGE) {
|
||||
xcb_randr_output_change_t output = event->u.oc;
|
||||
QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(output.window);
|
||||
if (!virtualDesktop)
|
||||
// Not for us
|
||||
return;
|
||||
|
||||
QXcbScreen *screen = findScreenForOutput(output.window, output.output);
|
||||
qCDebug(lcQpaScreen) << "QXcbConnection: XCB_RANDR_NOTIFY_OUTPUT_CHANGE:" << output.output;
|
||||
|
||||
if (screen && output.connection == XCB_RANDR_CONNECTION_DISCONNECTED) {
|
||||
qCDebug(lcQpaScreen) << "screen" << screen->name() << "has been disconnected";
|
||||
destroyScreen(screen);
|
||||
} else if (!screen && output.connection == XCB_RANDR_CONNECTION_CONNECTED) {
|
||||
// New XRandR output is available and it's enabled
|
||||
if (output.crtc != XCB_NONE && output.mode != XCB_NONE) {
|
||||
auto outputInfo = Q_XCB_REPLY(xcb_randr_get_output_info, xcb_connection(),
|
||||
output.output, output.config_timestamp);
|
||||
// Find a fake screen
|
||||
const auto scrs = virtualDesktop->screens();
|
||||
for (QPlatformScreen *scr : scrs) {
|
||||
QXcbScreen *xcbScreen = static_cast<QXcbScreen *>(scr);
|
||||
if (xcbScreen->output() == XCB_NONE) {
|
||||
screen = xcbScreen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (screen) {
|
||||
QString nameWas = screen->name();
|
||||
// Transform the fake screen into a physical screen
|
||||
screen->setOutput(output.output, outputInfo.get());
|
||||
updateScreen(screen, output);
|
||||
qCDebug(lcQpaScreen) << "output" << screen->name()
|
||||
<< "is connected and enabled; was fake:" << nameWas;
|
||||
} else {
|
||||
screen = createScreen(virtualDesktop, output, outputInfo.get());
|
||||
qCDebug(lcQpaScreen) << "output" << screen->name() << "is connected and enabled";
|
||||
}
|
||||
QHighDpiScaling::updateHighDpiScaling();
|
||||
}
|
||||
} else if (screen) {
|
||||
if (output.crtc == XCB_NONE && output.mode == XCB_NONE) {
|
||||
// Screen has been disabled
|
||||
auto outputInfo = Q_XCB_REPLY(xcb_randr_get_output_info, xcb_connection(),
|
||||
output.output, output.config_timestamp);
|
||||
if (outputInfo->crtc == XCB_NONE) {
|
||||
qCDebug(lcQpaScreen) << "output" << screen->name() << "has been disabled";
|
||||
destroyScreen(screen);
|
||||
} else {
|
||||
qCDebug(lcQpaScreen) << "output" << screen->name() << "has been temporarily disabled for the mode switch";
|
||||
// Reset crtc to skip RRCrtcChangeNotify events,
|
||||
// because they may be invalid in the middle of the mode switch
|
||||
screen->setCrtc(XCB_NONE);
|
||||
}
|
||||
} else {
|
||||
updateScreen(screen, output);
|
||||
qCDebug(lcQpaScreen) << "output has changed" << screen;
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(lcQpaScreen) << "primary output is" << qAsConst(m_screens).first()->name();
|
||||
}
|
||||
}
|
||||
|
||||
bool QXcbConnection::checkOutputIsPrimary(xcb_window_t rootWindow, xcb_randr_output_t output)
|
||||
{
|
||||
auto primary = Q_XCB_REPLY(xcb_randr_get_output_primary, xcb_connection(), rootWindow);
|
||||
if (!primary)
|
||||
qWarning("failed to get the primary output of the screen");
|
||||
|
||||
const bool isPrimary = primary ? (primary->output == output) : false;
|
||||
|
||||
return isPrimary;
|
||||
}
|
||||
|
||||
void QXcbConnection::updateScreen(QXcbScreen *screen, const xcb_randr_output_change_t &outputChange)
|
||||
{
|
||||
screen->setCrtc(outputChange.crtc); // Set the new crtc, because it can be invalid
|
||||
screen->updateGeometry(outputChange.config_timestamp);
|
||||
if (screen->mode() != outputChange.mode)
|
||||
screen->updateRefreshRate(outputChange.mode);
|
||||
// Only screen which belongs to the primary virtual desktop can be a primary screen
|
||||
if (screen->screenNumber() == primaryScreenNumber()) {
|
||||
if (!screen->isPrimary() && checkOutputIsPrimary(outputChange.window, outputChange.output)) {
|
||||
screen->setPrimary(true);
|
||||
|
||||
// If the screen became primary, reshuffle the order in QGuiApplicationPrivate
|
||||
const int idx = m_screens.indexOf(screen);
|
||||
if (idx > 0) {
|
||||
qAsConst(m_screens).first()->setPrimary(false);
|
||||
m_screens.swap(0, idx);
|
||||
}
|
||||
screen->virtualDesktop()->setPrimaryScreen(screen);
|
||||
QXcbIntegration::instance()->setPrimaryScreen(screen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QXcbScreen *QXcbConnection::createScreen(QXcbVirtualDesktop *virtualDesktop,
|
||||
const xcb_randr_output_change_t &outputChange,
|
||||
xcb_randr_get_output_info_reply_t *outputInfo)
|
||||
{
|
||||
QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, outputChange.output, outputInfo);
|
||||
// Only screen which belongs to the primary virtual desktop can be a primary screen
|
||||
if (screen->screenNumber() == primaryScreenNumber())
|
||||
screen->setPrimary(checkOutputIsPrimary(outputChange.window, outputChange.output));
|
||||
|
||||
if (screen->isPrimary()) {
|
||||
if (!m_screens.isEmpty())
|
||||
qAsConst(m_screens).first()->setPrimary(false);
|
||||
|
||||
m_screens.prepend(screen);
|
||||
} else {
|
||||
m_screens.append(screen);
|
||||
}
|
||||
virtualDesktop->addScreen(screen);
|
||||
QXcbIntegration::instance()->screenAdded(screen, screen->isPrimary());
|
||||
|
||||
return screen;
|
||||
}
|
||||
|
||||
void QXcbConnection::destroyScreen(QXcbScreen *screen)
|
||||
{
|
||||
QXcbVirtualDesktop *virtualDesktop = screen->virtualDesktop();
|
||||
if (virtualDesktop->screens().count() == 1) {
|
||||
// If there are no other screens on the same virtual desktop,
|
||||
// then transform the physical screen into a fake screen.
|
||||
const QString nameWas = screen->name();
|
||||
screen->setOutput(XCB_NONE, nullptr);
|
||||
qCDebug(lcQpaScreen) << "transformed" << nameWas << "to fake" << screen;
|
||||
} else {
|
||||
// There is more than one screen on the same virtual desktop, remove the screen
|
||||
m_screens.removeOne(screen);
|
||||
virtualDesktop->removeScreen(screen);
|
||||
|
||||
// When primary screen is removed, set the new primary screen
|
||||
// which belongs to the primary virtual desktop.
|
||||
if (screen->isPrimary()) {
|
||||
QXcbScreen *newPrimary = static_cast<QXcbScreen *>(virtualDesktop->screens().at(0));
|
||||
newPrimary->setPrimary(true);
|
||||
const int idx = m_screens.indexOf(newPrimary);
|
||||
if (idx > 0)
|
||||
m_screens.swap(0, idx);
|
||||
QXcbIntegration::instance()->setPrimaryScreen(newPrimary);
|
||||
}
|
||||
|
||||
QXcbIntegration::instance()->destroyScreen(screen);
|
||||
}
|
||||
}
|
||||
|
||||
void QXcbConnection::initializeScreens()
|
||||
{
|
||||
selectXRandrEvents();
|
||||
|
||||
xcb_screen_iterator_t it = xcb_setup_roots_iterator(setup());
|
||||
int xcbScreenNumber = 0; // screen number in the xcb sense
|
||||
QXcbScreen *primaryScreen = nullptr;
|
||||
while (it.rem) {
|
||||
// Each "screen" in xcb terminology is a virtual desktop,
|
||||
// potentially a collection of separate juxtaposed monitors.
|
||||
// But we want a separate QScreen for each output (e.g. DVI-I-1, VGA-1, etc.)
|
||||
// which will become virtual siblings.
|
||||
xcb_screen_t *xcbScreen = it.data;
|
||||
QXcbVirtualDesktop *virtualDesktop = new QXcbVirtualDesktop(this, xcbScreen, xcbScreenNumber);
|
||||
m_virtualDesktops.append(virtualDesktop);
|
||||
QList<QPlatformScreen *> siblings;
|
||||
if (hasXRender()) {
|
||||
// RRGetScreenResourcesCurrent is fast but it may return nothing if the
|
||||
// configuration is not initialized wrt to the hardware. We should call
|
||||
// RRGetScreenResources in this case.
|
||||
auto resources_current = Q_XCB_REPLY(xcb_randr_get_screen_resources_current,
|
||||
xcb_connection(), xcbScreen->root);
|
||||
if (!resources_current) {
|
||||
qWarning("failed to get the current screen resources");
|
||||
} else {
|
||||
xcb_timestamp_t timestamp = 0;
|
||||
xcb_randr_output_t *outputs = nullptr;
|
||||
int outputCount = xcb_randr_get_screen_resources_current_outputs_length(resources_current.get());
|
||||
if (outputCount) {
|
||||
timestamp = resources_current->config_timestamp;
|
||||
outputs = xcb_randr_get_screen_resources_current_outputs(resources_current.get());
|
||||
} else {
|
||||
auto resources = Q_XCB_REPLY(xcb_randr_get_screen_resources,
|
||||
xcb_connection(), xcbScreen->root);
|
||||
if (!resources) {
|
||||
qWarning("failed to get the screen resources");
|
||||
} else {
|
||||
timestamp = resources->config_timestamp;
|
||||
outputCount = xcb_randr_get_screen_resources_outputs_length(resources.get());
|
||||
outputs = xcb_randr_get_screen_resources_outputs(resources.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (outputCount) {
|
||||
auto primary = Q_XCB_REPLY(xcb_randr_get_output_primary, xcb_connection(), xcbScreen->root);
|
||||
if (!primary) {
|
||||
qWarning("failed to get the primary output of the screen");
|
||||
} else {
|
||||
for (int i = 0; i < outputCount; i++) {
|
||||
auto output = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_output_info,
|
||||
xcb_connection(), outputs[i], timestamp);
|
||||
// Invalid, disconnected or disabled output
|
||||
if (!output)
|
||||
continue;
|
||||
|
||||
if (output->connection != XCB_RANDR_CONNECTION_CONNECTED) {
|
||||
qCDebug(lcQpaScreen, "Output %s is not connected", qPrintable(
|
||||
QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.get()),
|
||||
xcb_randr_get_output_info_name_length(output.get()))));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (output->crtc == XCB_NONE) {
|
||||
qCDebug(lcQpaScreen, "Output %s is not enabled", qPrintable(
|
||||
QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.get()),
|
||||
xcb_randr_get_output_info_name_length(output.get()))));
|
||||
continue;
|
||||
}
|
||||
|
||||
QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, outputs[i], output.get());
|
||||
siblings << screen;
|
||||
m_screens << screen;
|
||||
|
||||
// There can be multiple outputs per screen, use either
|
||||
// the first or an exact match. An exact match isn't
|
||||
// always available if primary->output is XCB_NONE
|
||||
// or currently disconnected output.
|
||||
if (primaryScreenNumber() == xcbScreenNumber) {
|
||||
if (!primaryScreen || (primary && outputs[i] == primary->output)) {
|
||||
if (primaryScreen)
|
||||
primaryScreen->setPrimary(false);
|
||||
primaryScreen = screen;
|
||||
primaryScreen->setPrimary(true);
|
||||
siblings.prepend(siblings.takeLast());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (hasXinerama()) {
|
||||
// Xinerama is available
|
||||
auto screens = Q_XCB_REPLY(xcb_xinerama_query_screens, xcb_connection());
|
||||
if (screens) {
|
||||
xcb_xinerama_screen_info_iterator_t it = xcb_xinerama_query_screens_screen_info_iterator(screens.get());
|
||||
while (it.rem) {
|
||||
xcb_xinerama_screen_info_t *screen_info = it.data;
|
||||
QXcbScreen *screen = new QXcbScreen(this, virtualDesktop,
|
||||
XCB_NONE, nullptr,
|
||||
screen_info, it.index);
|
||||
siblings << screen;
|
||||
m_screens << screen;
|
||||
xcb_xinerama_screen_info_next(&it);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (siblings.isEmpty()) {
|
||||
// If there are no XRandR outputs or XRandR extension is missing,
|
||||
// then create a fake/legacy screen.
|
||||
QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, XCB_NONE, nullptr);
|
||||
qCDebug(lcQpaScreen) << "created fake screen" << screen;
|
||||
m_screens << screen;
|
||||
if (primaryScreenNumber() == xcbScreenNumber) {
|
||||
primaryScreen = screen;
|
||||
primaryScreen->setPrimary(true);
|
||||
}
|
||||
siblings << screen;
|
||||
}
|
||||
virtualDesktop->setScreens(siblings);
|
||||
xcb_screen_next(&it);
|
||||
++xcbScreenNumber;
|
||||
} // for each xcb screen
|
||||
|
||||
for (QXcbVirtualDesktop *virtualDesktop : qAsConst(m_virtualDesktops))
|
||||
virtualDesktop->subscribeToXFixesSelectionNotify();
|
||||
|
||||
if (m_virtualDesktops.isEmpty()) {
|
||||
qFatal("QXcbConnection: no screens available");
|
||||
} else {
|
||||
// Ensure the primary screen is first on the list
|
||||
if (primaryScreen) {
|
||||
if (qAsConst(m_screens).first() != primaryScreen) {
|
||||
m_screens.removeOne(primaryScreen);
|
||||
m_screens.prepend(primaryScreen);
|
||||
}
|
||||
}
|
||||
|
||||
// Push the screens to QGuiApplication
|
||||
for (QXcbScreen *screen : qAsConst(m_screens)) {
|
||||
qCDebug(lcQpaScreen) << "adding" << screen << "(Primary:" << screen->isPrimary() << ")";
|
||||
QXcbIntegration::instance()->screenAdded(screen, screen->isPrimary());
|
||||
}
|
||||
|
||||
qCDebug(lcQpaScreen) << "primary output is" << qAsConst(m_screens).first()->name();
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ SOURCES = \
|
|||
qxcbeventqueue.cpp \
|
||||
qxcbeventdispatcher.cpp \
|
||||
qxcbconnection_basic.cpp \
|
||||
qxcbconnection_screens.cpp \
|
||||
qxcbatom.cpp
|
||||
|
||||
HEADERS = \
|
||||
|
|
|
|||
Loading…
Reference in New Issue