From 9a4c98e55659b32db984612e6247ac193812a502 Mon Sep 17 00:00:00 2001 From: Liang Qi Date: Wed, 4 Nov 2020 10:29:13 +0100 Subject: [PATCH] xcb: support xrandr(1.5) monitor setup More information about monitor in xrandr 1.5, see https://keithp.com/blogs/MST-monitors/ Since this change, screen is logical instead of physical. If xrandr 1.5 and later is installed, Qt screen info will get from xrandr monitor object instead of xrandr output if only have 1.2 to 1.4. Users can manipulate monitor as they want, for example, a combination for two physical screens, half of one screen and etc. Didn't have chance to access MST monitors, but it should work if xrandr monitor object was created automatically. [ChangeLog][xcb] Qt screen info will get from xrandr monitor object if 1.5 is installed. Fixes: QTBUG-65457 Change-Id: Iad339cc0d4293b2403b4ef6bf6eb770feb3e685f Reviewed-by: Shawn Rutledge --- src/plugins/platforms/xcb/qxcbconnection.cpp | 21 +- src/plugins/platforms/xcb/qxcbconnection.h | 11 +- .../platforms/xcb/qxcbconnection_basic.cpp | 5 +- .../platforms/xcb/qxcbconnection_basic.h | 5 + .../platforms/xcb/qxcbconnection_screens.cpp | 368 +++++++++++++----- src/plugins/platforms/xcb/qxcbscreen.cpp | 120 +++++- src/plugins/platforms/xcb/qxcbscreen.h | 18 + tests/manual/CMakeLists.txt | 1 + tests/manual/manual.pro | 1 + tests/manual/qscreen_xrandr/CMakeLists.txt | 14 + .../manual/qscreen_xrandr/qscreen_xrandr.pro | 6 + .../qscreen_xrandr/tst_qscreen_xrandr.cpp | 110 ++++++ 12 files changed, 561 insertions(+), 119 deletions(-) create mode 100644 tests/manual/qscreen_xrandr/CMakeLists.txt create mode 100644 tests/manual/qscreen_xrandr/qscreen_xrandr.pro create mode 100644 tests/manual/qscreen_xrandr/tst_qscreen_xrandr.cpp diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp index e1c9bf80fc9..86d1c6cd90f 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection.cpp @@ -99,7 +99,7 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGra if (hasXRandr()) xrandrSelectEvents(); - initializeScreens(); + initializeScreens(false); if (hasXInput2()) { xi2SetupDevices(); @@ -610,8 +610,14 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) ev->event_x, ev->event_y, ev->detail, static_cast(m_buttonState)); HANDLE_PLATFORM_WINDOW_EVENT(xcb_motion_notify_event_t, event, handleMotionNotifyEvent); } - case XCB_CONFIGURE_NOTIFY: + case XCB_CONFIGURE_NOTIFY: { + if (isAtLeastXRandR15()) { + auto ev = reinterpret_cast(event); + if (ev->event == rootWindow()) + initializeScreens(true); + } HANDLE_PLATFORM_WINDOW_EVENT(xcb_configure_notify_event_t, event, handleConfigureNotifyEvent); + } case XCB_MAP_NOTIFY: HANDLE_PLATFORM_WINDOW_EVENT(xcb_map_notify_event_t, event, handleMapNotifyEvent); case XCB_UNMAP_NOTIFY: @@ -729,11 +735,14 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event) for (QXcbVirtualDesktop *virtualDesktop : qAsConst(m_virtualDesktops)) virtualDesktop->handleXFixesSelectionNotify(notify_event); } else if (isXRandrType(response_type, XCB_RANDR_NOTIFY)) { - updateScreens(reinterpret_cast(event)); + if (!isAtLeastXRandR15()) + updateScreens(reinterpret_cast(event)); } else if (isXRandrType(response_type, XCB_RANDR_SCREEN_CHANGE_NOTIFY)) { - auto change_event = reinterpret_cast(event); - if (auto virtualDesktop = virtualDesktopForRootWindow(change_event->root)) - virtualDesktop->handleScreenChange(change_event); + if (!isAtLeastXRandR15()) { + auto change_event = reinterpret_cast(event); + if (auto virtualDesktop = virtualDesktopForRootWindow(change_event->root)) + virtualDesktop->handleScreenChange(change_event); + } } else if (isXkbType(response_type)) { auto xkb_event = reinterpret_cast<_xkb_event *>(event); if (xkb_event->any.deviceID == m_keyboard->coreDeviceId()) { diff --git a/src/plugins/platforms/xcb/qxcbconnection.h b/src/plugins/platforms/xcb/qxcbconnection.h index a7db3eda89a..b0b26085d77 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.h +++ b/src/plugins/platforms/xcb/qxcbconnection.h @@ -261,7 +261,16 @@ private: const xcb_randr_output_change_t &outputChange, xcb_randr_get_output_info_reply_t *outputInfo); void destroyScreen(QXcbScreen *screen); - void initializeScreens(); + void initializeScreens(bool initialized); + void initializeScreensFromOutput(xcb_screen_iterator_t *it, int screenNumber, QXcbScreen *primaryScreen); + + void updateScreen_monitor(QXcbScreen *screen, xcb_randr_monitor_info_t *monitorInfo, xcb_timestamp_t timestamp = XCB_NONE); + QXcbScreen *createScreen_monitor(QXcbVirtualDesktop *virtualDesktop, + xcb_randr_monitor_info_t *monitorInfo, xcb_timestamp_t timestamp = XCB_NONE); + QXcbVirtualDesktop* virtualDesktopForNumber(int n) const; + QXcbScreen* findScreenForMonitorInfo(const QList &screens, xcb_randr_monitor_info_t *monitorInfo); + void initializeScreensFromMonitor(xcb_screen_iterator_t *it, int screenNumber, QXcbScreen *primaryScreen, bool initialized); + bool compressEvent(xcb_generic_event_t *event) const; inline bool timeGreaterThan(xcb_timestamp_t a, xcb_timestamp_t b) const { return static_cast(a - b) > 0 || b == XCB_CURRENT_TIME; } diff --git a/src/plugins/platforms/xcb/qxcbconnection_basic.cpp b/src/plugins/platforms/xcb/qxcbconnection_basic.cpp index 020412fc87a..6482395f868 100644 --- a/src/plugins/platforms/xcb/qxcbconnection_basic.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection_basic.cpp @@ -333,11 +333,14 @@ void QXcbBasicConnection::initializeXRandr() XCB_RANDR_MINOR_VERSION); if (!xrandrQuery || (xrandrQuery->major_version < 1 || (xrandrQuery->major_version == 1 && xrandrQuery->minor_version < 2))) { - qCWarning(lcQpaXcb, "failed to initialize XRandr"); + qCWarning(lcQpaXcb, "failed to initialize XRandr 1.2"); return; } m_hasXRandr = true; + + m_xrandr1Minor = xrandrQuery->minor_version; + m_xrandrFirstEvent = reply->first_event; } diff --git a/src/plugins/platforms/xcb/qxcbconnection_basic.h b/src/plugins/platforms/xcb/qxcbconnection_basic.h index bda02ce9c21..d6eb962f56f 100644 --- a/src/plugins/platforms/xcb/qxcbconnection_basic.h +++ b/src/plugins/platforms/xcb/qxcbconnection_basic.h @@ -98,6 +98,9 @@ public: bool hasXSync() const { return m_hasXSync; } bool hasBigRequest() const; + bool isAtLeastXRandR12() const { return m_hasXRandr && m_xrandr1Minor >= 2; } + bool isAtLeastXRandR15() const { return m_hasXRandr && m_xrandr1Minor >= 5; } + bool isAtLeastXI21() const { return m_xi2Enabled && m_xi2Minor >= 1; } bool isAtLeastXI22() const { return m_xi2Enabled && m_xi2Minor >= 2; } bool isXIEvent(xcb_generic_event_t *event) const; @@ -144,6 +147,8 @@ private: int m_xiOpCode = -1; uint32_t m_xinputFirstEvent = 0; + int m_xrandr1Minor = -1; + uint32_t m_xfixesFirstEvent = 0; uint32_t m_xrandrFirstEvent = 0; uint32_t m_xkbFirstEvent = 0; diff --git a/src/plugins/platforms/xcb/qxcbconnection_screens.cpp b/src/plugins/platforms/xcb/qxcbconnection_screens.cpp index 867bbef020d..420b296f354 100644 --- a/src/plugins/platforms/xcb/qxcbconnection_screens.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection_screens.cpp @@ -63,8 +63,15 @@ void QXcbConnection::xrandrSelectEvents() 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; + if (screen->root() == rootWindow) { + if (screen->m_monitor) { + if (screen->crtcs().contains(crtc)) + return screen; + } else { + if (screen->crtc() == crtc) + return screen; + } + } } return nullptr; @@ -73,8 +80,15 @@ QXcbScreen* QXcbConnection::findScreenForCrtc(xcb_window_t rootWindow, xcb_randr 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; + if (screen->root() == rootWindow) { + if (screen->m_monitor) { + if (screen->outputs().contains(output)) + return screen; + } else { + if (screen->output() == output) + return screen; + } + } } return nullptr; @@ -263,117 +277,84 @@ void QXcbConnection::destroyScreen(QXcbScreen *screen) QWindowSystemInterface::handlePrimaryScreenChanged(newPrimary); } + qCDebug(lcQpaScreen) << "destroyScreen: destroy" << screen; QWindowSystemInterface::handleScreenRemoved(screen); } } -void QXcbConnection::initializeScreens() +void QXcbConnection::updateScreen_monitor(QXcbScreen *screen, xcb_randr_monitor_info_t *monitorInfo, xcb_timestamp_t timestamp) +{ + screen->setMonitor(monitorInfo, timestamp); + + if (screen->isPrimary()) { + const int idx = m_screens.indexOf(screen); + if (idx > 0) { + qAsConst(m_screens).first()->setPrimary(false); + m_screens.swapItemsAt(0, idx); + } + screen->virtualDesktop()->setPrimaryScreen(screen); + QWindowSystemInterface::handlePrimaryScreenChanged(screen); + } + qCDebug(lcQpaScreen) << "updateScreen_monitor: update" << screen << "(Primary:" << screen->isPrimary() << ")"; +} + +QXcbScreen *QXcbConnection::createScreen_monitor(QXcbVirtualDesktop *virtualDesktop, xcb_randr_monitor_info_t *monitorInfo, xcb_timestamp_t timestamp) +{ + QXcbScreen *screen = new QXcbScreen(this, virtualDesktop, monitorInfo, timestamp); + + if (screen->isPrimary()) { + if (!m_screens.isEmpty()) + qAsConst(m_screens).first()->setPrimary(false); + + m_screens.prepend(screen); + } else { + m_screens.append(screen); + } + qCDebug(lcQpaScreen) << "createScreen_monitor: adding" << screen << "(Primary:" << screen->isPrimary() << ")"; + virtualDesktop->addScreen(screen); + QWindowSystemInterface::handleScreenAdded(screen, screen->isPrimary()); + return screen; +} + +QXcbVirtualDesktop *QXcbConnection::virtualDesktopForNumber(int n) const +{ + for (QXcbVirtualDesktop *virtualDesktop : m_virtualDesktops) { + if (virtualDesktop->number() == n) + return virtualDesktop; + } + + return nullptr; +} + +QXcbScreen *QXcbConnection::findScreenForMonitorInfo(const QList &screens, xcb_randr_monitor_info_t *monitorInfo) +{ + for (int i = 0; i < screens.size(); ++i) { + auto s = static_cast(screens[i]); + if (s->m_monitor && monitorInfo) { + QByteArray ba1 = atomName(s->m_monitor->name); + QByteArray ba2 = atomName(monitorInfo->name); + if (ba1 == ba2) + return s; + } + } + + return nullptr; +} + +void QXcbConnection::initializeScreens(bool initialized) { 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 (hasXRandr()) { - // 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); - decltype(Q_XCB_REPLY(xcb_randr_get_screen_resources, - xcb_connection(), xcbScreen->root)) resources; - 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 { - 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 (isAtLeastXRandR15()) + initializeScreensFromMonitor(&it, xcbScreenNumber, primaryScreen, initialized); + else if (isAtLeastXRandR12()) + initializeScreensFromOutput(&it, xcbScreenNumber, primaryScreen); - 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()); - } - } - } - } - } - } - } - 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(std::move(siblings)); xcb_screen_next(&it); ++xcbScreenNumber; - } // for each xcb screen + } for (QXcbVirtualDesktop *virtualDesktop : qAsConst(m_virtualDesktops)) virtualDesktop->subscribeToXFixesSelectionNotify(); @@ -390,11 +371,186 @@ void QXcbConnection::initializeScreens() } // Push the screens to QGuiApplication - for (QXcbScreen *screen : qAsConst(m_screens)) { - qCDebug(lcQpaScreen) << "adding" << screen << "(Primary:" << screen->isPrimary() << ")"; - QWindowSystemInterface::handleScreenAdded(screen, screen->isPrimary()); + if (!initialized) { + for (QXcbScreen *screen : qAsConst(m_screens)) { + qCDebug(lcQpaScreen) << "adding" << screen << "(Primary:" << screen->isPrimary() << ")"; + QWindowSystemInterface::handleScreenAdded(screen, screen->isPrimary()); + } } qCDebug(lcQpaScreen) << "primary output is" << qAsConst(m_screens).first()->name(); } } + +void QXcbConnection::initializeScreensFromOutput(xcb_screen_iterator_t *it, int xcbScreenNumber, QXcbScreen *primaryScreen) +{ + // 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 (isAtLeastXRandR12()) { + // 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); + decltype(Q_XCB_REPLY(xcb_randr_get_screen_resources, + xcb_connection(), xcbScreen->root)) resources; + 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 { + 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()); + } + } + } + } + } + } + } + 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(std::move(siblings)); +} + +void QXcbConnection::initializeScreensFromMonitor(xcb_screen_iterator_t *it, int xcbScreenNumber, QXcbScreen *primaryScreen, bool initialized) +{ + // 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 = nullptr; + if (initialized) + virtualDesktop = virtualDesktopForNumber(xcbScreenNumber); + if (!virtualDesktop) { + virtualDesktop = new QXcbVirtualDesktop(this, xcbScreen, xcbScreenNumber); + m_virtualDesktops.append(virtualDesktop); + } + QList old = virtualDesktop->m_screens; + + QList siblings; + + xcb_randr_get_monitors_cookie_t monitors_c = xcb_randr_get_monitors(xcb_connection(), xcbScreen->root, 1); + xcb_randr_get_monitors_reply_t *monitors_r = xcb_randr_get_monitors_reply(xcb_connection(), monitors_c, nullptr); + + if (!monitors_r) { + qWarning("RANDR GetMonitors failed; this should not be possible"); + return; + } + + xcb_randr_monitor_info_iterator_t monitor_iter = xcb_randr_get_monitors_monitors_iterator(monitors_r); + while (monitor_iter.rem) { + xcb_randr_monitor_info_t *monitor_info = monitor_iter.data; + QXcbScreen *screen = nullptr; + if (!initialized) { + screen = new QXcbScreen(this, virtualDesktop, monitor_info, monitors_r->timestamp); + m_screens << screen; + } else { + screen = findScreenForMonitorInfo(virtualDesktop->m_screens, monitor_info); + if (!screen) { + screen = createScreen_monitor(virtualDesktop, monitor_info, monitors_r->timestamp); + QHighDpiScaling::updateHighDpiScaling(); + } else { + updateScreen_monitor(screen, monitor_info, monitors_r->timestamp); + old.removeAll(screen); + } + } + + siblings << screen; + + xcb_randr_monitor_info_next(&monitor_iter); + } + + if (siblings.isEmpty()) { + // If there are no XRandR outputs or XRandR extension is missing, + // then create a fake/legacy screen. + auto screen = new QXcbScreen(this, virtualDesktop, nullptr); + qCDebug(lcQpaScreen) << "created fake screen" << screen; + + if (primaryScreenNumber() == xcbScreenNumber) { + primaryScreen = screen; + primaryScreen->setPrimary(true); + } + siblings << screen; + m_screens << screen; + } + + if (initialized) { + for (QPlatformScreen *ps : old) + destroyScreen(static_cast(ps)); + } + + virtualDesktop->setScreens(std::move(siblings)); +} diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp index c084b177823..3a6185a0832 100644 --- a/src/plugins/platforms/xcb/qxcbscreen.cpp +++ b/src/plugins/platforms/xcb/qxcbscreen.cpp @@ -530,12 +530,13 @@ QXcbScreen::QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDe xcb_randr_output_t outputId, xcb_randr_get_output_info_reply_t *output) : QXcbObject(connection) , m_virtualDesktop(virtualDesktop) + , m_monitor(nullptr) , m_output(outputId) , m_crtc(output ? output->crtc : XCB_NONE) , m_outputName(getOutputName(output)) , m_outputSizeMillimeters(output ? QSize(output->mm_width, output->mm_height) : QSize()) { - if (connection->hasXRandr()) { + if (connection->isAtLeastXRandR12()) { xcb_randr_select_input(xcb_connection(), screen()->root, true); auto crtc = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_crtc_info, xcb_connection(), m_crtc, output ? output->timestamp : 0); @@ -567,7 +568,7 @@ QXcbScreen::QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDe m_colorSpace = QColorSpace::fromIccProfile(data); } } - if (connection->hasXRandr()) { // Parse EDID + if (connection->isAtLeastXRandR12()) { // Parse EDID QByteArray edid = getEdid(); if (m_edid.parse(edid)) { qCDebug(lcQpaScreen, "EDID data for output \"%s\": identifier '%s', manufacturer '%s'," @@ -610,6 +611,97 @@ QXcbScreen::QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDe m_colorSpace = QColorSpace::SRgb; } +QXcbScreen::QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDesktop, + xcb_randr_monitor_info_t *monitorInfo, xcb_timestamp_t timestamp) + : QXcbObject(connection) + , m_virtualDesktop(virtualDesktop) + , m_monitor(monitorInfo) +{ + setMonitor(monitorInfo, timestamp); +} + +void QXcbScreen::setMonitor(xcb_randr_monitor_info_t *monitorInfo, xcb_timestamp_t timestamp) +{ + if (!connection()->isAtLeastXRandR15() || !monitorInfo) + return; + + xcb_randr_select_input(xcb_connection(), screen()->root, true); + + m_monitor = monitorInfo; + QRect monitorGeometry = QRect(m_monitor->x, m_monitor->y, + m_monitor->width, m_monitor->height); + + m_outputs.clear(); + m_crtcs.clear(); + + int outputCount = xcb_randr_monitor_info_outputs_length(m_monitor); + xcb_randr_output_t *outputs = nullptr; + if (outputCount) { + outputs = xcb_randr_monitor_info_outputs(m_monitor); + 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; + } + + m_outputs << outputs[i]; + m_crtcs << output->crtc; + } + } + + if (m_crtcs.size() == 1) { + auto crtc = Q_XCB_REPLY(xcb_randr_get_crtc_info, + xcb_connection(), m_crtcs[0], timestamp); + m_singlescreen = (monitorGeometry == (QRect(crtc->x, crtc->y, crtc->width, crtc->height))); + if (m_singlescreen) { + if (crtc->mode) { + if (crtc->rotation == XCB_RANDR_ROTATION_ROTATE_90 || + crtc->rotation == XCB_RANDR_ROTATION_ROTATE_270) + std::swap(crtc->width, crtc->height); + updateGeometry(QRect(crtc->x, crtc->y, crtc->width, crtc->height), crtc->rotation); + if (mode() != crtc->mode) + updateRefreshRate(crtc->mode); + } + } + } + + if (!m_singlescreen) + m_geometry = monitorGeometry; + m_availableGeometry = m_virtualDesktop->availableGeometry(m_geometry); + if (m_geometry.isEmpty()) + m_geometry = QRect(QPoint(), virtualDesktop()->size()); + if (m_availableGeometry.isEmpty()) + m_availableGeometry = m_virtualDesktop->availableGeometry(m_geometry); + + m_sizeMillimeters = sizeInMillimeters(m_geometry.size(), m_virtualDesktop->dpi()); + + if (m_sizeMillimeters.isEmpty()) + m_sizeMillimeters = virtualDesktop()->physicalSize(); + + QByteArray ba = connection()->atomName(monitorInfo->name); + m_outputName = getName(monitorInfo); + + if (monitorInfo->primary) + m_primary = true; + + m_cursor = new QXcbCursor(connection(), this); +} + QXcbScreen::~QXcbScreen() { delete m_cursor; @@ -632,6 +724,23 @@ QString QXcbScreen::getOutputName(xcb_randr_get_output_info_reply_t *outputInfo) return name; } +QString QXcbScreen::getName(xcb_randr_monitor_info_t *monitorInfo) +{ + QString name; + QByteArray ba = connection()->atomName(monitorInfo->name); + if (!ba.isEmpty()) { + name = QString::fromLatin1(ba.constData()); + } else { + QByteArray displayName = connection()->displayName(); + int dotPos = displayName.lastIndexOf('.'); + if (dotPos != -1) + displayName.truncate(dotPos); + name = QString::fromLocal8Bit(displayName) + QLatin1Char('.') + + QString::number(m_virtualDesktop->number()); + } + return name; +} + QString QXcbScreen::manufacturer() const { return m_edid.manufacturer; @@ -773,6 +882,7 @@ QPlatformCursor *QXcbScreen::cursor() const void QXcbScreen::setOutput(xcb_randr_output_t outputId, xcb_randr_get_output_info_reply_t *outputInfo) { + m_monitor = nullptr; m_output = outputId; m_crtc = outputInfo ? outputInfo->crtc : XCB_NONE; m_mode = XCB_NONE; @@ -782,7 +892,7 @@ void QXcbScreen::setOutput(xcb_randr_output_t outputId, void QXcbScreen::updateGeometry(xcb_timestamp_t timestamp) { - if (!connection()->hasXRandr()) + if (!connection()->isAtLeastXRandR12()) return; auto crtc = Q_XCB_REPLY_UNCHECKED(xcb_randr_get_crtc_info, xcb_connection(), @@ -838,7 +948,7 @@ void QXcbScreen::updateAvailableGeometry() void QXcbScreen::updateRefreshRate(xcb_randr_mode_t mode) { - if (!connection()->hasXRandr()) + if (!connection()->isAtLeastXRandR12()) return; if (m_mode == mode) @@ -974,7 +1084,7 @@ QByteArray QXcbScreen::getOutputProperty(xcb_atom_t atom) const QByteArray QXcbScreen::getEdid() const { QByteArray result; - if (!connection()->hasXRandr()) + if (!connection()->isAtLeastXRandR12()) return result; // Try a bunch of atoms diff --git a/src/plugins/platforms/xcb/qxcbscreen.h b/src/plugins/platforms/xcb/qxcbscreen.h index ff30f599d6b..133349fdf18 100644 --- a/src/plugins/platforms/xcb/qxcbscreen.h +++ b/src/plugins/platforms/xcb/qxcbscreen.h @@ -138,6 +138,8 @@ private: QMap m_visualDepths; mutable QMap m_visualColormaps; uint16_t m_rotation = 0; + + friend class QXcbConnection; }; class Q_XCB_EXPORT QXcbScreen : public QXcbObject, public QPlatformScreen @@ -146,9 +148,12 @@ class Q_XCB_EXPORT QXcbScreen : public QXcbObject, public QPlatformScreen public: QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDesktop, xcb_randr_output_t outputId, xcb_randr_get_output_info_reply_t *outputInfo); + QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDesktop, + xcb_randr_monitor_info_t *monitorInfo, xcb_timestamp_t timestamp = XCB_NONE); ~QXcbScreen(); QString getOutputName(xcb_randr_get_output_info_reply_t *outputInfo); + QString getName(xcb_randr_monitor_info_t *monitorInfo); QPixmap grabWindow(WId window, int x, int y, int width, int height) const override; @@ -184,9 +189,13 @@ public: xcb_randr_crtc_t crtc() const { return m_crtc; } xcb_randr_mode_t mode() const { return m_mode; } + QList outputs() const { return m_outputs; } + QList crtcs() const { return m_crtcs; } + void setOutput(xcb_randr_output_t outputId, xcb_randr_get_output_info_reply_t *outputInfo); void setCrtc(xcb_randr_crtc_t crtc) { m_crtc = crtc; } + void setMonitor(xcb_randr_monitor_info_t *monitorInfo, xcb_timestamp_t timestamp = XCB_NONE); void windowShown(QXcbWindow *window); QString windowManagerName() const { return m_virtualDesktop->windowManagerName(); } @@ -219,11 +228,17 @@ private: QByteArray getEdid() const; QXcbVirtualDesktop *m_virtualDesktop; + xcb_randr_monitor_info_t *m_monitor; xcb_randr_output_t m_output; xcb_randr_crtc_t m_crtc; xcb_randr_mode_t m_mode = XCB_NONE; bool m_primary = false; + bool m_singlescreen = false; + + QList m_outputs; + QList m_crtcs; + QString m_outputName; QSizeF m_outputSizeMillimeters; QSizeF m_sizeMillimeters; @@ -234,6 +249,9 @@ private: QXcbCursor *m_cursor; qreal m_refreshRate = 60.0; QEdidParser m_edid; + + friend class QXcbConnection; + friend class QXcbVirtualDesktop; }; #ifndef QT_NO_DEBUG_STREAM diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt index f9eb23cd5f8..6dee2e74481 100644 --- a/tests/manual/CMakeLists.txt +++ b/tests/manual/CMakeLists.txt @@ -39,6 +39,7 @@ endif() #special case end add_subdirectory(qstorageinfo) add_subdirectory(qscreen) +add_subdirectory(qscreen_xrandr) add_subdirectory(qsslsocket) add_subdirectory(qsysinfo) add_subdirectory(qtabletevent) diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro index 2ef802c8f3a..8ad58822345 100644 --- a/tests/manual/manual.pro +++ b/tests/manual/manual.pro @@ -27,6 +27,7 @@ qnetworkaccessmanager/qget \ qnetworkreply \ qstorageinfo \ qscreen \ +qscreen_xrandr \ qssloptions \ qsslsocket \ qsysinfo \ diff --git a/tests/manual/qscreen_xrandr/CMakeLists.txt b/tests/manual/qscreen_xrandr/CMakeLists.txt new file mode 100644 index 00000000000..095948b0700 --- /dev/null +++ b/tests/manual/qscreen_xrandr/CMakeLists.txt @@ -0,0 +1,14 @@ +# Generated from qscreen_xrandr.pro. + +##################################################################### +## tst_qscreen2 Test: +##################################################################### + +qt_internal_add_test(tst_qscreen_xrandr + SOURCES + tst_qscreen_xrandr.cpp + PUBLIC_LIBRARIES + Qt::CorePrivate + Qt::Gui + Qt::GuiPrivate +) diff --git a/tests/manual/qscreen_xrandr/qscreen_xrandr.pro b/tests/manual/qscreen_xrandr/qscreen_xrandr.pro new file mode 100644 index 00000000000..61132f63c23 --- /dev/null +++ b/tests/manual/qscreen_xrandr/qscreen_xrandr.pro @@ -0,0 +1,6 @@ +CONFIG += testcase +TARGET = tst_qscreen_xrandr + +QT += core-private gui-private testlib + +SOURCES += tst_qscreen_xrandr.cpp diff --git a/tests/manual/qscreen_xrandr/tst_qscreen_xrandr.cpp b/tests/manual/qscreen_xrandr/tst_qscreen_xrandr.cpp new file mode 100644 index 00000000000..f7611fc95ab --- /dev/null +++ b/tests/manual/qscreen_xrandr/tst_qscreen_xrandr.cpp @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include + +#include + +class tst_QScreen_Xrandr: public QObject +{ + Q_OBJECT + +private slots: + void xrandr_15(); +}; + +// this test requires an X11 desktop with at least two screens +void tst_QScreen_Xrandr::xrandr_15() +{ + QStringList originalScreenNames; + QStringList mergedScreenNames; + { + QList screens = QGuiApplication::screens(); + QVERIFY(screens.size() >= 2); + qDebug() << "initial set of screens:" << screens.size(); + for (QScreen *s : screens) { + qDebug() << "screen: " << s->name(); + originalScreenNames << s->name(); + if (mergedScreenNames.size() < 2) + mergedScreenNames << s->name(); + } + } + + QSignalSpy addedSpy(qApp, &QGuiApplication::screenAdded); + QSignalSpy removedSpy(qApp, &QGuiApplication::screenRemoved); + + // combine the first two screens into one monitor + // e.g. "xrandr --setmonitor Qt-merged auto eDP,HDMI-0" + QString prog1 = "xrandr"; + QStringList args1; + args1 << "--setmonitor" << "Qt-merged" << "auto" << mergedScreenNames.join(','); + qDebug() << prog1 << args1; + QProcess *myProcess1 = new QProcess(this); + myProcess1->start(prog1, args1); + QVERIFY(myProcess1->waitForFinished()); + + QTRY_COMPARE(removedSpy.count(), 2); + QVERIFY(QGuiApplication::screens().size() != originalScreenNames.size()); + auto combinedScreens = QGuiApplication::screens(); + qDebug() << "added" << addedSpy.count() << "removed" << removedSpy.count() << "combined screen(s):" << combinedScreens.size(); + QScreen *merged = nullptr; + for (QScreen *s : combinedScreens) { + qDebug() << "screen: " << s->name(); + if (s->name() == QLatin1String("Qt-merged")) + merged = s; + } + // the screen that we created must be in the list now + QVERIFY(merged); + QCOMPARE(QGuiApplication::screens().size(), originalScreenNames.size() - 1); + addedSpy.clear(); + removedSpy.clear(); + + // "xrandr --delmonitor Qt-merged" + QString prog2 = "xrandr"; + QStringList args2; + args2 << "--delmonitor" << "Qt-merged"; + QProcess *myProcess2 = new QProcess(this); + myProcess2->start(prog2, args2); + QVERIFY(myProcess2->waitForFinished()); + + QTRY_COMPARE(removedSpy.count(), 1); + QVERIFY(QGuiApplication::screens().size() != combinedScreens.size()); + auto separatedScreens = QGuiApplication::screens(); + qDebug() << "added" << addedSpy.count() << "removed" << removedSpy.count() << "separated screen(s):" << separatedScreens.size(); + for (QScreen *s : separatedScreens) + qDebug() << "screen: " << s->name(); + + QCOMPARE(QGuiApplication::screens().size(), originalScreenNames.size()); +} + +#include +QTEST_MAIN(tst_QScreen_Xrandr);