xcb: dynamic QScreens; primary first; corrected logical DPI

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>
This commit is contained in:
Shawn Rutledge 2012-09-19 11:55:44 +02:00 committed by The Qt Project
parent 06c89ae026
commit 1a31561178
6 changed files with 151 additions and 94 deletions

View 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 ) {

View 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];

View 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

View File

@ -106,6 +106,8 @@ private:
#endif
QScopedPointer<QPlatformServices> m_services;
friend class QXcbConnection; // access QPlatformIntegration::screenAdded()
};
QT_END_NAMESPACE

View 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;

View 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; }