summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection.cpp21
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection.h11
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection_basic.cpp5
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection_basic.h5
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection_screens.cpp368
-rw-r--r--src/plugins/platforms/xcb/qxcbscreen.cpp120
-rw-r--r--src/plugins/platforms/xcb/qxcbscreen.h18
-rw-r--r--tests/manual/CMakeLists.txt1
-rw-r--r--tests/manual/manual.pro1
-rw-r--r--tests/manual/qscreen_xrandr/CMakeLists.txt14
-rw-r--r--tests/manual/qscreen_xrandr/qscreen_xrandr.pro6
-rw-r--r--tests/manual/qscreen_xrandr/tst_qscreen_xrandr.cpp110
12 files changed, 561 insertions, 119 deletions
diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp
index e1c9bf80fc..86d1c6cd90 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<unsigned int>(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<xcb_configure_notify_event_t *>(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<xcb_randr_notify_event_t *>(event));
+ if (!isAtLeastXRandR15())
+ updateScreens(reinterpret_cast<xcb_randr_notify_event_t *>(event));
} else if (isXRandrType(response_type, XCB_RANDR_SCREEN_CHANGE_NOTIFY)) {
- auto change_event = reinterpret_cast<xcb_randr_screen_change_notify_event_t *>(event);
- if (auto virtualDesktop = virtualDesktopForRootWindow(change_event->root))
- virtualDesktop->handleScreenChange(change_event);
+ if (!isAtLeastXRandR15()) {
+ auto change_event = reinterpret_cast<xcb_randr_screen_change_notify_event_t *>(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 a7db3eda89..b0b26085d7 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<QPlatformScreen *> &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<int32_t>(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 020412fc87..6482395f86 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 bda02ce9c2..d6eb962f56 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 867bbef020..420b296f35 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<QPlatformScreen *> &screens, xcb_randr_monitor_info_t *monitorInfo)
+{
+ for (int i = 0; i < screens.size(); ++i) {
+ auto s = static_cast<QXcbScreen*>(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<QPlatformScreen *> 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<QPlatformScreen *> 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<QPlatformScreen*> old = virtualDesktop->m_screens;
+
+ QList<QPlatformScreen *> 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<QXcbScreen*>(ps));
+ }
+
+ virtualDesktop->setScreens(std::move(siblings));
+}
diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp
index c084b17782..3a6185a083 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 ff30f599d6..133349fdf1 100644
--- a/src/plugins/platforms/xcb/qxcbscreen.h
+++ b/src/plugins/platforms/xcb/qxcbscreen.h
@@ -138,6 +138,8 @@ private:
QMap<xcb_visualid_t, quint8> m_visualDepths;
mutable QMap<xcb_visualid_t, xcb_colormap_t> 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<xcb_randr_output_t> outputs() const { return m_outputs; }
+ QList<xcb_randr_crtc_t> 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<xcb_randr_output_t> m_outputs;
+ QList<xcb_randr_crtc_t> 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 f9eb23cd5f..6dee2e7448 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 2ef802c8f3..8ad5882234 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 0000000000..095948b070
--- /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 0000000000..61132f63c2
--- /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 0000000000..f7611fc95a
--- /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 <qpainter.h>
+#include <qrasterwindow.h>
+#include <qscreen.h>
+#include <qpa/qwindowsysteminterface.h>
+#include <QProcess>
+
+#include <QtTest/QtTest>
+
+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<QScreen *> 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 <tst_qscreen_xrandr.moc>
+QTEST_MAIN(tst_QScreen_Xrandr);