xcb: support xrandr(1.5) monitor setup

More information about monitor in xrandr 1.5, see
https://keithp.com/blogs/MST-monitors/

Since this change, screen is logical instead of physical.

If xrandr 1.5 and later is installed, Qt screen info will get
from xrandr monitor object instead of xrandr output if only have
1.2 to 1.4.

Users can manipulate monitor as they want, for example, a
combination for two physical screens, half of one screen and etc.

Didn't have chance to access MST monitors, but it should work
if xrandr monitor object was created automatically.

[ChangeLog][xcb] Qt screen info will get from xrandr monitor
object if 1.5 is installed.

Fixes: QTBUG-65457
Change-Id: Iad339cc0d4293b2403b4ef6bf6eb770feb3e685f
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
This commit is contained in:
Liang Qi 2020-11-04 10:29:13 +01:00 committed by Shawn Rutledge
parent f3743073a7
commit 9a4c98e556
12 changed files with 561 additions and 119 deletions

View File

@ -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()) {

View File

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

View File

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

View File

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

View File

@ -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));
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -27,6 +27,7 @@ qnetworkaccessmanager/qget \
qnetworkreply \
qstorageinfo \
qscreen \
qscreen_xrandr \
qssloptions \
qsslsocket \
qsysinfo \

View File

@ -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
)

View File

@ -0,0 +1,6 @@
CONFIG += testcase
TARGET = tst_qscreen_xrandr
QT += core-private gui-private testlib
SOURCES += tst_qscreen_xrandr.cpp

View File

@ -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);