From e9121328866efa6ba3eb78a991fef785338fd55e Mon Sep 17 00:00:00 2001
From: Alexander Volkov <a.volkov@rusbitech.ru>
Date: Mon, 13 Apr 2015 14:13:34 +0300
Subject: [PATCH] xcb: Use XShape for DnD when a compositing manager is not
 running

Otherwise transparent areas of the drag'n'drop pixmap are painted
with the black color.

Task-number: QTBUG-45193
Change-Id: I55b7c7caababe13584fa1c7a52835f112e20f920
Reviewed-by: Lars Knoll <lars.knoll@theqtcompany.com>
---
 src/gui/kernel/qshapedpixmapdndwindow.cpp    | 13 ++++++--
 src/gui/kernel/qshapedpixmapdndwindow_p.h    |  2 ++
 src/gui/kernel/qsimpledrag.cpp               |  3 +-
 src/gui/kernel/qsimpledrag_p.h               |  4 +++
 src/plugins/platforms/xcb/qxcbclipboard.cpp  | 24 ++-------------
 src/plugins/platforms/xcb/qxcbconnection.cpp | 45 ++++++++++++++++++++++++++--
 src/plugins/platforms/xcb/qxcbconnection.h   |  4 +++
 src/plugins/platforms/xcb/qxcbdrag.cpp       |  3 ++
 src/plugins/platforms/xcb/qxcbscreen.cpp     | 28 +++++++++++++++++
 src/plugins/platforms/xcb/qxcbscreen.h       |  8 +++++
 10 files changed, 106 insertions(+), 28 deletions(-)

diff --git a/src/gui/kernel/qshapedpixmapdndwindow.cpp b/src/gui/kernel/qshapedpixmapdndwindow.cpp
index 5736c41..d77b6dc 100644
--- a/src/gui/kernel/qshapedpixmapdndwindow.cpp
+++ b/src/gui/kernel/qshapedpixmapdndwindow.cpp
@@ -35,12 +35,16 @@
 
 #include <QtGui/QPainter>
 #include <QtGui/QCursor>
+#include <QtGui/QGuiApplication>
+#include <QtGui/QPalette>
+#include <QtGui/QBitmap>
 
 QT_BEGIN_NAMESPACE
 
 QShapedPixmapWindow::QShapedPixmapWindow(QScreen *screen)
     : QWindow(screen),
-      m_backingStore(0)
+      m_backingStore(0),
+      m_useCompositing(true)
 {
     QSurfaceFormat format;
     format.setAlphaBufferSize(8);
@@ -68,7 +72,10 @@ void QShapedPixmapWindow::render()
 
     {
         QPainter p(device);
-        p.setCompositionMode(QPainter::CompositionMode_Source);
+        if (m_useCompositing)
+            p.setCompositionMode(QPainter::CompositionMode_Source);
+        else
+            p.fillRect(rect, QGuiApplication::palette().base());
         p.drawPixmap(0, 0, m_pixmap);
     }
 
@@ -79,6 +86,8 @@ void QShapedPixmapWindow::render()
 void QShapedPixmapWindow::setPixmap(const QPixmap &pixmap)
 {
     m_pixmap = pixmap;
+    if (!m_useCompositing)
+        setMask(m_pixmap.mask());
 }
 
 void QShapedPixmapWindow::setHotspot(const QPoint &hotspot)
diff --git a/src/gui/kernel/qshapedpixmapdndwindow_p.h b/src/gui/kernel/qshapedpixmapdndwindow_p.h
index 7536c09..3d7974f 100644
--- a/src/gui/kernel/qshapedpixmapdndwindow_p.h
+++ b/src/gui/kernel/qshapedpixmapdndwindow_p.h
@@ -60,6 +60,7 @@ public:
 
     void render();
 
+    void setUseCompositing(bool on) { m_useCompositing = on; }
     void setPixmap(const QPixmap &pixmap);
     void setHotspot(const QPoint &hotspot);
 
@@ -72,6 +73,7 @@ private:
     QBackingStore *m_backingStore;
     QPixmap m_pixmap;
     QPoint m_hotSpot;
+    bool m_useCompositing;
 };
 
 QT_END_NAMESPACE
diff --git a/src/gui/kernel/qsimpledrag.cpp b/src/gui/kernel/qsimpledrag.cpp
index 6acac4c..9f38c9b 100644
--- a/src/gui/kernel/qsimpledrag.cpp
+++ b/src/gui/kernel/qsimpledrag.cpp
@@ -88,7 +88,7 @@ static QWindow* topLevelAt(const QPoint &pos)
 QBasicDrag::QBasicDrag() :
     m_restoreCursor(false), m_eventLoop(0),
     m_executed_drop_action(Qt::IgnoreAction), m_can_drop(false),
-    m_drag(0), m_drag_icon_window(0)
+    m_drag(0), m_drag_icon_window(0), m_useCompositing(true)
 {
 }
 
@@ -226,6 +226,7 @@ void QBasicDrag::recreateShapedPixmapWindow(QScreen *screen, const QPoint &pos)
     // when QDrag is used without a pixmap - QDrag::setPixmap()
     m_drag_icon_window = new QShapedPixmapWindow(screen);
 
+    m_drag_icon_window->setUseCompositing(m_useCompositing);
     m_drag_icon_window->setPixmap(m_drag->pixmap());
     m_drag_icon_window->setHotspot(m_drag->hotSpot());
     m_drag_icon_window->updateGeometry(pos);
diff --git a/src/gui/kernel/qsimpledrag_p.h b/src/gui/kernel/qsimpledrag_p.h
index 4c9edba..055136c 100644
--- a/src/gui/kernel/qsimpledrag_p.h
+++ b/src/gui/kernel/qsimpledrag_p.h
@@ -87,6 +87,9 @@ protected:
     bool canDrop() const { return m_can_drop; }
     void setCanDrop(bool c) { m_can_drop = c; }
 
+    bool useCompositing() const { return m_useCompositing; }
+    void setUseCompositing(bool on) { m_useCompositing = on; }
+
     Qt::DropAction executedDropAction() const { return m_executed_drop_action; }
     void  setExecutedDropAction(Qt::DropAction da) { m_executed_drop_action = da; }
 
@@ -104,6 +107,7 @@ private:
     bool m_can_drop;
     QDrag *m_drag;
     QShapedPixmapWindow *m_drag_icon_window;
+    bool m_useCompositing;
 };
 
 class Q_GUI_EXPORT QSimpleDrag : public QBasicDrag
diff --git a/src/plugins/platforms/xcb/qxcbclipboard.cpp b/src/plugins/platforms/xcb/qxcbclipboard.cpp
index 248d1b4..8b75c13 100644
--- a/src/plugins/platforms/xcb/qxcbclipboard.cpp
+++ b/src/plugins/platforms/xcb/qxcbclipboard.cpp
@@ -275,22 +275,8 @@ QXcbClipboard::QXcbClipboard(QXcbConnection *c)
     m_clientClipboard[QClipboard::Selection] = 0;
     m_timestamp[QClipboard::Clipboard] = XCB_CURRENT_TIME;
     m_timestamp[QClipboard::Selection] = XCB_CURRENT_TIME;
+    m_owner = connection()->getQtSelectionOwner();
 
-    QXcbScreen *platformScreen = screen();
-
-    int x = 0, y = 0, w = 3, h = 3;
-
-    m_owner = xcb_generate_id(xcb_connection());
-    Q_XCB_CALL(xcb_create_window(xcb_connection(),
-                                 XCB_COPY_FROM_PARENT,            // depth -- same as root
-                                 m_owner,                        // window id
-                                 platformScreen->screen()->root,                   // parent window id
-                                 x, y, w, h,
-                                 0,                               // border width
-                                 XCB_WINDOW_CLASS_INPUT_OUTPUT,   // window class
-                                 platformScreen->screen()->root_visual, // visual
-                                 0,                               // value mask
-                                 0));                             // value list
 #ifndef QT_NO_DEBUG
     QByteArray ba("Qt clipboard window");
     Q_XCB_CALL(xcb_change_property(xcb_connection(),
@@ -353,13 +339,7 @@ void QXcbClipboard::incrTransactionPeeker(xcb_generic_event_t *ge, bool &accepte
 
 xcb_window_t QXcbClipboard::getSelectionOwner(xcb_atom_t atom) const
 {
-    xcb_connection_t *c = xcb_connection();
-    xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(c, atom);
-    xcb_get_selection_owner_reply_t *reply;
-    reply = xcb_get_selection_owner_reply(c, cookie, 0);
-    xcb_window_t win = reply->owner;
-    free(reply);
-    return win;
+    return connection()->getSelectionOwner(atom);
 }
 
 xcb_atom_t QXcbClipboard::atomForMode(QClipboard::Mode mode) const
diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp
index 13d73c7..a20d957 100644
--- a/src/plugins/platforms/xcb/qxcbconnection.cpp
+++ b/src/plugins/platforms/xcb/qxcbconnection.cpp
@@ -449,6 +449,9 @@ void QXcbConnection::initializeScreens()
         ++xcbScreenNumber;
     } // for each xcb screen
 
+    foreach (QXcbVirtualDesktop *virtualDesktop, m_virtualDesktops)
+        virtualDesktop->subscribeToXFixesSelectionNotify();
+
     // If there's no randr extension, or there was some error above, or we found a
     // screen which doesn't have outputs for some other reason (e.g. on VNC or ssh -X),
     // but the dimensions are known anyway, and we don't already have any lingering
@@ -507,6 +510,7 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGra
     , m_systemTrayTracker(0)
     , m_glIntegration(Q_NULLPTR)
     , m_xiGrab(false)
+    , m_qtSelectionOwner(0)
 {
 #ifdef XCB_USE_XLIB
     Display *dpy = XOpenDisplay(m_displayName.constData());
@@ -551,12 +555,12 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGra
     m_netWmUserTime = XCB_CURRENT_TIME;
 
     initializeXRandr();
+    initializeXFixes();
     initializeScreens();
 
     if (m_screens.isEmpty())
         qFatal("QXcbConnection: no screens available");
 
-    initializeXFixes();
     initializeXRender();
     m_xi2Enabled = false;
 #if defined(XCB_USE_XINPUT2)
@@ -1139,10 +1143,14 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
 
     if (!handled) {
         if (response_type == xfixes_first_event + XCB_XFIXES_SELECTION_NOTIFY) {
-            setTime(((xcb_xfixes_selection_notify_event_t *)event)->timestamp);
+            xcb_xfixes_selection_notify_event_t *notify_event = (xcb_xfixes_selection_notify_event_t *)event;
+            setTime(notify_event->timestamp);
 #ifndef QT_NO_CLIPBOARD
-            m_clipboard->handleXFixesSelectionRequest((xcb_xfixes_selection_notify_event_t *)event);
+            m_clipboard->handleXFixesSelectionRequest(notify_event);
 #endif
+            foreach (QXcbVirtualDesktop *virtualDesktop, m_virtualDesktops)
+                virtualDesktop->handleXFixesSelectionNotify(notify_event);
+
             handled = true;
         } else if (has_randr_extension && response_type == xrandr_first_event + XCB_RANDR_NOTIFY) {
             updateScreens((xcb_randr_notify_event_t *)event);
@@ -1371,6 +1379,37 @@ xcb_timestamp_t QXcbConnection::getTimestamp()
     return timestamp;
 }
 
+xcb_window_t QXcbConnection::getSelectionOwner(xcb_atom_t atom) const
+{
+    xcb_connection_t *c = xcb_connection();
+    xcb_get_selection_owner_cookie_t cookie = xcb_get_selection_owner(c, atom);
+    xcb_get_selection_owner_reply_t *reply;
+    reply = xcb_get_selection_owner_reply(c, cookie, 0);
+    xcb_window_t win = reply->owner;
+    free(reply);
+    return win;
+}
+
+xcb_window_t QXcbConnection::getQtSelectionOwner()
+{
+    if (!m_qtSelectionOwner) {
+        xcb_screen_t *xcbScreen = primaryVirtualDesktop()->screen();
+        int x = 0, y = 0, w = 3, h = 3;
+        m_qtSelectionOwner = xcb_generate_id(xcb_connection());
+        Q_XCB_CALL(xcb_create_window(xcb_connection(),
+                                     XCB_COPY_FROM_PARENT,               // depth -- same as root
+                                     m_qtSelectionOwner,                 // window id
+                                     xcbScreen->root,                    // parent window id
+                                     x, y, w, h,
+                                     0,                                  // border width
+                                     XCB_WINDOW_CLASS_INPUT_OUTPUT,      // window class
+                                     xcbScreen->root_visual,             // visual
+                                     0,                                  // value mask
+                                     0));                                // value list
+    }
+    return m_qtSelectionOwner;
+}
+
 xcb_window_t QXcbConnection::rootWindow()
 {
     QXcbScreen *s = primaryScreen();
diff --git a/src/plugins/platforms/xcb/qxcbconnection.h b/src/plugins/platforms/xcb/qxcbconnection.h
index fb5b941..3c82170 100644
--- a/src/plugins/platforms/xcb/qxcbconnection.h
+++ b/src/plugins/platforms/xcb/qxcbconnection.h
@@ -460,6 +460,8 @@ public:
     bool threadedEventHandling() const { return m_reader->isRunning(); }
 
     xcb_timestamp_t getTimestamp();
+    xcb_window_t getSelectionOwner(xcb_atom_t atom) const;
+    xcb_window_t getQtSelectionOwner();
 
     void setButton(Qt::MouseButton button, bool down) { if (down) m_buttons |= button; else m_buttons &= ~button; }
     Qt::MouseButtons buttons() const { return m_buttons; }
@@ -650,6 +652,8 @@ private:
     QXcbGlIntegration *m_glIntegration;
     bool m_xiGrab;
 
+    xcb_window_t m_qtSelectionOwner;
+
     friend class QXcbEventReader;
 };
 
diff --git a/src/plugins/platforms/xcb/qxcbdrag.cpp b/src/plugins/platforms/xcb/qxcbdrag.cpp
index f9c3aa7..d19ea24 100644
--- a/src/plugins/platforms/xcb/qxcbdrag.cpp
+++ b/src/plugins/platforms/xcb/qxcbdrag.cpp
@@ -191,6 +191,8 @@ void QXcbDrag::startDrag()
         xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->clipboard()->owner(),
                             atom(QXcbAtom::XdndTypelist),
                             XCB_ATOM_ATOM, 32, drag_types.size(), (const void *)drag_types.constData());
+
+    setUseCompositing(current_virtual_desktop->compositingActive());
     QBasicDrag::startDrag();
 }
 
@@ -316,6 +318,7 @@ void QXcbDrag::move(const QPoint &globalPos)
     QPoint deviceIndependentPos = QHighDpiScaling::mapPositionFromNative(globalPos, screen);
 
     if (virtualDesktop != current_virtual_desktop) {
+        setUseCompositing(virtualDesktop->compositingActive());
         recreateShapedPixmapWindow(static_cast<QPlatformScreen*>(screen)->screen(), deviceIndependentPos);
         current_virtual_desktop = virtualDesktop;
     } else {
diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp
index 64645b9..0aa5810 100644
--- a/src/plugins/platforms/xcb/qxcbscreen.cpp
+++ b/src/plugins/platforms/xcb/qxcbscreen.cpp
@@ -54,6 +54,10 @@ QXcbVirtualDesktop::QXcbVirtualDesktop(QXcbConnection *connection, xcb_screen_t
     , m_number(number)
     , m_xSettings(Q_NULLPTR)
 {
+    QByteArray cmAtomName("_NET_WM_CM_S");
+    cmAtomName += QByteArray::number(m_number);
+    m_net_wm_cm_atom = connection->internAtom(cmAtomName.constData());
+    m_compositingActive = connection->getSelectionOwner(m_net_wm_cm_atom);
 }
 
 QXcbVirtualDesktop::~QXcbVirtualDesktop()
@@ -79,6 +83,30 @@ QXcbXSettings *QXcbVirtualDesktop::xSettings() const
     return m_xSettings;
 }
 
+bool QXcbVirtualDesktop::compositingActive() const
+{
+    if (connection()->hasXFixes())
+        return m_compositingActive;
+    else
+        return connection()->getSelectionOwner(m_net_wm_cm_atom);
+}
+
+void QXcbVirtualDesktop::handleXFixesSelectionNotify(xcb_xfixes_selection_notify_event_t *notify_event)
+{
+    if (notify_event->selection == m_net_wm_cm_atom)
+        m_compositingActive = notify_event->owner;
+}
+
+void QXcbVirtualDesktop::subscribeToXFixesSelectionNotify()
+{
+    if (connection()->hasXFixes()) {
+        const uint32_t mask = XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER |
+                              XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY |
+                              XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE;
+        Q_XCB_CALL(xcb_xfixes_select_selection_input_checked(xcb_connection(), connection()->getQtSelectionOwner(), m_net_wm_cm_atom, mask));
+    }
+}
+
 QXcbScreen::QXcbScreen(QXcbConnection *connection, QXcbVirtualDesktop *virtualDesktop,
                        xcb_randr_output_t outputId, xcb_randr_get_output_info_reply_t *output,
                        QString outputName)
diff --git a/src/plugins/platforms/xcb/qxcbscreen.h b/src/plugins/platforms/xcb/qxcbscreen.h
index d8d6360..51c92a4 100644
--- a/src/plugins/platforms/xcb/qxcbscreen.h
+++ b/src/plugins/platforms/xcb/qxcbscreen.h
@@ -39,6 +39,7 @@
 
 #include <xcb/xcb.h>
 #include <xcb/randr.h>
+#include <xcb/xfixes.h>
 
 #include "qxcbobject.h"
 #include "qxcbscreen.h"
@@ -69,11 +70,18 @@ public:
 
     QXcbXSettings *xSettings() const;
 
+    bool compositingActive() const;
+
+    void handleXFixesSelectionNotify(xcb_xfixes_selection_notify_event_t *notify_event);
+    void subscribeToXFixesSelectionNotify();
+
 private:
     xcb_screen_t *m_screen;
     int m_number;
 
     QXcbXSettings *m_xSettings;
+    xcb_atom_t m_net_wm_cm_atom;
+    bool m_compositingActive;
 };
 
 class Q_XCB_EXPORT QXcbScreen : public QXcbObject, public QPlatformScreen
-- 
2.6.3

