From 67c3c0f240241340994898d46df7429d3eda4b69 Mon Sep 17 00:00:00 2001 From: Gatis Paeglis Date: Sat, 22 Sep 2018 18:52:54 +0200 Subject: 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 --- src/plugins/platforms/xcb/qxcbconnection.cpp | 372 ------------------ src/plugins/platforms/xcb/qxcbconnection.h | 19 +- .../platforms/xcb/qxcbconnection_screens.cpp | 416 +++++++++++++++++++++ src/plugins/platforms/xcb/xcb_qpa_lib.pro | 1 + 4 files changed, 426 insertions(+), 382 deletions(-) create mode 100644 src/plugins/platforms/xcb/qxcbconnection_screens.cpp (limited to 'src/plugins/platforms') diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp index 10d07c37bb..9e857ea2ff 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection.cpp @@ -38,12 +38,10 @@ ****************************************************************************/ #include -#include #include #include "qxcbconnection.h" #include "qxcbkeyboard.h" -#include "qxcbscreen.h" #include "qxcbwindow.h" #include "qxcbclipboard.h" #if QT_CONFIG(draganddrop) @@ -67,7 +65,6 @@ #include #include -#include #if QT_CONFIG(xkb) #define explicit dont_use_cxx_explicit #include @@ -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(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(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 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) diff --git a/src/plugins/platforms/xcb/qxcbconnection.h b/src/plugins/platforms/xcb/qxcbconnection.h index fcc1309afe..49e79ec3fd 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.h +++ b/src/plugins/platforms/xcb/qxcbconnection.h @@ -330,7 +330,15 @@ private: ScrollingDevice *scrollingDeviceForId(int id); static bool xi2GetValuatorValueIfSet(const void *event, int valuatorNum, double *value); -#endif + + QHash 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 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 m_peekFuncs; diff --git a/src/plugins/platforms/xcb/qxcbconnection_screens.cpp b/src/plugins/platforms/xcb/qxcbconnection_screens.cpp new file mode 100644 index 0000000000..4c380bf39f --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbconnection_screens.cpp @@ -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 +#include +#include + +#include + +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(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(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 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(); + } +} diff --git a/src/plugins/platforms/xcb/xcb_qpa_lib.pro b/src/plugins/platforms/xcb/xcb_qpa_lib.pro index c0704e4c18..c65b145fb6 100644 --- a/src/plugins/platforms/xcb/xcb_qpa_lib.pro +++ b/src/plugins/platforms/xcb/xcb_qpa_lib.pro @@ -33,6 +33,7 @@ SOURCES = \ qxcbeventqueue.cpp \ qxcbeventdispatcher.cpp \ qxcbconnection_basic.cpp \ + qxcbconnection_screens.cpp \ qxcbatom.cpp HEADERS = \ -- cgit v1.2.3