From 4e1b09fa8ff1a9ab42c0a29a2efe1ae7f4700d71 Mon Sep 17 00:00:00 2001 From: Paul Olav Tvete Date: Fri, 20 Mar 2015 17:30:26 +0100 Subject: [PATCH] Keep screen geometries from overlapping The simple mapping of dividing each position by the devicePixelRatio does not work when screens have different DPR. If the low-DPR screen above or to the left of the high-DPR screen, the geometries will overlap in the Qt coordinate system. This change introduces a new mapping where the origin of each screen does not move. This mapping is not perfect: it will have gaps between contiguous screens. However, it will keep non-overlapping screens non-overlapping in the Qt coordinate system. Since there is no longer a simple linear coordinate transform, we have to add screen-dependent mapping functions, and distinguish between local and non-local coordinates. A side benefit is that the code is now easier to read, since we remove most manual coordinate transformation. We also have to cache the screen of each window: since we send resize events before screen change events, we cannot rely on QPlatformWindow::screen() (which is set from the screen change event). Task-number: QTBUG-45076 Change-Id: Ie95a0b71ae274e02903caa102a98af2050a44129 Reviewed-by: Shawn Rutledge --- src/plugins/platforms/xcb/qxcbcursor.cpp | 7 +- src/plugins/platforms/xcb/qxcbscreen.cpp | 29 ++++- src/plugins/platforms/xcb/qxcbscreen.h | 5 + src/plugins/platforms/xcb/qxcbwindow.cpp | 143 +++++++++++++++-------- src/plugins/platforms/xcb/qxcbwindow.h | 10 +- 5 files changed, 138 insertions(+), 56 deletions(-) diff --git a/src/plugins/platforms/xcb/qxcbcursor.cpp b/src/plugins/platforms/xcb/qxcbcursor.cpp index e30cc26313..e51ab85e30 100644 --- a/src/plugins/platforms/xcb/qxcbcursor.cpp +++ b/src/plugins/platforms/xcb/qxcbcursor.cpp @@ -633,18 +633,17 @@ void QXcbCursor::queryPointer(QXcbConnection *c, xcb_window_t *rootWin, QPoint * QPoint QXcbCursor::pos() const { - const int dpr = int(m_screen->devicePixelRatio()); QPoint p; queryPointer(connection(), 0, &p); - return p / dpr; + return m_screen->mapFromNative(p); } void QXcbCursor::setPos(const QPoint &pos) { - const int dpr = int(m_screen->devicePixelRatio()); + const QPoint xPos = m_screen->mapToNative(pos); xcb_window_t root = 0; queryPointer(connection(), &root, 0); - xcb_warp_pointer(xcb_connection(), XCB_NONE, root, 0, 0, 0, 0, pos.x()*dpr, pos.y()*dpr); + xcb_warp_pointer(xcb_connection(), XCB_NONE, root, 0, 0, 0, 0, xPos.x(), xPos.y()); xcb_flush(xcb_connection()); } diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp index fcf70f3c51..bcaa13eb1e 100644 --- a/src/plugins/platforms/xcb/qxcbscreen.cpp +++ b/src/plugins/platforms/xcb/qxcbscreen.cpp @@ -273,6 +273,31 @@ QWindow *QXcbScreen::topLevelAt(const QPoint &p) const return 0; } + +QPoint QXcbScreen::mapToNative(const QPoint &pos) const +{ + const int dpr = int(devicePixelRatio()); + return (pos - m_geometry.topLeft()) * dpr + m_nativeGeometry.topLeft(); +} + +QPoint QXcbScreen::mapFromNative(const QPoint &pos) const +{ + const int dpr = int(devicePixelRatio()); + return (pos - m_nativeGeometry.topLeft()) / dpr + m_geometry.topLeft(); +} + +QRect QXcbScreen::mapToNative(const QRect &rect) const +{ + const int dpr = int(devicePixelRatio()); + return QRect(mapToNative(rect.topLeft()), rect.size() * dpr); +} + +QRect QXcbScreen::mapFromNative(const QRect &rect) const +{ + const int dpr = int(devicePixelRatio()); + return QRect(mapFromNative(rect.topLeft()), rect.size() / dpr); +} + void QXcbScreen::windowShown(QXcbWindow *window) { // Freedesktop.org Startup Notification @@ -499,9 +524,9 @@ void QXcbScreen::updateGeometry(const QRect &geom, uint8_t rotation) qreal dpi = xGeometry.width() / physicalSize().width() * qreal(25.4); m_devicePixelRatio = qRound(dpi/96); const int dpr = int(devicePixelRatio()); // we may override m_devicePixelRatio - m_geometry = QRect(xGeometry.topLeft()/dpr, xGeometry.size()/dpr); + m_geometry = QRect(xGeometry.topLeft(), xGeometry.size()/dpr); m_nativeGeometry = QRect(xGeometry.topLeft(), xGeometry.size()); - m_availableGeometry = QRect(xAvailableGeometry.topLeft()/dpr, xAvailableGeometry.size()/dpr); + m_availableGeometry = QRect(mapFromNative(xAvailableGeometry.topLeft()), xAvailableGeometry.size()/dpr); QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), m_geometry, m_availableGeometry); } diff --git a/src/plugins/platforms/xcb/qxcbscreen.h b/src/plugins/platforms/xcb/qxcbscreen.h index 6b60ea9bb1..a29efc1e79 100644 --- a/src/plugins/platforms/xcb/qxcbscreen.h +++ b/src/plugins/platforms/xcb/qxcbscreen.h @@ -140,6 +140,11 @@ public: QXcbXSettings *xSettings() const; + QPoint mapToNative(const QPoint &pos) const; + QPoint mapFromNative(const QPoint &pos) const; + QRect mapToNative(const QRect &rect) const; + QRect mapFromNative(const QRect &rect) const; + private: static bool xResource(const QByteArray &identifier, const QByteArray &expectedIdentifier, diff --git a/src/plugins/platforms/xcb/qxcbwindow.cpp b/src/plugins/platforms/xcb/qxcbwindow.cpp index b06b41a77b..b5d0741fa6 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.cpp +++ b/src/plugins/platforms/xcb/qxcbwindow.cpp @@ -138,7 +138,7 @@ enum QX11EmbedMessageType { const quint32 XEMBED_VERSION = 0; -static inline QRect mapToNative(const QRect &qtRect, int dpr) +static inline QRect mapLocalGeometryToNative(const QRect &qtRect, int dpr) { return QRect(qtRect.x() * dpr, qtRect.y() * dpr, qtRect.width() * dpr, qtRect.height() * dpr); } @@ -166,11 +166,45 @@ static inline QRect mapExposeFromNative(const QRect &xRect, int dpr) return QRect(dpr_floor(xRect.topLeft(), dpr), dpr_ceil(xRect.bottomRight(), dpr)); } -static inline QRect mapGeometryFromNative(const QRect &xRect, int dpr) +static inline QRect mapLocalGeometryFromNative(const QRect &xRect, int dpr) { return QRect(xRect.topLeft() / dpr, dpr_ceil(xRect.size(), dpr)); } +QXcbScreen *QXcbWindow::parentScreen() +{ + return parent() ? static_cast(parent())->parentScreen() : m_xcbScreen; +} + +QPoint QXcbWindow::mapToNative(const QPoint &pos, const QXcbScreen *screen) const +{ + if (parent()) + return pos * int(screen->devicePixelRatio()); + else + return screen->mapToNative(pos); +} +QPoint QXcbWindow::mapFromNative(const QPoint &pos, const QXcbScreen *screen) const +{ + if (parent()) + return pos / int(screen->devicePixelRatio()); + else + return screen->mapFromNative(pos); +} +QRect QXcbWindow::mapToNative(const QRect &rect, const QXcbScreen *screen) const +{ + if (parent()) + return mapLocalGeometryToNative(rect, int(screen->devicePixelRatio())); + else + return screen->mapToNative(rect); +} +QRect QXcbWindow::mapFromNative(const QRect &rect, const QXcbScreen *screen) const +{ + if (parent()) + return mapLocalGeometryFromNative(rect, int(screen->devicePixelRatio())); + else + return screen->mapFromNative(rect); +} + // Returns \c true if we should set WM_TRANSIENT_FOR on \a w static inline bool isTransient(const QWindow *w) { @@ -291,6 +325,7 @@ static const char *wm_window_type_property_id = "_q_xcb_wm_window_type"; QXcbWindow::QXcbWindow(QWindow *window) : QPlatformWindow(window) , m_window(0) + , m_xcbScreen(0) , m_syncCounter(0) , m_gravity(XCB_GRAVITY_STATIC) , m_mapped(false) @@ -344,8 +379,11 @@ void QXcbWindow::create() Qt::WindowType type = window()->type(); - QXcbScreen *platformScreen = xcbScreen(); + QXcbScreen *currentScreen = xcbScreen(); + QRect rect = window()->geometry(); + QXcbScreen *platformScreen = parent() ? parentScreen() : static_cast(screenForGeometry(rect)); + m_xcbScreen = platformScreen; if (type == Qt::Desktop) { m_window = platformScreen->root(); m_depth = platformScreen->screen()->root_depth; @@ -371,13 +409,10 @@ void QXcbWindow::create() // Parameters to XCreateWindow() are frame corner + inner size. // This fits in case position policy is frame inclusive. There is // currently no way to implement it for frame-exclusive geometries. - QRect rect = window()->geometry(); QPlatformWindow::setGeometry(rect); - QXcbScreen *currentScreen = xcbScreen(); - QPlatformScreen *newScreen = screenForGeometry(rect); - if (newScreen != currentScreen) - QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->screen()); + if (platformScreen != currentScreen) + QWindowSystemInterface::handleWindowScreenChanged(window(), platformScreen->QPlatformScreen::screen()); const int dpr = int(devicePixelRatio()); @@ -428,7 +463,7 @@ void QXcbWindow::create() m_visualId = visualInfo->visualid; - const QRect xRect = mapToNative(rect, dpr); + const QRect xRect = mapToNative(rect, platformScreen); m_window = XCreateWindow(DISPLAY_FROM_XCB(this), xcb_parent_id, xRect.x(), xRect.y(), xRect.width(), xRect.height(), 0, visualInfo->depth, InputOutput, visualInfo->visual, @@ -472,15 +507,16 @@ void QXcbWindow::create() const xcb_visualtype_t *visual = platformScreen->visualForId(m_visualId); m_imageFormat = imageFormatForVisual(m_depth, visual->red_mask, visual->blue_mask, &m_imageRgbSwap); + const QRect xRect = mapToNative(rect, platformScreen); Q_XCB_CALL(xcb_create_window(xcb_connection(), m_depth, m_window, // window id xcb_parent_id, // parent window id - rect.x(), - rect.y(), - rect.width(), - rect.height(), + xRect.x(), + xRect.y(), + xRect.width(), + xRect.height(), 0, // border width XCB_WINDOW_CLASS_INPUT_OUTPUT, // window class m_visualId, // visual @@ -631,7 +667,7 @@ void QXcbWindow::destroy() void QXcbWindow::maybeSetScreen(QXcbScreen *screen) { - if (!window()->screen() && screen->geometry().contains(geometry().topLeft() * int(devicePixelRatio()))) { + if (!window()->screen() && screen->geometry().contains(geometry().topLeft())) { QWindowSystemInterface::handleWindowScreenChanged(window(), static_cast(screen)->screen()); QWindowSystemInterface::handleExposeEvent(window(), QRegion(QRect(QPoint(0, 0), window()->size()))); } @@ -644,16 +680,17 @@ void QXcbWindow::setGeometry(const QRect &rect) propagateSizeHints(); QXcbScreen *currentScreen = xcbScreen(); - QPlatformScreen *newScreen = screenForGeometry(rect); + QXcbScreen *newScreen = parent() ? parentScreen() : static_cast(screenForGeometry(rect)); + if (!newScreen) newScreen = currentScreen; - const QRect xRect = mapToNative(rect, int(newScreen->devicePixelRatio())); + m_xcbScreen = newScreen; + const QRect xRect = mapToNative(rect, newScreen); const QRect wmGeometry = windowToWmGeometry(xRect); - if (newScreen != currentScreen) - QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->screen()); + QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->QPlatformScreen::screen()); if (qt_window_private(window())->positionAutomatic) { const quint32 mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; @@ -1160,6 +1197,8 @@ void QXcbWindow::changeNetWmState(bool set, xcb_atom_t one, xcb_atom_t two) event.data.data32[3] = 0; event.data.data32[4] = 0; + if (!xcbScreen()) + return; Q_XCB_CALL(xcb_send_event(xcb_connection(), 0, xcbScreen()->root(), XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *)&event)); } @@ -1554,7 +1593,7 @@ void QXcbWindow::propagateSizeHints() memset(&hints, 0, sizeof(hints)); const int dpr = int(devicePixelRatio()); - const QRect xRect = mapToNative(windowToWmGeometry(geometry()), dpr); + const QRect xRect = windowToWmGeometry(mapToNative(geometry(), xcbScreen())); QWindow *win = window(); @@ -1892,20 +1931,21 @@ void QXcbWindow::handleClientMessageEvent(const xcb_client_message_event_t *even } } -// Temporary workaround for bug in QPlatformScreen::screenForNativeGeometry +// Temporary workaround for bug in QPlatformScreen::screenForGeometry // we need the native geometries to detect our screen, but that's not // available in cross-platform code. Will be fixed properly when highDPI // support is refactored to expose the native coordinate system. -QPlatformScreen *QXcbWindow::screenForNativeGeometry(const QRect &newGeometry) const +QXcbScreen *QXcbWindow::screenForNativeGeometry(const QRect &newGeometry) const { - QXcbScreen *currentScreen = static_cast(screen()); + QXcbScreen *currentScreen = xcbScreen(); if (!currentScreen && QGuiApplication::primaryScreen()) currentScreen = static_cast(QGuiApplication::primaryScreen()->handle()); if (currentScreen && !parent() && !currentScreen->nativeGeometry().intersects(newGeometry)) { Q_FOREACH (QPlatformScreen* screen, currentScreen->virtualSiblings()) { - if (static_cast(screen)->nativeGeometry().intersects(newGeometry)) - return screen; + QXcbScreen *xcbScreen = static_cast(screen); + if (xcbScreen->nativeGeometry().intersects(newGeometry)) + return xcbScreen; } } return currentScreen; @@ -1927,23 +1967,18 @@ void QXcbWindow::handleConfigureNotifyEvent(const xcb_configure_notify_event_t * } } - const int dpr = devicePixelRatio(); const QRect nativeRect = QRect(pos, QSize(event->width, event->height)); - const QRect rect = mapGeometryFromNative(nativeRect, dpr); + QXcbScreen *newScreen = parent() ? parentScreen() : screenForNativeGeometry(nativeRect); + + m_xcbScreen = newScreen; + if (!newScreen) + return; + const QRect rect = mapFromNative(nativeRect, newScreen); QPlatformWindow::setGeometry(rect); QWindowSystemInterface::handleGeometryChange(window(), rect); - QPlatformScreen *newScreen = screenForNativeGeometry(nativeRect); - if (newScreen != screen()) { - if (newScreen) - QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->screen()); - int newDpr = newScreen->devicePixelRatio(); - if (newDpr != dpr) { - QRect newRect = mapGeometryFromNative(nativeRect, newDpr); - QPlatformWindow::setGeometry(newRect); - QWindowSystemInterface::handleGeometryChange(window(), newRect); - } - } + if (newScreen != screen()) + QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->QPlatformScreen::screen()); m_configureNotifyPending = false; @@ -1984,12 +2019,12 @@ QPoint QXcbWindow::mapToGlobal(const QPoint &pos) const xcb_translate_coordinates_reply_t *reply = xcb_translate_coordinates_reply(xcb_connection(), cookie, NULL); if (reply) { - ret.setX(reply->dst_x / dpr); - ret.setY(reply->dst_y / dpr); + ret.setX(reply->dst_x); + ret.setY(reply->dst_y); free(reply); } - return ret; + return mapFromNative(ret, xcbScreen()); } QPoint QXcbWindow::mapFromGlobal(const QPoint &pos) const @@ -1999,9 +2034,10 @@ QPoint QXcbWindow::mapFromGlobal(const QPoint &pos) const const int dpr = int(devicePixelRatio()); QPoint ret; + QPoint xPos = mapToNative(pos, xcbScreen()); xcb_translate_coordinates_cookie_t cookie = xcb_translate_coordinates(xcb_connection(), xcbScreen()->root(), xcb_window(), - pos.x() *dpr, pos.y() * dpr); + xPos.x(), xPos.y()); xcb_translate_coordinates_reply_t *reply = xcb_translate_coordinates_reply(xcb_connection(), cookie, NULL); if (reply) { @@ -2055,7 +2091,7 @@ void QXcbWindow::handleButtonPressEvent(const xcb_button_press_event_t *event) } const int dpr = int(devicePixelRatio()); QPoint local(event->event_x/dpr, event->event_y/dpr); - QPoint global(event->root_x/dpr, event->root_y/dpr); + QPoint global = xcbScreen()->mapFromNative(QPoint(event->root_x, event->root_y)); Qt::KeyboardModifiers modifiers = connection()->keyboard()->translateModifiers(event->state); @@ -2080,7 +2116,7 @@ void QXcbWindow::handleButtonReleaseEvent(const xcb_button_release_event_t *even { const int dpr = int(devicePixelRatio()); QPoint local(event->event_x/dpr, event->event_y/dpr); - QPoint global(event->root_x/dpr, event->root_y/dpr); + QPoint global = xcbScreen()->mapFromNative(QPoint(event->root_x, event->root_y)); Qt::KeyboardModifiers modifiers = connection()->keyboard()->translateModifiers(event->state); if (event->detail >= 4 && event->detail <= 7) { @@ -2095,7 +2131,9 @@ void QXcbWindow::handleMotionNotifyEvent(const xcb_motion_notify_event_t *event) { const int dpr = int(devicePixelRatio()); QPoint local(event->event_x/dpr, event->event_y/dpr); - QPoint global(event->root_x/dpr, event->root_y/dpr); + if (!xcbScreen()) + return; + QPoint global = xcbScreen()->mapFromNative(QPoint(event->root_x, event->root_y)); Qt::KeyboardModifiers modifiers = connection()->keyboard()->translateModifiers(event->state); handleMouseEvent(event->time, local, global, modifiers); @@ -2152,7 +2190,9 @@ void QXcbWindow::handleEnterNotifyEvent(const xcb_enter_notify_event_t *event) const int dpr = int(devicePixelRatio()); const QPoint local(event->event_x/dpr, event->event_y/dpr); - const QPoint global(event->root_x/dpr, event->root_y/dpr); + if (!xcbScreen()) + return; + QPoint global = xcbScreen()->mapFromNative(QPoint(event->root_x, event->root_y)); QWindowSystemInterface::handleEnterEvent(window(), local, global); } @@ -2170,7 +2210,9 @@ void QXcbWindow::handleLeaveNotifyEvent(const xcb_leave_notify_event_t *event) if (enterWindow) { const int dpr = int(devicePixelRatio()); QPoint local(enter->event_x/dpr, enter->event_y/dpr); - QPoint global(enter->root_x/dpr, enter->root_y/dpr); + if (!xcbScreen()) + return; + QPoint global = xcbScreen()->mapFromNative(QPoint(event->root_x, event->root_y)); QWindowSystemInterface::handleEnterLeaveEvent(enterWindow->window(), window(), local, global); } else { @@ -2185,6 +2227,8 @@ void QXcbWindow::handlePropertyNotifyEvent(const xcb_property_notify_event_t *ev connection()->setTime(event->time); const bool propertyDeleted = event->state == XCB_PROPERTY_DELETE; + if (!xcbScreen()) + return; if (event->atom == atom(QXcbAtom::_NET_WM_STATE) || event->atom == atom(QXcbAtom::WM_STATE)) { if (propertyDeleted) @@ -2333,7 +2377,6 @@ void QXcbWindow::windowEvent(QEvent *event) bool QXcbWindow::startSystemResize(const QPoint &pos, Qt::Corner corner) { - const int dpr = int(devicePixelRatio()); const xcb_atom_t moveResize = connection()->atom(QXcbAtom::_NET_WM_MOVERESIZE); if (!connection()->wmSupport()->isSupportedByWM(moveResize)) return false; @@ -2342,7 +2385,7 @@ bool QXcbWindow::startSystemResize(const QPoint &pos, Qt::Corner corner) xev.type = moveResize; xev.window = xcb_window(); xev.format = 32; - const QPoint globalPos = window()->mapToGlobal(pos) * dpr; + const QPoint globalPos = mapToNative(window()->mapToGlobal(pos), xcbScreen()); xev.data.data32[0] = globalPos.x(); xev.data.data32[1] = globalPos.y(); const bool bottom = corner == Qt::BottomRightCorner || corner == Qt::BottomLeftCorner; @@ -2471,7 +2514,7 @@ void QXcbWindow::setMask(const QRegion ®ion) const int dpr = devicePixelRatio(); QVector rects; foreach (const QRect &r, region.rects()) - rects.push_back(qRectToXCBRectangle(mapToNative(r, dpr))); + rects.push_back(qRectToXCBRectangle(mapLocalGeometryToNative(r, dpr))); xcb_shape_rectangles(connection()->xcb_connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_CLIP_ORDERING_UNSORTED, xcb_window(), 0, 0, rects.size(), &rects[0]); @@ -2495,6 +2538,8 @@ bool QXcbWindow::needsSync() const void QXcbWindow::postSyncWindowRequest() { + if (!xcbScreen()) + return; if (!m_pendingSyncRequest) { QXcbSyncWindowRequest *e = new QXcbSyncWindowRequest(this); m_pendingSyncRequest = e; diff --git a/src/plugins/platforms/xcb/qxcbwindow.h b/src/plugins/platforms/xcb/qxcbwindow.h index c08408a1ca..fbf3cfe172 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.h +++ b/src/plugins/platforms/xcb/qxcbwindow.h @@ -155,7 +155,7 @@ public: virtual void create(); virtual void destroy(); void maybeSetScreen(QXcbScreen *screen); - QPlatformScreen *screenForNativeGeometry(const QRect &newGeometry) const; + QXcbScreen *screenForNativeGeometry(const QRect &newGeometry) const; public Q_SLOTS: void updateSyncRequestCounter(); @@ -165,6 +165,12 @@ protected: virtual void *createVisual() { return Q_NULLPTR; } virtual bool supportsSyncProtocol() { return !window()->supportsOpenGL(); } + QPoint mapToNative(const QPoint &pos, const QXcbScreen *screen) const; + QPoint mapFromNative(const QPoint &pos, const QXcbScreen *screen) const; + QRect mapToNative(const QRect &rect, const QXcbScreen *screen) const; + QRect mapFromNative(const QRect &rect, const QXcbScreen *screen) const; + QXcbScreen *parentScreen(); + void changeNetWmState(bool set, xcb_atom_t one, xcb_atom_t two = 0); NetWmStates netWmStates(); void setNetWmStates(NetWmStates); @@ -192,6 +198,8 @@ protected: xcb_window_t m_window; + QXcbScreen *m_xcbScreen; + uint m_depth; QImage::Format m_imageFormat; bool m_imageRgbSwap;