xcb: dynamic QScreens; primary first; corrected logical DPI
authorShawn Rutledge <shawn.rutledge@digia.com>
Wed, 19 Sep 2012 09:55:44 +0000 (11:55 +0200)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Sun, 23 Sep 2012 22:26:07 +0000 (00:26 +0200)
A new QScreen is created when an output is activated (monitor or
projector is added, for example), and destroyed when the output is
turned off.  Ensures that screens and siblings are always in
the right order: primary comes first.
Logical DPI is derived from virtual geom / virtual size,
which will be different than output geom / physical size
if X was started with --dpi override.  This is a good thing:
when X gets wrong EDID info for physical size and you need to
override it to get reasonable font sizes, Qt will heed the
logical DPI for font sizing.

Change-Id: I5e3de34013c1b6b21067243de56f3f1eb72787fa
Reviewed-by: Samuel Rødal <samuel.rodal@digia.com>
src/plugins/platforms/xcb/qxcbconnection.cpp
src/plugins/platforms/xcb/qxcbconnection.h
src/plugins/platforms/xcb/qxcbintegration.cpp
src/plugins/platforms/xcb/qxcbintegration.h
src/plugins/platforms/xcb/qxcbscreen.cpp
src/plugins/platforms/xcb/qxcbscreen.h

index 26f304b..9e394d6 100644 (file)
@@ -50,6 +50,7 @@
 #include "qxcbdrag.h"
 #include "qxcbwmsupport.h"
 #include "qxcbnativeinterface.h"
+#include "qxcbintegration.h"
 
 #include <QtAlgorithms>
 #include <QSocketNotifier>
@@ -100,27 +101,147 @@ static int nullErrorHandler(Display *, XErrorEvent *)
 }
 #endif
 
-QXcbScreen* QXcbConnection::createScreenWithFabricatedName(int screenNumber, xcb_screen_t* xcbScreen)
+QXcbScreen* QXcbConnection::findOrCreateScreen(QList<QXcbScreen *>& newScreens,
+    int screenNumber, xcb_screen_t* xcbScreen, xcb_randr_get_output_info_reply_t *output)
 {
-    QByteArray displayName = m_displayName;
-    int dotPos = displayName.lastIndexOf('.');
-    if (dotPos != -1)
-        displayName.truncate(dotPos);
-    QString name = displayName + QLatin1Char('.') + QString::number(screenNumber);
-    QXcbScreen *screen = new QXcbScreen(this, xcbScreen, NULL, name, screenNumber);
-    // make sure the primary screen appears first since it is used by QGuiApplication::primaryScreen()
-    if (m_primaryScreen == screenNumber) {
-        m_screens.prepend(screen);
-    } else {
-        m_screens.append(screen);
+    QString name;
+    if (output)
+        name = QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output),
+                xcb_randr_get_output_info_name_length(output));
+    else {
+        QByteArray displayName = m_displayName;
+        int dotPos = displayName.lastIndexOf('.');
+        if (dotPos != -1)
+            displayName.truncate(dotPos);
+        name = displayName + QLatin1Char('.') + QString::number(screenNumber);
     }
-    return screen;
+    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;
+}
+
+/*!
+    \brief Synchronizes the screen list, adds new screens, removes deleted ones
+*/
+void QXcbConnection::updateScreens()
+{
+    xcb_screen_iterator_t it = xcb_setup_roots_iterator(m_setup);
+    int screenNumber = 0;       // index of this QScreen in QGuiApplication::screens()
+    int xcbScreenNumber = 0;    // screen number in the xcb sense
+    QSet<QXcbScreen *> activeScreens;
+    QList<QXcbScreen *> newScreens;
+    QXcbScreen* primaryScreen = NULL;
+    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;
+        QList<QPlatformScreen *> siblings;
+        if (has_randr_extension) {
+            xcb_randr_get_output_primary_cookie_t primaryCookie =
+                xcb_randr_get_output_primary_unchecked(xcb_connection(), xcbScreen->root);
+            xcb_randr_get_screen_resources_current_cookie_t resourcesCookie =
+                xcb_randr_get_screen_resources_current_unchecked(xcb_connection(), xcbScreen->root);
+            xcb_randr_get_output_primary_reply_t *primary =
+                    xcb_randr_get_output_primary_reply(xcb_connection(), primaryCookie, NULL);
+            xcb_randr_get_screen_resources_current_reply_t *resources =
+                    xcb_randr_get_screen_resources_current_reply(xcb_connection(), resourcesCookie, NULL);
+            xcb_timestamp_t timestamp = resources->config_timestamp;
+            int outputCount = xcb_randr_get_screen_resources_current_outputs_length(resources);
+            xcb_randr_output_t *outputs = xcb_randr_get_screen_resources_current_outputs(resources);
+
+            if (outputCount == 0) {
+                // This happens on VNC for example.  But there is actually a screen anyway.
+#ifdef Q_XCB_DEBUG
+                qDebug("Found a screen with zero outputs");
+#endif
+                QXcbScreen* screen = findOrCreateScreen(newScreens, xcbScreenNumber, xcbScreen);
+                siblings << screen;
+                activeScreens << screen;
+                if (!primaryScreen)
+                    primaryScreen = screen;
+                ++screenNumber;
+            }
+            for (int i = 0; i < outputCount; i++) {
+                xcb_randr_get_output_info_reply_t *output =
+                        xcb_randr_get_output_info_reply(xcb_connection(),
+                            xcb_randr_get_output_info_unchecked(xcb_connection(), outputs[i], timestamp), NULL);
+                if (output == NULL)
+                    continue;
+
+#ifdef Q_XCB_DEBUG
+                QString outputName = QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output),
+                    xcb_randr_get_output_info_name_length(output));
+#endif
+
+                if (output->crtc == XCB_NONE) {
+#ifdef Q_XCB_DEBUG
+                    qDebug("Screen output %s is not connected", qPrintable(outputName));
+#endif
+                    continue;
+                }
+
+                QXcbScreen *screen = findOrCreateScreen(newScreens, xcbScreenNumber, xcbScreen, output);
+                siblings << screen;
+                activeScreens << screen;
+                ++screenNumber;
+                if (!primaryScreen && primary) {
+                    if (primary->output == XCB_NONE || outputs[i] == primary->output) {
+                        primaryScreen = screen;
+                        siblings.prepend(siblings.takeLast());
+#ifdef Q_XCB_DEBUG
+                        qDebug("Primary output is %d: %s", primary->output, qPrintable(outputName));
+#endif
+                    }
+                }
+                free(output);
+            }
+            free(primary);
+            free(resources);
+        } else {
+            QXcbScreen *screen = findOrCreateScreen(newScreens, xcbScreenNumber, xcbScreen);
+            siblings << screen;
+            activeScreens << screen;
+            if (!primaryScreen)
+                primaryScreen = screen;
+            ++screenNumber;
+        }
+        foreach (QPlatformScreen* s, siblings)
+            ((QXcbScreen*)s)->setVirtualSiblings(siblings);
+        xcb_screen_next(&it);
+        ++xcbScreenNumber;
+    }
+
+    // Now activeScreens is the complete set of screens which are active at this time.
+    // Delete any existing screens which are not in activeScreens
+    for (int i = m_screens.count() - 1; i >= 0; --i)
+        if (!activeScreens.contains(m_screens[i])) {
+            delete m_screens[i];
+            m_screens.removeAt(i);
+        }
+
+    // Add any new screens, and make sure the primary screen comes first
+    // since it is used by QGuiApplication::primaryScreen()
+    foreach (QXcbScreen* screen, newScreens) {
+        if (screen == primaryScreen)
+            m_screens.prepend(screen);
+        else
+            m_screens.append(screen);
+    }
+
+    // Now that they are in the right order, emit the added signals for new screens only
+    foreach (QXcbScreen* screen, m_screens)
+        if (newScreens.contains(screen))
+            ((QXcbIntegration*)QGuiApplicationPrivate::platformIntegration())->screenAdded(screen);
 }
 
 QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, const char *displayName)
     : m_connection(0)
     , m_primaryScreen(0)
-    , m_primaryOutput(-1)
     , m_displayName(displayName ? QByteArray(displayName) : qgetenv("DISPLAY"))
     , m_nativeInterface(nativeInterface)
 #ifdef XCB_USE_XINPUT2_MAEMO
@@ -182,80 +303,8 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, const char
 
     m_time = XCB_CURRENT_TIME;
 
-    xcb_screen_iterator_t it = xcb_setup_roots_iterator(m_setup);
-
     initializeXRandr();
-
-    int screenNumber = 0;
-    while (it.rem) {
-        // Each "screen" in xcb terminology is a virtual desktop,
-        // potentially a collection of separate juxtaposed monitors.
-        // Now iterate the individual outputs (e.g. DVI-I-1, VGA-1, etc.)
-        // and make a QScreen instance for each.
-        xcb_screen_t *xcbScreen = it.data;
-        QList<QPlatformScreen *> siblings;
-        if (has_randr_extension) {
-            xcb_randr_get_output_primary_cookie_t primaryCookie =
-                xcb_randr_get_output_primary_unchecked(xcb_connection(), xcbScreen->root);
-            xcb_randr_get_screen_resources_current_cookie_t resourcesCookie =
-                xcb_randr_get_screen_resources_current_unchecked(xcb_connection(), xcbScreen->root);
-            xcb_randr_get_output_primary_reply_t *primary =
-                    xcb_randr_get_output_primary_reply(xcb_connection(), primaryCookie, NULL);
-            xcb_randr_get_screen_resources_current_reply_t *resources =
-                    xcb_randr_get_screen_resources_current_reply(xcb_connection(), resourcesCookie, NULL);
-            xcb_timestamp_t timestamp = resources->config_timestamp;
-            int outputCount = xcb_randr_get_screen_resources_current_outputs_length(resources);
-            xcb_randr_output_t *outputs = xcb_randr_get_screen_resources_current_outputs(resources);
-
-            if (outputCount == 0) {
-                // This happens on VNC for example.  But there is actually a screen anyway.
-#ifdef Q_XCB_DEBUG
-                qDebug("Found a screen with zero outputs");
-#endif
-                QXcbScreen *screen = createScreenWithFabricatedName(screenNumber, it.data);
-                siblings << screen;
-                ++screenNumber;
-            }
-            for (int i = 0; i < outputCount; i++) {
-                xcb_randr_get_output_info_reply_t *output =
-                        xcb_randr_get_output_info_reply(xcb_connection(),
-                            xcb_randr_get_output_info_unchecked(xcb_connection(), outputs[i], timestamp), NULL);
-                if (output == NULL)
-                    continue;
-                QString outputName = QString::fromUtf8((const char*)xcb_randr_get_output_info_name(output),
-                                                       xcb_randr_get_output_info_name_length(output));
-
-                if (output->crtc == XCB_NONE) {
-#ifdef Q_XCB_DEBUG
-                    qDebug("Screen output %s is not connected", qPrintable(outputName));
-#endif
-                    continue;
-                }
-
-                QXcbScreen *screen = new QXcbScreen(this, xcbScreen, output, outputName, screenNumber);
-                siblings << screen;
-                // make sure the primary screen appears first since it is used by QGuiApplication::primaryScreen()
-                if (outputs[i] == primary->output && m_primaryOutput < 0) {
-                    m_primaryOutput = screenNumber;
-                    m_screens.prepend(screen);
-                } else {
-                    m_screens.append(screen);
-                }
-                ++screenNumber;
-                free(output);
-            }
-
-            free(primary);
-            free(resources);
-        } else {
-            QXcbScreen *screen = createScreenWithFabricatedName(screenNumber, it.data);
-            siblings << screen;
-            ++screenNumber;
-        }
-        foreach (QPlatformScreen* s, siblings)
-            ((QXcbScreen*)s)->setVirtualSiblings(siblings);
-        xcb_screen_next(&it);
-    }
+    updateScreens();
 
     m_connectionEventListener = xcb_generate_id(m_connection);
     xcb_create_window(m_connection, XCB_COPY_FROM_PARENT,
@@ -709,6 +758,7 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
 #endif
             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 ) {
index df91ad6..08dd304 100644 (file)
@@ -66,6 +66,7 @@ struct XInput2MaemoData;
 #endif
 struct XInput2DeviceData;
 #endif
+struct xcb_randr_get_output_info_reply_t;
 
 //#define Q_XCB_DEBUG
 
@@ -398,7 +399,9 @@ private:
     void handleGenericEventMaemo(xcb_ge_event_t *event);
 #endif
     void handleClientMessageEvent(const xcb_client_message_event_t *event);
-    QXcbScreen* createScreenWithFabricatedName(int screenNumber, xcb_screen_t* xcbScreen);
+    QXcbScreen* findOrCreateScreen(QList<QXcbScreen *>& newScreens, int screenNumber,
+        xcb_screen_t* xcbScreen, xcb_randr_get_output_info_reply_t *output = NULL);
+    void updateScreens();
 
     bool m_xi2Enabled;
     int m_xi2Minor;
@@ -443,7 +446,6 @@ private:
 
     QList<QXcbScreen *> m_screens;
     int m_primaryScreen;
-    int m_primaryOutput;
 
     xcb_atom_t m_allAtoms[QXcbAtom::NAtoms];
 
index 0b7f17d..8784046 100644 (file)
@@ -110,10 +110,6 @@ QXcbIntegration::QXcbIntegration(const QStringList &parameters)
         m_connections << new QXcbConnection(m_nativeInterface.data(), display.toLatin1().constData());
     }
 
-    foreach (QXcbConnection *connection, m_connections)
-        foreach (QXcbScreen *screen, connection->screens())
-            screenAdded(screen);
-
     m_fontDatabase.reset(new QGenericUnixFontDatabase());
     m_inputContext.reset(QPlatformInputContextFactory::create());
 #ifndef QT_NO_ACCESSIBILITY
index 2a7bc60..7f7c523 100644 (file)
@@ -106,6 +106,8 @@ private:
 #endif
 
     QScopedPointer<QPlatformServices> m_services;
+
+    friend class QXcbConnection; // access QPlatformIntegration::screenAdded()
 };
 
 QT_END_NAMESPACE
index 3798bf0..6452186 100644 (file)
@@ -83,7 +83,7 @@ QXcbScreen::QXcbScreen(QXcbConnection *connection, xcb_screen_t *scr,
 
 #ifdef Q_XCB_DEBUG
     qDebug();
-    qDebug("Screen %s:", m_outputName.toUtf8().constData());
+    qDebug("Screen output %s of xcb screen %d:", m_outputName.toUtf8().constData(), m_number);
     qDebug("  width..........: %lf", m_sizeMillimeters.width());
     qDebug("  height.........: %lf", m_sizeMillimeters.height());
     qDebug("  geometry.......: %d x %d +%d +%d", m_geometry.width(), m_geometry.height(), m_geometry.x(), m_geometry.y());
@@ -240,6 +240,12 @@ QImage::Format QXcbScreen::format() const
     return QImage::Format_RGB32;
 }
 
+QDpi QXcbScreen::logicalDpi() const
+{
+    return QDpi(25.4 * m_virtualSize.width() / m_virtualSizeMillimeters.width(),
+                25.4 * m_virtualSize.height() / m_virtualSizeMillimeters.height());
+}
+
 QPlatformCursor *QXcbScreen::cursor() const
 {
     return m_cursor;
index f5439b3..d9eee46 100644 (file)
@@ -71,6 +71,7 @@ public:
     int depth() const { return m_screen->root_depth; }
     QImage::Format format() const;
     QSizeF physicalSize() const { return m_sizeMillimeters; }
+    QDpi logicalDpi() const;
     QPlatformCursor *cursor() const;
     qreal refreshRate() const { return m_refreshRate; }
     Qt::ScreenOrientation orientation() const { return m_orientation; }