714 lines
33 KiB
Diff
714 lines
33 KiB
Diff
commit 3b30a8215e98f6164a1650ce7c7e2a42a3d8746f
|
|
Author: Daniel Vrátil <dvratil@redhat.com>
|
|
Date: Mon Feb 23 20:27:37 2015 +0100
|
|
|
|
Improve handling of XRandR events in XCB backend
|
|
|
|
Querying X server for data can be very expensive, especially when there
|
|
are multiple processes querying it at the same time (which is exactly what
|
|
happens when screen configuration changes and all Qt applications receive
|
|
XRandR change notifications). This patch is aiming to reduce the number of
|
|
queries to X server as much as possible by making use of detailed information
|
|
available in the RRCrtcChangeNotify and RROutputChangeNotify events.
|
|
|
|
Firstly, the backend now does not rebuild all QXcbScreens on any change (which
|
|
involved the very expensive xcb_randr_get_screen_resources() call), but only
|
|
builds the full set of QXcbScreens once in initializeScreens(), and then just
|
|
incrementally updates it.
|
|
|
|
Secondly, it avoids querying X server for all screens geometry as much as
|
|
possible, and only does so when CRTC/Output change notification for a particular
|
|
screen is delivered.
|
|
|
|
As a result, handling of all XRandR events on screen change is reduced from tens
|
|
of seconds to less then a seconds and applications are better responsive after
|
|
that, because we don't block the event loop for long. The X server is also more
|
|
responsive after the screen change, since we are not overloading it with requests.
|
|
|
|
Change-Id: I9b8308341cada71dfc9590030909b1e68a335a1f
|
|
Reviewed-by: Shawn Rutledge <shawn.rutledge@digia.com>
|
|
|
|
diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp
|
|
index 6efb876..0db7603 100644
|
|
--- a/src/plugins/platforms/xcb/qxcbconnection.cpp
|
|
+++ b/src/plugins/platforms/xcb/qxcbconnection.cpp
|
|
@@ -49,6 +49,7 @@
|
|
#include <QAbstractEventDispatcher>
|
|
#include <QTimer>
|
|
#include <QByteArray>
|
|
+#include <QScopedPointer>
|
|
|
|
#include <algorithm>
|
|
|
|
@@ -155,8 +156,29 @@ typedef struct {
|
|
} QGLXBufferSwapComplete;
|
|
#endif
|
|
|
|
-QXcbScreen* QXcbConnection::findOrCreateScreen(QList<QXcbScreen *>& newScreens,
|
|
- int screenNumber, xcb_screen_t* xcbScreen, xcb_randr_get_output_info_reply_t *output)
|
|
+QXcbScreen* QXcbConnection::findScreenForCrtc(xcb_window_t rootWindow, xcb_randr_crtc_t crtc)
|
|
+{
|
|
+ foreach (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)
|
|
+{
|
|
+ foreach (QXcbScreen *screen, m_screens) {
|
|
+ if (screen->root() == rootWindow && screen->output() == output)
|
|
+ return screen;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+QXcbScreen* QXcbConnection::createScreen(int screenNumber, xcb_screen_t* xcbScreen,
|
|
+ xcb_randr_output_t outputId,
|
|
+ xcb_randr_get_output_info_reply_t *output)
|
|
{
|
|
QString name;
|
|
if (output)
|
|
@@ -169,23 +191,147 @@ QXcbScreen* QXcbConnection::findOrCreateScreen(QList<QXcbScreen *>& newScreens,
|
|
displayName.truncate(dotPos);
|
|
name = displayName + QLatin1Char('.') + QString::number(screenNumber);
|
|
}
|
|
- foreach (QXcbScreen* scr, m_screens)
|
|
- if (scr->name() == name && scr->root() == xcbScreen->root)
|
|
- return scr;
|
|
- QXcbScreen *ret = new QXcbScreen(this, xcbScreen, output, name, screenNumber);
|
|
- newScreens << ret;
|
|
- return ret;
|
|
+
|
|
+ return new QXcbScreen(this, xcbScreen, outputId, output, name, screenNumber);
|
|
+}
|
|
+
|
|
+bool QXcbConnection::checkOutputIsPrimary(xcb_window_t rootWindow, xcb_randr_output_t output)
|
|
+{
|
|
+ xcb_generic_error_t *error = 0;
|
|
+ xcb_randr_get_output_primary_cookie_t primaryCookie =
|
|
+ xcb_randr_get_output_primary(xcb_connection(), rootWindow);
|
|
+ QScopedPointer<xcb_randr_get_output_primary_reply_t, QScopedPointerPodDeleter> primary (
|
|
+ xcb_randr_get_output_primary_reply(xcb_connection(), primaryCookie, &error));
|
|
+ if (!primary || error) {
|
|
+ qWarning("failed to get the primary output of the screen");
|
|
+ free(error);
|
|
+ error = NULL;
|
|
+ }
|
|
+ const bool isPrimary = primary ? (primary->output == output) : false;
|
|
+
|
|
+ return isPrimary;
|
|
+}
|
|
+
|
|
+xcb_screen_t* QXcbConnection::xcbScreenForRootWindow(xcb_window_t rootWindow, int *xcbScreenNumber)
|
|
+{
|
|
+ xcb_screen_iterator_t xcbScreenIter = xcb_setup_roots_iterator(m_setup);
|
|
+ for (; xcbScreenIter.rem; xcb_screen_next(&xcbScreenIter)) {
|
|
+ if (xcbScreenIter.data->root == rootWindow) {
|
|
+ if (xcbScreenNumber)
|
|
+ *xcbScreenNumber = xcb_setup_roots_length(m_setup) - xcbScreenIter.rem;
|
|
+ return xcbScreenIter.data;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
/*!
|
|
\brief Synchronizes the screen list, adds new screens, removes deleted ones
|
|
*/
|
|
-void QXcbConnection::updateScreens()
|
|
+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;
|
|
+ xcb_screen_t *xcbScreen = xcbScreenForRootWindow(crtc.window);
|
|
+ if (!xcbScreen)
|
|
+ // Not for us
|
|
+ return;
|
|
+
|
|
+ qCDebug(lcQpaScreen) << "QXcbConnection: XCB_RANDR_NOTIFY_CRTC_CHANGE:" << crtc.crtc;
|
|
+ QXcbScreen *screen = findScreenForCrtc(crtc.window, crtc.crtc);
|
|
+ // 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) {
|
|
+ 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;
|
|
+ int xcbScreenNumber = 0;
|
|
+ xcb_screen_t *xcbScreen = xcbScreenForRootWindow(output.window, &xcbScreenNumber);
|
|
+ if (!xcbScreen)
|
|
+ // 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";
|
|
+
|
|
+ // Known screen removed -> delete it
|
|
+ m_screens.removeOne(screen);
|
|
+ foreach (QXcbScreen *otherScreen, m_screens)
|
|
+ otherScreen->removeVirtualSibling((QPlatformScreen *) screen);
|
|
+
|
|
+ QXcbIntegration::instance()->destroyScreen(screen);
|
|
+
|
|
+ // QTBUG-40174, QTBUG-42985: If there are no outputs, then there must be
|
|
+ // no QScreen instances; a Qt application can survive this situation, and
|
|
+ // start rendering again later when there is a screen again.
|
|
+
|
|
+ } 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) {
|
|
+ xcb_randr_get_output_info_cookie_t outputInfoCookie =
|
|
+ xcb_randr_get_output_info(xcb_connection(), output.output, output.config_timestamp);
|
|
+ QScopedPointer<xcb_randr_get_output_info_reply_t, QScopedPointerPodDeleter> outputInfo(
|
|
+ xcb_randr_get_output_info_reply(xcb_connection(), outputInfoCookie, NULL));
|
|
+
|
|
+ screen = createScreen(xcbScreenNumber, xcbScreen, output.output, outputInfo.data());
|
|
+ qCDebug(lcQpaScreen) << "output" << screen->name() << "is connected and enabled";
|
|
+
|
|
+ screen->setPrimary(checkOutputIsPrimary(output.window, output.output));
|
|
+ foreach (QXcbScreen *otherScreen, m_screens)
|
|
+ if (otherScreen->root() == output.window)
|
|
+ otherScreen->addVirtualSibling(screen);
|
|
+ m_screens << screen;
|
|
+ QXcbIntegration::instance()->screenAdded(screen, screen->isPrimary());
|
|
+ }
|
|
+ // else ignore disabled screens
|
|
+ } else if (screen) {
|
|
+ // Screen has been disabled -> remove
|
|
+ if (output.crtc == XCB_NONE && output.mode == XCB_NONE) {
|
|
+ qCDebug(lcQpaScreen) << "output" << screen->name() << "has been disabled";
|
|
+ m_screens.removeOne(screen);
|
|
+ foreach (QXcbScreen *otherScreen, m_screens)
|
|
+ otherScreen->removeVirtualSibling((QPlatformScreen *) screen);
|
|
+ QXcbIntegration::instance()->destroyScreen(screen);
|
|
+ } else {
|
|
+ // Just update existing screen
|
|
+ screen->updateGeometry(output.config_timestamp);
|
|
+ const bool wasPrimary = screen->isPrimary();
|
|
+ screen->setPrimary(checkOutputIsPrimary(output.window, output.output));
|
|
+ if (screen->mode() != output.mode)
|
|
+ screen->updateRefreshRate(output.mode);
|
|
+
|
|
+ // If the screen became primary, reshuffle the order in QGuiApplicationPrivate
|
|
+ // TODO: add a proper mechanism for updating primary screen
|
|
+ if (!wasPrimary && screen->isPrimary()) {
|
|
+ QScreen *realScreen = static_cast<QPlatformScreen*>(screen)->screen();
|
|
+ QGuiApplicationPrivate::screen_list.removeOne(realScreen);
|
|
+ QGuiApplicationPrivate::screen_list.prepend(realScreen);
|
|
+ m_screens.removeOne(screen);
|
|
+ m_screens.prepend(screen);
|
|
+ }
|
|
+ qCDebug(lcQpaScreen) << "output has changed" << screen;
|
|
+ }
|
|
+ }
|
|
+ if (!m_screens.isEmpty())
|
|
+ qCDebug(lcQpaScreen) << "primary output is" << m_screens.first()->name();
|
|
+ else
|
|
+ qCDebug(lcQpaScreen) << "no outputs";
|
|
+ }
|
|
+}
|
|
+
|
|
+void QXcbConnection::initializeScreens()
|
|
{
|
|
xcb_screen_iterator_t it = xcb_setup_roots_iterator(m_setup);
|
|
int xcbScreenNumber = 0; // screen number in the xcb sense
|
|
- QList<QXcbScreen *> activeScreens;
|
|
- QList<QXcbScreen *> newScreens;
|
|
QXcbScreen* primaryScreen = NULL;
|
|
while (it.rem) {
|
|
// Each "screen" in xcb terminology is a virtual desktop,
|
|
@@ -200,59 +346,73 @@ void QXcbConnection::updateScreens()
|
|
xcb_generic_error_t *error = NULL;
|
|
xcb_randr_get_output_primary_cookie_t primaryCookie =
|
|
xcb_randr_get_output_primary(xcb_connection(), xcbScreen->root);
|
|
+ // TODO: RRGetScreenResources has to be called on each X display at least once before
|
|
+ // RRGetScreenResourcesCurrent can be used - we can't know if we are the first application
|
|
+ // to do so or not, so we always call the slower version here. Ideally we should share some
|
|
+ // global flag (an atom on root window maybe) that at least other Qt apps would understand
|
|
+ // and could call RRGetScreenResourcesCurrent here, speeding up start.
|
|
xcb_randr_get_screen_resources_cookie_t resourcesCookie =
|
|
xcb_randr_get_screen_resources(xcb_connection(), xcbScreen->root);
|
|
- xcb_randr_get_output_primary_reply_t *primary =
|
|
- xcb_randr_get_output_primary_reply(xcb_connection(), primaryCookie, &error);
|
|
+ QScopedPointer<xcb_randr_get_output_primary_reply_t, QScopedPointerPodDeleter> primary(
|
|
+ xcb_randr_get_output_primary_reply(xcb_connection(), primaryCookie, &error));
|
|
if (!primary || error) {
|
|
- qWarning("QXcbConnection: Failed to get the primary output of the screen");
|
|
+ qWarning("failed to get the primary output of the screen");
|
|
free(error);
|
|
} else {
|
|
- xcb_randr_get_screen_resources_reply_t *resources =
|
|
- xcb_randr_get_screen_resources_reply(xcb_connection(), resourcesCookie, &error);
|
|
+ QScopedPointer<xcb_randr_get_screen_resources_reply_t, QScopedPointerPodDeleter> resources(
|
|
+ xcb_randr_get_screen_resources_reply(xcb_connection(), resourcesCookie, &error));
|
|
if (!resources || error) {
|
|
- qWarning("QXcbConnection: Failed to get the screen resources");
|
|
+ qWarning("failed to get the screen resources");
|
|
free(error);
|
|
} else {
|
|
xcb_timestamp_t timestamp = resources->config_timestamp;
|
|
- outputCount = xcb_randr_get_screen_resources_outputs_length(resources);
|
|
- xcb_randr_output_t *outputs = xcb_randr_get_screen_resources_outputs(resources);
|
|
+ outputCount = xcb_randr_get_screen_resources_outputs_length(resources.data());
|
|
+ xcb_randr_output_t *outputs = xcb_randr_get_screen_resources_outputs(resources.data());
|
|
|
|
for (int i = 0; i < outputCount; i++) {
|
|
- xcb_randr_get_output_info_reply_t *output =
|
|
+ QScopedPointer<xcb_randr_get_output_info_reply_t, QScopedPointerPodDeleter> output(
|
|
xcb_randr_get_output_info_reply(xcb_connection(),
|
|
- xcb_randr_get_output_info_unchecked(xcb_connection(), outputs[i], timestamp), NULL);
|
|
+ xcb_randr_get_output_info_unchecked(xcb_connection(), outputs[i], timestamp), NULL));
|
|
+
|
|
+ // Invalid, disconnected or disabled output
|
|
if (output == NULL)
|
|
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.data()),
|
|
+ xcb_randr_get_output_info_name_length(output.data()))));
|
|
+ continue;
|
|
+ }
|
|
|
|
if (output->crtc == XCB_NONE) {
|
|
- qCDebug(lcQpaScreen, "output %s is not connected", qPrintable(
|
|
- QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output),
|
|
- xcb_randr_get_output_info_name_length(output))));
|
|
+ qCDebug(lcQpaScreen, "Output %s is not enabled", qPrintable(
|
|
+ QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output.data()),
|
|
+ xcb_randr_get_output_info_name_length(output.data()))));
|
|
continue;
|
|
}
|
|
|
|
- QXcbScreen *screen = findOrCreateScreen(newScreens, xcbScreenNumber, xcbScreen, output);
|
|
+ QXcbScreen *screen = createScreen(xcbScreenNumber, xcbScreen, outputs[i], output.data());
|
|
siblings << screen;
|
|
- activeScreens << screen;
|
|
++connectedOutputCount;
|
|
+ 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 (m_primaryScreenNumber == xcbScreenNumber) {
|
|
if (!primaryScreen || (primary && outputs[i] == primary->output)) {
|
|
+ if (primaryScreen)
|
|
+ primaryScreen->setPrimary(false);
|
|
primaryScreen = screen;
|
|
+ primaryScreen->setPrimary(true);
|
|
siblings.prepend(siblings.takeLast());
|
|
}
|
|
}
|
|
- free(output);
|
|
}
|
|
}
|
|
- free(resources);
|
|
}
|
|
- free(primary);
|
|
}
|
|
foreach (QPlatformScreen* s, siblings)
|
|
((QXcbScreen*)s)->setVirtualSiblings(siblings);
|
|
@@ -260,47 +420,7 @@ void QXcbConnection::updateScreens()
|
|
++xcbScreenNumber;
|
|
} // for each xcb screen
|
|
|
|
- QXcbIntegration *integration = QXcbIntegration::instance();
|
|
-
|
|
- // Rebuild screen list, ensuring primary screen is always in front,
|
|
- // both in the QXcbConnection::m_screens list as well as in the
|
|
- // QGuiApplicationPrivate::screen_list list, which gets updated via
|
|
- // - screen added: integration->screenAdded()
|
|
- // - screen removed: integration->destroyScreen
|
|
-
|
|
- // Gather screens to delete
|
|
- QList<QXcbScreen*> screensToDelete;
|
|
- for (int i = m_screens.count() - 1; i >= 0; --i) {
|
|
- if (!activeScreens.contains(m_screens[i])) {
|
|
- screensToDelete.append(m_screens.takeAt(i));
|
|
- }
|
|
- }
|
|
-
|
|
- // If there is a new primary screen, add that one first
|
|
- if (newScreens.contains(primaryScreen)) {
|
|
- newScreens.removeOne(primaryScreen);
|
|
- m_screens.prepend(primaryScreen);
|
|
- qCDebug(lcQpaScreen) << "adding as primary" << primaryScreen;
|
|
- integration->screenAdded(primaryScreen, true);
|
|
- }
|
|
-
|
|
- // Add the remaining new screens
|
|
- foreach (QXcbScreen* screen, newScreens) {
|
|
- m_screens.append(screen);
|
|
- qCDebug(lcQpaScreen) << "adding" << screen;
|
|
- integration->screenAdded(screen);
|
|
- }
|
|
-
|
|
- // Delete the old screens, now that the new ones were added
|
|
- // and we are sure that there is at least one screen available
|
|
- foreach (QXcbScreen* screen, screensToDelete) {
|
|
- qCDebug(lcQpaScreen) << "removing" << screen;
|
|
- integration->destroyScreen(screen);
|
|
- }
|
|
-
|
|
- // Ensure that the primary screen is first in m_screens too
|
|
- // (in case the assignment of primary was the only change,
|
|
- // without adding or removing screens)
|
|
+ // Ensure the primary screen is first in the list
|
|
if (primaryScreen) {
|
|
Q_ASSERT(!m_screens.isEmpty());
|
|
if (m_screens.first() != primaryScreen) {
|
|
@@ -309,13 +429,15 @@ void QXcbConnection::updateScreens()
|
|
}
|
|
}
|
|
|
|
+ // Push the screens to QApplication
|
|
+ QXcbIntegration *integration = QXcbIntegration::instance();
|
|
+ foreach (QXcbScreen* screen, m_screens) {
|
|
+ qCDebug(lcQpaScreen) << "adding" << screen << "(Primary:" << screen->isPrimary() << ")";
|
|
+ integration->screenAdded(screen, screen->isPrimary());
|
|
+ }
|
|
+
|
|
if (!m_screens.isEmpty())
|
|
qCDebug(lcQpaScreen) << "primary output is" << m_screens.first()->name();
|
|
- else
|
|
- // QTBUG-40174, QTBUG-42985: If there are no outputs, then there must be
|
|
- // no QScreen instances; a Qt application can survive this situation, and
|
|
- // start rendering again later when there is a screen again.
|
|
- qCDebug(lcQpaScreen) << "xcb connection has no outputs";
|
|
}
|
|
|
|
QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGrabServer, const char *displayName)
|
|
@@ -395,7 +517,7 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGra
|
|
m_netWmUserTime = XCB_CURRENT_TIME;
|
|
|
|
initializeXRandr();
|
|
- updateScreens();
|
|
+ initializeScreens();
|
|
|
|
initializeGLX();
|
|
initializeXFixes();
|
|
@@ -967,14 +1089,14 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
|
|
m_clipboard->handleXFixesSelectionRequest((xcb_xfixes_selection_notify_event_t *)event);
|
|
#endif
|
|
handled = true;
|
|
+ } else if (has_randr_extension && response_type == xrandr_first_event + XCB_RANDR_NOTIFY) {
|
|
+ updateScreens((xcb_randr_notify_event_t *)event);
|
|
+ handled = true;
|
|
} else if (has_randr_extension && response_type == xrandr_first_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
|
|
- updateScreens();
|
|
xcb_randr_screen_change_notify_event_t *change_event = (xcb_randr_screen_change_notify_event_t *)event;
|
|
foreach (QXcbScreen *s, m_screens) {
|
|
- if (s->root() == change_event->root ) {
|
|
+ if (s->root() == change_event->root )
|
|
s->handleScreenChange(change_event);
|
|
- s->updateRefreshRate();
|
|
- }
|
|
}
|
|
handled = true;
|
|
#ifndef QT_NO_XKB
|
|
@@ -1741,6 +1863,17 @@ void QXcbConnection::initializeXRandr()
|
|
has_randr_extension = false;
|
|
}
|
|
free(xrandr_query);
|
|
+
|
|
+ xcb_screen_iterator_t rootIter = xcb_setup_roots_iterator(m_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
|
|
+ );
|
|
+ }
|
|
}
|
|
|
|
void QXcbConnection::initializeXShape()
|
|
diff --git a/src/plugins/platforms/xcb/qxcbconnection.h b/src/plugins/platforms/xcb/qxcbconnection.h
|
|
index 6e7e87d..9c190d7 100644
|
|
--- a/src/plugins/platforms/xcb/qxcbconnection.h
|
|
+++ b/src/plugins/platforms/xcb/qxcbconnection.h
|
|
@@ -35,6 +35,7 @@
|
|
#define QXCBCONNECTION_H
|
|
|
|
#include <xcb/xcb.h>
|
|
+#include <xcb/randr.h>
|
|
|
|
#include <QHash>
|
|
#include <QList>
|
|
@@ -492,9 +493,15 @@ private:
|
|
void initializeXShape();
|
|
void initializeXKB();
|
|
void handleClientMessageEvent(const xcb_client_message_event_t *event);
|
|
- QXcbScreen* findOrCreateScreen(QList<QXcbScreen *>& newScreens, int screenNumber,
|
|
- xcb_screen_t* xcbScreen, xcb_randr_get_output_info_reply_t *output = NULL);
|
|
- void updateScreens();
|
|
+ QXcbScreen* createScreen(int screenNumber, xcb_screen_t* xcbScreen,
|
|
+ xcb_randr_output_t outputId = XCB_NONE,
|
|
+ xcb_randr_get_output_info_reply_t *output = 0);
|
|
+ QXcbScreen* findScreenForCrtc(xcb_window_t rootWindow, xcb_randr_crtc_t crtc);
|
|
+ QXcbScreen* findScreenForOutput(xcb_window_t rootWindow, xcb_randr_output_t output);
|
|
+ xcb_screen_t* xcbScreenForRootWindow(xcb_window_t rootWindow, int *xcbScreenNumber = 0);
|
|
+ bool checkOutputIsPrimary(xcb_window_t rootWindow, xcb_randr_output_t output);
|
|
+ void initializeScreens();
|
|
+ void updateScreens(const xcb_randr_notify_event_t *event);
|
|
void handleButtonPress(xcb_generic_event_t *event);
|
|
void handleButtonRelease(xcb_generic_event_t *event);
|
|
|
|
diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp
|
|
index 2aebb84..d89bdbb 100644
|
|
--- a/src/plugins/platforms/xcb/qxcbscreen.cpp
|
|
+++ b/src/plugins/platforms/xcb/qxcbscreen.cpp
|
|
@@ -48,10 +48,15 @@
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
QXcbScreen::QXcbScreen(QXcbConnection *connection, xcb_screen_t *scr,
|
|
- xcb_randr_get_output_info_reply_t *output, QString outputName, int number)
|
|
+ xcb_randr_output_t outputId, xcb_randr_get_output_info_reply_t *output,
|
|
+ QString outputName, int number)
|
|
: QXcbObject(connection)
|
|
, m_screen(scr)
|
|
+ , m_output(outputId)
|
|
, m_crtc(output ? output->crtc : 0)
|
|
+ , m_mode(XCB_NONE)
|
|
+ , m_primary(false)
|
|
+ , m_rotation(XCB_RANDR_ROTATION_ROTATE_0)
|
|
, m_outputName(outputName)
|
|
, m_outputSizeMillimeters(output ? QSize(output->mm_width, output->mm_height) : QSize())
|
|
, m_virtualSize(scr->width_in_pixels, scr->height_in_pixels)
|
|
@@ -67,11 +72,20 @@ QXcbScreen::QXcbScreen(QXcbConnection *connection, xcb_screen_t *scr,
|
|
, m_antialiasingEnabled(-1)
|
|
, m_xSettings(0)
|
|
{
|
|
- if (connection->hasXRandr())
|
|
+ if (connection->hasXRandr()) {
|
|
xcb_randr_select_input(xcb_connection(), screen()->root, true);
|
|
-
|
|
- updateGeometry(output ? output->timestamp : 0);
|
|
- updateRefreshRate();
|
|
+ xcb_randr_get_crtc_info_cookie_t crtcCookie =
|
|
+ xcb_randr_get_crtc_info_unchecked(xcb_connection(), m_crtc, output ? output->timestamp : 0);
|
|
+ xcb_randr_get_crtc_info_reply_t *crtc =
|
|
+ xcb_randr_get_crtc_info_reply(xcb_connection(), crtcCookie, NULL);
|
|
+ if (crtc) {
|
|
+ updateGeometry(QRect(crtc->x, crtc->y, crtc->width, crtc->height), crtc->rotation);
|
|
+ updateRefreshRate(crtc->mode);
|
|
+ free(crtc);
|
|
+ }
|
|
+ } else {
|
|
+ updateGeometry(output ? output->timestamp : 0);
|
|
+ }
|
|
|
|
const int dpr = int(devicePixelRatio());
|
|
// On VNC, it can be that physical size is unknown while
|
|
@@ -352,9 +366,15 @@ QPlatformCursor *QXcbScreen::cursor() const
|
|
*/
|
|
void QXcbScreen::handleScreenChange(xcb_randr_screen_change_notify_event_t *change_event)
|
|
{
|
|
- updateGeometry(change_event->config_timestamp);
|
|
+ // No need to do anything when screen rotation did not change - if any
|
|
+ // xcb output geometry has changed, we will get RRCrtcChangeNotify and
|
|
+ // RROutputChangeNotify events next
|
|
+ if (change_event->rotation == m_rotation)
|
|
+ return;
|
|
|
|
- switch (change_event->rotation) {
|
|
+ m_rotation = change_event->rotation;
|
|
+ updateGeometry(change_event->timestamp);
|
|
+ switch (m_rotation) {
|
|
case XCB_RANDR_ROTATION_ROTATE_0: // xrandr --rotate normal
|
|
m_orientation = Qt::LandscapeOrientation;
|
|
m_virtualSize.setWidth(change_event->width);
|
|
@@ -398,35 +418,37 @@ void QXcbScreen::handleScreenChange(xcb_randr_screen_change_notify_event_t *chan
|
|
|
|
void QXcbScreen::updateGeometry(xcb_timestamp_t timestamp)
|
|
{
|
|
- QRect xGeometry;
|
|
- QRect xAvailableGeometry;
|
|
+ xcb_randr_get_crtc_info_cookie_t crtcCookie =
|
|
+ xcb_randr_get_crtc_info_unchecked(xcb_connection(), m_crtc, timestamp);
|
|
+ xcb_randr_get_crtc_info_reply_t *crtc =
|
|
+ xcb_randr_get_crtc_info_reply(xcb_connection(), crtcCookie, NULL);
|
|
+ if (crtc) {
|
|
+ updateGeometry(QRect(crtc->x, crtc->y, crtc->width, crtc->height), crtc->rotation);
|
|
+ free(crtc);
|
|
+ }
|
|
+}
|
|
|
|
- if (connection()->hasXRandr()) {
|
|
- xcb_randr_get_crtc_info_reply_t *crtc = xcb_randr_get_crtc_info_reply(xcb_connection(),
|
|
- xcb_randr_get_crtc_info_unchecked(xcb_connection(), m_crtc, timestamp), NULL);
|
|
- if (crtc) {
|
|
- xGeometry = QRect(crtc->x, crtc->y, crtc->width, crtc->height);
|
|
- xAvailableGeometry = xGeometry;
|
|
- switch (crtc->rotation) {
|
|
- case XCB_RANDR_ROTATION_ROTATE_0: // xrandr --rotate normal
|
|
- m_orientation = Qt::LandscapeOrientation;
|
|
- m_sizeMillimeters = m_outputSizeMillimeters;
|
|
- break;
|
|
- case XCB_RANDR_ROTATION_ROTATE_90: // xrandr --rotate left
|
|
- m_orientation = Qt::PortraitOrientation;
|
|
- m_sizeMillimeters = m_outputSizeMillimeters.transposed();
|
|
- break;
|
|
- case XCB_RANDR_ROTATION_ROTATE_180: // xrandr --rotate inverted
|
|
- m_orientation = Qt::InvertedLandscapeOrientation;
|
|
- m_sizeMillimeters = m_outputSizeMillimeters;
|
|
- break;
|
|
- case XCB_RANDR_ROTATION_ROTATE_270: // xrandr --rotate right
|
|
- m_orientation = Qt::InvertedPortraitOrientation;
|
|
- m_sizeMillimeters = m_outputSizeMillimeters.transposed();
|
|
- break;
|
|
- }
|
|
- free(crtc);
|
|
- }
|
|
+void QXcbScreen::updateGeometry(const QRect &geom, uint8_t rotation)
|
|
+{
|
|
+ QRect xGeometry = geom;
|
|
+ QRect xAvailableGeometry = xGeometry;
|
|
+ switch (rotation) {
|
|
+ case XCB_RANDR_ROTATION_ROTATE_0: // xrandr --rotate normal
|
|
+ m_orientation = Qt::LandscapeOrientation;
|
|
+ m_sizeMillimeters = m_outputSizeMillimeters;
|
|
+ break;
|
|
+ case XCB_RANDR_ROTATION_ROTATE_90: // xrandr --rotate left
|
|
+ m_orientation = Qt::PortraitOrientation;
|
|
+ m_sizeMillimeters = m_outputSizeMillimeters.transposed();
|
|
+ break;
|
|
+ case XCB_RANDR_ROTATION_ROTATE_180: // xrandr --rotate inverted
|
|
+ m_orientation = Qt::InvertedLandscapeOrientation;
|
|
+ m_sizeMillimeters = m_outputSizeMillimeters;
|
|
+ break;
|
|
+ case XCB_RANDR_ROTATION_ROTATE_270: // xrandr --rotate right
|
|
+ m_orientation = Qt::InvertedPortraitOrientation;
|
|
+ m_sizeMillimeters = m_outputSizeMillimeters.transposed();
|
|
+ break;
|
|
}
|
|
|
|
xcb_get_property_reply_t * workArea =
|
|
@@ -455,31 +477,38 @@ void QXcbScreen::updateGeometry(xcb_timestamp_t timestamp)
|
|
m_geometry = QRect(xGeometry.topLeft()/dpr, xGeometry.size()/dpr);
|
|
m_nativeGeometry = QRect(xGeometry.topLeft(), xGeometry.size());
|
|
m_availableGeometry = QRect(xAvailableGeometry.topLeft()/dpr, xAvailableGeometry.size()/dpr);
|
|
-
|
|
QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), m_geometry, m_availableGeometry);
|
|
}
|
|
|
|
-void QXcbScreen::updateRefreshRate()
|
|
+void QXcbScreen::updateRefreshRate(xcb_randr_mode_t mode)
|
|
{
|
|
if (!connection()->hasXRandr())
|
|
return;
|
|
|
|
- int rate = m_refreshRate;
|
|
-
|
|
- xcb_randr_get_screen_info_reply_t *screenInfoReply =
|
|
- xcb_randr_get_screen_info_reply(xcb_connection(), xcb_randr_get_screen_info_unchecked(xcb_connection(), m_screen->root), 0);
|
|
-
|
|
- if (screenInfoReply) {
|
|
- rate = screenInfoReply->rate;
|
|
- free(screenInfoReply);
|
|
- }
|
|
-
|
|
- if (rate == m_refreshRate)
|
|
+ if (m_mode == mode)
|
|
return;
|
|
|
|
- m_refreshRate = rate;
|
|
+ // we can safely use get_screen_resources_current here, because in order to
|
|
+ // get here, we must have called get_screen_resources before
|
|
+ xcb_randr_get_screen_resources_current_cookie_t resourcesCookie =
|
|
+ xcb_randr_get_screen_resources_current_unchecked(xcb_connection(), m_screen->root);
|
|
+ xcb_randr_get_screen_resources_current_reply_t *resources =
|
|
+ xcb_randr_get_screen_resources_current_reply(xcb_connection(), resourcesCookie, NULL);
|
|
+ if (resources) {
|
|
+ xcb_randr_mode_info_iterator_t modesIter =
|
|
+ xcb_randr_get_screen_resources_current_modes_iterator(resources);
|
|
+ for (; modesIter.rem; xcb_randr_mode_info_next(&modesIter)) {
|
|
+ xcb_randr_mode_info_t *modeInfo = modesIter.data;
|
|
+ if (modeInfo->id == mode) {
|
|
+ m_refreshRate = modeInfo->dot_clock / (modeInfo->htotal * modeInfo->vtotal);
|
|
+ m_mode = mode;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
|
|
- QWindowSystemInterface::handleScreenRefreshRateChange(QPlatformScreen::screen(), rate);
|
|
+ free(resources);
|
|
+ QWindowSystemInterface::handleScreenRefreshRateChange(QPlatformScreen::screen(), m_refreshRate);
|
|
+ }
|
|
}
|
|
|
|
QPixmap QXcbScreen::grabWindow(WId window, int x, int y, int width, int height) const
|
|
diff --git a/src/plugins/platforms/xcb/qxcbscreen.h b/src/plugins/platforms/xcb/qxcbscreen.h
|
|
index 81c3445..a654a3e 100644
|
|
--- a/src/plugins/platforms/xcb/qxcbscreen.h
|
|
+++ b/src/plugins/platforms/xcb/qxcbscreen.h
|
|
@@ -57,7 +57,8 @@ class QXcbScreen : public QXcbObject, public QPlatformScreen
|
|
{
|
|
public:
|
|
QXcbScreen(QXcbConnection *connection, xcb_screen_t *screen,
|
|
- xcb_randr_get_output_info_reply_t *output, QString outputName, int number);
|
|
+ xcb_randr_output_t outputId, xcb_randr_get_output_info_reply_t *output,
|
|
+ QString outputName, int number);
|
|
~QXcbScreen();
|
|
|
|
QPixmap grabWindow(WId window, int x, int y, int width, int height) const;
|
|
@@ -79,11 +80,19 @@ public:
|
|
Qt::ScreenOrientation orientation() const { return m_orientation; }
|
|
QList<QPlatformScreen *> virtualSiblings() const { return m_siblings; }
|
|
void setVirtualSiblings(QList<QPlatformScreen *> sl) { m_siblings = sl; }
|
|
+ void removeVirtualSibling(QPlatformScreen *s) { m_siblings.removeOne(s); }
|
|
+ void addVirtualSibling(QPlatformScreen *s) { ((QXcbScreen *) s)->isPrimary() ? m_siblings.prepend(s) : m_siblings.append(s); }
|
|
+
|
|
+ void setPrimary(bool primary) { m_primary = primary; }
|
|
+ bool isPrimary() const { return m_primary; }
|
|
|
|
int screenNumber() const { return m_number; }
|
|
|
|
xcb_screen_t *screen() const { return m_screen; }
|
|
xcb_window_t root() const { return m_screen->root; }
|
|
+ xcb_randr_output_t output() const { return m_output; }
|
|
+ xcb_randr_crtc_t crtc() const { return m_crtc; }
|
|
+ xcb_randr_mode_t mode() const { return m_mode; }
|
|
|
|
xcb_window_t clientLeader() const { return m_clientLeader; }
|
|
|
|
@@ -97,8 +106,9 @@ public:
|
|
QString name() const { return m_outputName; }
|
|
|
|
void handleScreenChange(xcb_randr_screen_change_notify_event_t *change_event);
|
|
- void updateGeometry(xcb_timestamp_t timestamp);
|
|
- void updateRefreshRate();
|
|
+ void updateGeometry(const QRect &geom, uint8_t rotation);
|
|
+ void updateGeometry(xcb_timestamp_t timestamp = XCB_TIME_CURRENT_TIME);
|
|
+ void updateRefreshRate(xcb_randr_mode_t mode);
|
|
|
|
void readXResources();
|
|
|
|
@@ -116,7 +126,12 @@ private:
|
|
void sendStartupMessage(const QByteArray &message) const;
|
|
|
|
xcb_screen_t *m_screen;
|
|
+ xcb_randr_output_t m_output;
|
|
xcb_randr_crtc_t m_crtc;
|
|
+ xcb_randr_mode_t m_mode;
|
|
+ bool m_primary;
|
|
+ uint8_t m_rotation;
|
|
+
|
|
QString m_outputName;
|
|
QSizeF m_outputSizeMillimeters;
|
|
QSizeF m_sizeMillimeters;
|