From c55a36cb9015cf1eebd49eaa5b1b4f4ec9b28451 Mon Sep 17 00:00:00 2001
From: Alexander Volkov <a.volkov@rusbitech.ru>
Date: Wed, 7 Oct 2015 16:58:09 +0300
Subject: [PATCH] xcb: Fix DnD for separate X screens

Recreate QShapedPixmapWindow when the cursor goes to
another X screen.

Change-Id: Ifd4c4281971b23abc45a9f6c0509832a45c31521
Reviewed-by: Lars Knoll <lars.knoll@theqtcompany.com>
---
 src/gui/kernel/qshapedpixmapdndwindow.cpp  |  4 +--
 src/gui/kernel/qshapedpixmapdndwindow_p.h  |  2 +-
 src/gui/kernel/qsimpledrag.cpp             | 29 ++++++++++---------
 src/gui/kernel/qsimpledrag_p.h             |  2 ++
 src/plugins/platforms/xcb/qxcbconnection.h |  2 ++
 src/plugins/platforms/xcb/qxcbcursor.cpp   | 45 ++++++++++++++++--------------
 src/plugins/platforms/xcb/qxcbcursor.h     |  2 +-
 src/plugins/platforms/xcb/qxcbdrag.cpp     | 43 +++++++++-------------------
 src/plugins/platforms/xcb/qxcbdrag.h       |  2 +-
 src/plugins/platforms/xcb/qxcbscreen.cpp   |  9 ++++++
 src/plugins/platforms/xcb/qxcbscreen.h     |  3 ++
 11 files changed, 74 insertions(+), 69 deletions(-)

diff --git a/src/gui/kernel/qshapedpixmapdndwindow.cpp b/src/gui/kernel/qshapedpixmapdndwindow.cpp
index 8f80789..5736c41 100644
--- a/src/gui/kernel/qshapedpixmapdndwindow.cpp
+++ b/src/gui/kernel/qshapedpixmapdndwindow.cpp
@@ -38,8 +38,8 @@
 
 QT_BEGIN_NAMESPACE
 
-QShapedPixmapWindow::QShapedPixmapWindow()
-    : QWindow(),
+QShapedPixmapWindow::QShapedPixmapWindow(QScreen *screen)
+    : QWindow(screen),
       m_backingStore(0)
 {
     QSurfaceFormat format;
diff --git a/src/gui/kernel/qshapedpixmapdndwindow_p.h b/src/gui/kernel/qshapedpixmapdndwindow_p.h
index fc311cf..7536c09 100644
--- a/src/gui/kernel/qshapedpixmapdndwindow_p.h
+++ b/src/gui/kernel/qshapedpixmapdndwindow_p.h
@@ -55,7 +55,7 @@ class QShapedPixmapWindow : public QWindow
 {
     Q_OBJECT
 public:
-    QShapedPixmapWindow();
+    explicit QShapedPixmapWindow(QScreen *screen = 0);
     ~QShapedPixmapWindow();
 
     void render();
diff --git a/src/gui/kernel/qsimpledrag.cpp b/src/gui/kernel/qsimpledrag.cpp
index b02f1dd..6acac4c 100644
--- a/src/gui/kernel/qsimpledrag.cpp
+++ b/src/gui/kernel/qsimpledrag.cpp
@@ -203,25 +203,15 @@ void QBasicDrag::restoreCursor()
 
 void QBasicDrag::startDrag()
 {
-    // ### TODO Check if its really necessary to have m_drag_icon_window
-    // when QDrag is used without a pixmap - QDrag::setPixmap()
-    if (!m_drag_icon_window)
-        m_drag_icon_window = new QShapedPixmapWindow();
-
-    m_drag_icon_window->setPixmap(m_drag->pixmap());
-    m_drag_icon_window->setHotspot(m_drag->hotSpot());
-
+    QPoint pos;
 #ifndef QT_NO_CURSOR
-    QPoint pos = QCursor::pos();
+    pos = QCursor::pos();
     if (pos.x() == int(qInf())) {
         // ### fixme: no mouse pos registered. Get pos from touch...
         pos = QPoint();
     }
-    m_drag_icon_window->updateGeometry(pos);
 #endif
-
-    m_drag_icon_window->setVisible(true);
-
+    recreateShapedPixmapWindow(Q_NULLPTR, pos);
     enableEventFilter();
 }
 
@@ -229,6 +219,19 @@ void QBasicDrag::endDrag()
 {
 }
 
+void QBasicDrag::recreateShapedPixmapWindow(QScreen *screen, const QPoint &pos)
+{
+    delete m_drag_icon_window;
+    // ### TODO Check if its really necessary to have m_drag_icon_window
+    // when QDrag is used without a pixmap - QDrag::setPixmap()
+    m_drag_icon_window = new QShapedPixmapWindow(screen);
+
+    m_drag_icon_window->setPixmap(m_drag->pixmap());
+    m_drag_icon_window->setHotspot(m_drag->hotSpot());
+    m_drag_icon_window->updateGeometry(pos);
+    m_drag_icon_window->setVisible(true);
+}
+
 void QBasicDrag::cancel()
 {
     disableEventFilter();
diff --git a/src/gui/kernel/qsimpledrag_p.h b/src/gui/kernel/qsimpledrag_p.h
index a011475..4c9edba 100644
--- a/src/gui/kernel/qsimpledrag_p.h
+++ b/src/gui/kernel/qsimpledrag_p.h
@@ -58,6 +58,7 @@ class QWindow;
 class QEventLoop;
 class QDropData;
 class QShapedPixmapWindow;
+class QScreen;
 
 class Q_GUI_EXPORT QBasicDrag : public QPlatformDrag, public QObject
 {
@@ -80,6 +81,7 @@ protected:
 
     void moveShapedPixmapWindow(const QPoint &deviceIndependentPosition);
     QShapedPixmapWindow *shapedPixmapWindow() const { return m_drag_icon_window; }
+    void recreateShapedPixmapWindow(QScreen *screen, const QPoint &pos);
     void updateCursor(Qt::DropAction action);
 
     bool canDrop() const { return m_can_drop; }
diff --git a/src/plugins/platforms/xcb/qxcbconnection.h b/src/plugins/platforms/xcb/qxcbconnection.h
index 6ee32bf..fb5b941 100644
--- a/src/plugins/platforms/xcb/qxcbconnection.h
+++ b/src/plugins/platforms/xcb/qxcbconnection.h
@@ -376,8 +376,10 @@ public:
 
     QXcbConnection *connection() const { return const_cast<QXcbConnection *>(this); }
 
+    const QList<QXcbVirtualDesktop *> &virtualDesktops() const { return m_virtualDesktops; }
     const QList<QXcbScreen *> &screens() const { return m_screens; }
     int primaryScreenNumber() const { return m_primaryScreenNumber; }
+    QXcbVirtualDesktop *primaryVirtualDesktop() const { return m_virtualDesktops.value(m_primaryScreenNumber); }
     QXcbScreen *primaryScreen() const;
 
     inline xcb_atom_t atom(QXcbAtom::Atom atom) const { return m_allAtoms[atom]; }
diff --git a/src/plugins/platforms/xcb/qxcbcursor.cpp b/src/plugins/platforms/xcb/qxcbcursor.cpp
index 0cd9159..b321ed9 100644
--- a/src/plugins/platforms/xcb/qxcbcursor.cpp
+++ b/src/plugins/platforms/xcb/qxcbcursor.cpp
@@ -607,30 +607,33 @@ xcb_cursor_t QXcbCursor::createBitmapCursor(QCursor *cursor)
 }
 #endif
 
-void QXcbCursor::queryPointer(QXcbConnection *c, xcb_window_t *rootWin, QPoint *pos, int *keybMask)
+void QXcbCursor::queryPointer(QXcbConnection *c, QXcbVirtualDesktop **virtualDesktop, QPoint *pos, int *keybMask)
 {
     if (pos)
         *pos = QPoint();
-    xcb_screen_iterator_t it = xcb_setup_roots_iterator(c->setup());
-    while (it.rem) {
-        xcb_window_t root = it.data->root;
-        xcb_query_pointer_cookie_t cookie = xcb_query_pointer(c->xcb_connection(), root);
-        xcb_generic_error_t *err = 0;
-        xcb_query_pointer_reply_t *reply = xcb_query_pointer_reply(c->xcb_connection(), cookie, &err);
-        if (!err && reply) {
-            if (pos)
-                *pos = QPoint(reply->root_x, reply->root_y);
-            if (rootWin)
-                *rootWin = root;
-            if (keybMask)
-                *keybMask = reply->mask;
-            free(reply);
-            return;
+
+    xcb_window_t root = c->primaryVirtualDesktop()->root();
+    xcb_query_pointer_cookie_t cookie = xcb_query_pointer(c->xcb_connection(), root);
+    xcb_generic_error_t *err = 0;
+    xcb_query_pointer_reply_t *reply = xcb_query_pointer_reply(c->xcb_connection(), cookie, &err);
+    if (!err && reply) {
+        if (virtualDesktop) {
+            foreach (QXcbVirtualDesktop *vd, c->virtualDesktops()) {
+                if (vd->root() == reply->root) {
+                    *virtualDesktop = vd;
+                    break;
+                }
+            }
         }
-        free(err);
+        if (pos)
+            *pos = QPoint(reply->root_x, reply->root_y);
+        if (keybMask)
+            *keybMask = reply->mask;
         free(reply);
-        xcb_screen_next(&it);
+        return;
     }
+    free(err);
+    free(reply);
 }
 
 QPoint QXcbCursor::pos() const
@@ -642,9 +645,9 @@ QPoint QXcbCursor::pos() const
 
 void QXcbCursor::setPos(const QPoint &pos)
 {
-    xcb_window_t root = 0;
-    queryPointer(connection(), &root, 0);
-    xcb_warp_pointer(xcb_connection(), XCB_NONE, root, 0, 0, 0, 0, pos.x(), pos.y());
+    QXcbVirtualDesktop *virtualDesktop = Q_NULLPTR;
+    queryPointer(connection(), &virtualDesktop, 0);
+    xcb_warp_pointer(xcb_connection(), XCB_NONE, virtualDesktop->root(), 0, 0, 0, 0, pos.x(), pos.y());
     xcb_flush(xcb_connection());
 }
 
diff --git a/src/plugins/platforms/xcb/qxcbcursor.h b/src/plugins/platforms/xcb/qxcbcursor.h
index f4f6e61..3c6dece 100644
--- a/src/plugins/platforms/xcb/qxcbcursor.h
+++ b/src/plugins/platforms/xcb/qxcbcursor.h
@@ -75,7 +75,7 @@ public:
     QPoint pos() const Q_DECL_OVERRIDE;
     void setPos(const QPoint &pos) Q_DECL_OVERRIDE;
 
-    static void queryPointer(QXcbConnection *c, xcb_window_t *rootWin, QPoint *pos, int *keybMask = 0);
+    static void queryPointer(QXcbConnection *c, QXcbVirtualDesktop **virtualDesktop, QPoint *pos, int *keybMask = 0);
 
 private:
 #ifndef QT_NO_CURSOR
diff --git a/src/plugins/platforms/xcb/qxcbdrag.cpp b/src/plugins/platforms/xcb/qxcbdrag.cpp
index de23c59..f9c3aa7 100644
--- a/src/plugins/platforms/xcb/qxcbdrag.cpp
+++ b/src/plugins/platforms/xcb/qxcbdrag.cpp
@@ -39,6 +39,7 @@
 #include "qxcbwindow.h"
 #include "qxcbscreen.h"
 #include "qwindow.h"
+#include "qxcbcursor.h"
 #include <private/qdnd_p.h>
 #include <qdebug.h>
 #include <qevent.h>
@@ -160,7 +161,7 @@ void QXcbDrag::init()
     source_time = XCB_CURRENT_TIME;
     target_time = XCB_CURRENT_TIME;
 
-    current_screen = 0;
+    QXcbCursor::queryPointer(connection(), &current_virtual_desktop, 0);
     drag_types.clear();
 }
 
@@ -308,38 +309,20 @@ void QXcbDrag::move(const QPoint &globalPos)
     if (source_sameanswer.contains(globalPos) && source_sameanswer.isValid())
         return;
 
-    const QList<QXcbScreen *> &screens = connection()->screens();
-    QXcbScreen *screen = connection()->primaryScreen();
-    for (int i = 0; i < screens.size(); ++i) {
-        if (screens.at(i)->geometry().contains(globalPos)) {
-            screen = screens.at(i);
-            break;
-        }
-    }
+    QXcbVirtualDesktop *virtualDesktop = Q_NULLPTR;
+    QPoint cursorPos;
+    QXcbCursor::queryPointer(connection(), &virtualDesktop, &cursorPos);
+    QXcbScreen *screen = virtualDesktop->screenAt(cursorPos);
+    QPoint deviceIndependentPos = QHighDpiScaling::mapPositionFromNative(globalPos, screen);
 
-    QBasicDrag::moveShapedPixmapWindow(QHighDpiScaling::mapPositionFromNative(globalPos, screen));
-
-    if (screen != current_screen) {
-        // ### need to recreate the shaped pixmap window?
-//    int screen = QCursor::x11Screen();
-//    if ((qt_xdnd_current_screen == -1 && screen != X11->defaultScreen) || (screen != qt_xdnd_current_screen)) {
-//        // recreate the pixmap on the new screen...
-//        delete xdnd_data.deco;
-//        QWidget* parent = object->source()->window()->x11Info().screen() == screen
-//            ? object->source()->window() : QApplication::desktop()->screen(screen);
-//        xdnd_data.deco = new QShapedPixmapWidget(parent);
-//        if (!QWidget::mouseGrabber()) {
-//            updatePixmap();
-//            xdnd_data.deco->grabMouse();
-//        }
-//    }
-//    xdnd_data.deco->move(QCursor::pos() - xdnd_data.deco->pm_hot);
-        current_screen = screen;
+    if (virtualDesktop != current_virtual_desktop) {
+        recreateShapedPixmapWindow(static_cast<QPlatformScreen*>(screen)->screen(), deviceIndependentPos);
+        current_virtual_desktop = virtualDesktop;
+    } else {
+        QBasicDrag::moveShapedPixmapWindow(deviceIndependentPos);
     }
 
-
-//    qt_xdnd_current_screen = screen;
-    xcb_window_t rootwin = current_screen->root();
+    xcb_window_t rootwin = current_virtual_desktop->root();
     xcb_translate_coordinates_reply_t *translate =
             ::translateCoordinates(connection(), rootwin, rootwin, globalPos.x(), globalPos.y());
     if (!translate)
diff --git a/src/plugins/platforms/xcb/qxcbdrag.h b/src/plugins/platforms/xcb/qxcbdrag.h
index 9584c04..c9d2579 100644
--- a/src/plugins/platforms/xcb/qxcbdrag.h
+++ b/src/plugins/platforms/xcb/qxcbdrag.h
@@ -133,7 +133,7 @@ private:
     // window to send events to (always valid if current_target)
     xcb_window_t current_proxy_target;
 
-    QXcbScreen *current_screen;
+    QXcbVirtualDesktop *current_virtual_desktop;
 
     // 10 minute timer used to discard old XdndDrop transactions
     enum { XdndDropTransactionTimeout = 600000 };
diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp
index c6e48dc..2e0c55e 100644
--- a/src/plugins/platforms/xcb/qxcbscreen.cpp
+++ b/src/plugins/platforms/xcb/qxcbscreen.cpp
@@ -61,6 +61,15 @@ QXcbVirtualDesktop::~QXcbVirtualDesktop()
     delete m_xSettings;
 }
 
+QXcbScreen *QXcbVirtualDesktop::screenAt(const QPoint &pos) const
+{
+    foreach (QXcbScreen *screen, connection()->screens()) {
+        if (screen->virtualDesktop() == this && screen->nativeGeometry().contains(pos))
+            return screen;
+    }
+    return Q_NULLPTR;
+}
+
 QXcbXSettings *QXcbVirtualDesktop::xSettings() const
 {
     if (!m_xSettings) {
diff --git a/src/plugins/platforms/xcb/qxcbscreen.h b/src/plugins/platforms/xcb/qxcbscreen.h
index cbb6307..d8d6360 100644
--- a/src/plugins/platforms/xcb/qxcbscreen.h
+++ b/src/plugins/platforms/xcb/qxcbscreen.h
@@ -64,6 +64,8 @@ public:
     int number() const { return m_number; }
     QSize size() const { return QSize(m_screen->width_in_pixels, m_screen->height_in_pixels); }
     QSize physicalSize() const { return QSize(m_screen->width_in_millimeters, m_screen->height_in_millimeters); }
+    xcb_window_t root() const { return m_screen->root; }
+    QXcbScreen *screenAt(const QPoint &pos) const;
 
     QXcbXSettings *xSettings() const;
 
@@ -104,6 +106,7 @@ public:
     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); }
+    QXcbVirtualDesktop *virtualDesktop() const { return m_virtualDesktop; }
 
     void setPrimary(bool primary) { m_primary = primary; }
     bool isPrimary() const { return m_primary; }
-- 
2.6.3

