From 9d5774868ae8907ee03e61caa4f265e8d6e58881 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Mon, 25 Jul 2011 13:01:04 +0300 Subject: [PATCH] Drag and drop support in Wayland plug-in. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Change-Id: I5e4448a9b3d3df9e3a12733386079031be340a42 Reviewed-on: http://codereview.qt.nokia.com/2101 Reviewed-by: Qt Sanity Bot Reviewed-by: Samuel Rødal --- src/plugins/platforms/wayland/qwaylanddisplay.cpp | 3 + src/plugins/platforms/wayland/qwaylanddnd.cpp | 423 +++++++++++++++++++++ src/plugins/platforms/wayland/qwaylanddnd.h | 86 +++++ .../platforms/wayland/qwaylandinputdevice.h | 1 + .../platforms/wayland/qwaylandintegration.cpp | 6 + .../platforms/wayland/qwaylandintegration.h | 2 + src/plugins/platforms/wayland/wayland.pro | 6 +- 7 files changed, 525 insertions(+), 2 deletions(-) create mode 100644 src/plugins/platforms/wayland/qwaylanddnd.cpp create mode 100644 src/plugins/platforms/wayland/qwaylanddnd.h diff --git a/src/plugins/platforms/wayland/qwaylanddisplay.cpp b/src/plugins/platforms/wayland/qwaylanddisplay.cpp index 1314eda..ca0e657 100644 --- a/src/plugins/platforms/wayland/qwaylanddisplay.cpp +++ b/src/plugins/platforms/wayland/qwaylanddisplay.cpp @@ -46,6 +46,7 @@ #include "qwaylandcursor.h" #include "qwaylandinputdevice.h" #include "qwaylandclipboard.h" +#include "qwaylanddnd.h" #ifdef QT_WAYLAND_GL_SUPPORT #include "gl_integration/qwaylandglintegration.h" @@ -315,6 +316,8 @@ void QWaylandDisplay::displayHandleGlobal(uint32_t id, mInputDevices.append(inputDevice); } else if (interface == "wl_selection_offer") { QWaylandClipboard::instance(display)->createSelectionOffer(id); + } else if (interface == "wl_drag_offer") { + QWaylandDrag::instance(display)->createDragOffer(id); } } diff --git a/src/plugins/platforms/wayland/qwaylanddnd.cpp b/src/plugins/platforms/wayland/qwaylanddnd.cpp new file mode 100644 index 0000000..5368daa --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddnd.cpp @@ -0,0 +1,423 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwaylanddnd.h" +#include "qwaylandinputdevice.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "qwaylandcursor.h" + +class QWaylandDragWrapper +{ +public: + QWaylandDragWrapper(QWaylandDisplay *display, QMimeData *data); + ~QWaylandDragWrapper(); + QMimeData *mimeData() const { return mData; } + +private: + static void target(void *data, wl_drag *drag, const char *mimeType); + static void finish(void *data, wl_drag *drag, int fd); + static void reject(void *data, wl_drag *drag); + static const wl_drag_listener dragListener; + + QWaylandDisplay *mDisplay; + wl_drag *mDrag; + QMimeData *mData; + QString mAcceptedType; +}; + +class QWaylandDragOfferWrapper +{ +public: + QWaylandDragOfferWrapper(QWaylandDisplay *display, QMimeData *data, uint32_t id); + ~QWaylandDragOfferWrapper(); + +private: + static void offer(void *data, struct wl_drag_offer *offer, const char *mimeType); + static void pointerFocus(void *data, struct wl_drag_offer *offer, uint32_t time, + wl_surface *surface, + int32_t x, int32_t y, + int32_t surfaceX, int32_t surfaceY); + static void motion(void *data, struct wl_drag_offer *offer, uint32_t time, + int32_t x, int32_t y, + int32_t surfaceX, int32_t surfaceY); + static void drop(void *data, struct wl_drag_offer *offer); + static const wl_drag_offer_listener dragOfferListener; + + void sendEventToWindow(struct wl_drag_offer *offer, uint32_t time, + wl_surface *surface, const QPoint &pos); + + QWaylandDisplay *mDisplay; + QMimeData *mData; + struct wl_drag_offer *mOffer; + QMimeData mOfferedTypes; // no data in this one, just the formats + wl_surface *mFocusSurface; + bool mAccepted; + QPoint mLastEventPos; + friend class QWaylandDrag; +}; + +static QWaylandDrag *dnd = 0; + +QWaylandDrag *QWaylandDrag::instance(QWaylandDisplay *display) +{ + if (!dnd) + dnd = new QWaylandDrag(display); + return dnd; +} + +QWaylandDrag::QWaylandDrag(QWaylandDisplay *display) + : mDisplay(display), mDropData(0), mCurrentDrag(0), mCurrentOffer(0) +{ + mDropData = new QMimeData; +} + +QWaylandDrag::~QWaylandDrag() +{ + delete mCurrentDrag; + delete mCurrentOffer; + delete mDropData; +} + +QMimeData *QWaylandDrag::platformDropData() +{ + return mDropData; +} + +static void showDragPixmap(bool show) +{ + QCursor c(QDragManager::self()->object->pixmap()); + QList > cursors = QPlatformCursorPrivate::getInstances(); + foreach (QWeakPointer cursor, cursors) + if (cursor) + static_cast(cursor.data())->setupPixmapCursor(show ? &c : 0); +} + + +QWaylandDragWrapper::QWaylandDragWrapper(QWaylandDisplay *display, QMimeData *data) + : mDisplay(display), mDrag(0), mData(data) +{ + QWaylandWindow *w = mDisplay->inputDevices().at(0)->pointerFocus(); + if (!w) { + qWarning("QWaylandDragWrapper: No window with pointer focus?!"); + return; + } + qDebug() << "QWaylandDragWrapper" << data->formats(); + struct wl_shell *shell = display->wl_shell(); + mDrag = wl_shell_create_drag(shell); + wl_drag_add_listener(mDrag, &dragListener, this); + foreach (const QString &format, data->formats()) + wl_drag_offer(mDrag, format.toLatin1().constData()); + struct timeval tv; + gettimeofday(&tv, 0); + wl_drag_activate(mDrag, + w->wl_surface(), + display->inputDevices().at(0)->wl_input_device(), + tv.tv_sec * 1000 + tv.tv_usec / 1000); + showDragPixmap(true); +} + +QWaylandDragWrapper::~QWaylandDragWrapper() +{ + QWaylandDrag *dragHandler = QWaylandDrag::instance(mDisplay); + if (dragHandler->mCurrentDrag == this) + dragHandler->mCurrentDrag = 0; + wl_drag_destroy(mDrag); +} + +const wl_drag_listener QWaylandDragWrapper::dragListener = { + QWaylandDragWrapper::target, + QWaylandDragWrapper::finish, + QWaylandDragWrapper::reject +}; + +void QWaylandDragWrapper::target(void *data, wl_drag *drag, const char *mimeType) +{ + Q_UNUSED(drag); + QWaylandDragWrapper *self = static_cast(data); + self->mAcceptedType = mimeType ? QString::fromLatin1(mimeType) : QString(); + qDebug() << "target" << self->mAcceptedType; + QDragManager *manager = QDragManager::self(); + if (mimeType) + manager->global_accepted_action = manager->defaultAction(manager->possible_actions, + QGuiApplication::keyboardModifiers()); + else + manager->global_accepted_action = Qt::IgnoreAction; +} + +void QWaylandDragWrapper::finish(void *data, wl_drag *drag, int fd) +{ + Q_UNUSED(drag); + QWaylandDragWrapper *self = static_cast(data); + qDebug() << "finish" << self->mAcceptedType; + if (self->mAcceptedType.isEmpty()) + return; // no drag target was valid when the drag finished + QByteArray content = self->mData->data(self->mAcceptedType); + if (!content.isEmpty()) { + QFile f; + if (f.open(fd, QIODevice::WriteOnly)) + f.write(content); + } + close(fd); + // Drag finished on source side with drop. + + QDragManager::self()->stopDrag(); + showDragPixmap(false); + delete self; + qDebug() << " *** DRAG OVER WITH DROP"; +} + +void QWaylandDragWrapper::reject(void *data, wl_drag *drag) +{ + Q_UNUSED(drag); + QWaylandDragWrapper *self = static_cast(data); + self->mAcceptedType = QString(); + qDebug() << "reject"; + QDragManager::self()->global_accepted_action = Qt::IgnoreAction; +} + + +QWaylandDragOfferWrapper::QWaylandDragOfferWrapper(QWaylandDisplay *display, + QMimeData *data, + uint32_t id) + : mDisplay(display), mData(data), mOffer(0), mFocusSurface(0), + mAccepted(false) +{ + mOffer = wl_drag_offer_create(mDisplay->wl_display(), id, 1); + wl_drag_offer_add_listener(mOffer, &dragOfferListener, this); +} + +QWaylandDragOfferWrapper::~QWaylandDragOfferWrapper() +{ + QWaylandDrag *dragHandler = QWaylandDrag::instance(mDisplay); + if (dragHandler->mCurrentOffer == this) + dragHandler->mCurrentOffer = 0; + wl_drag_offer_destroy(mOffer); +} + +const wl_drag_offer_listener QWaylandDragOfferWrapper::dragOfferListener = { + QWaylandDragOfferWrapper::offer, + QWaylandDragOfferWrapper::pointerFocus, + QWaylandDragOfferWrapper::motion, + QWaylandDragOfferWrapper::drop +}; + +void QWaylandDragOfferWrapper::offer(void *data, struct wl_drag_offer *offer, const char *mimeType) +{ + // Called for each type before pointerFocus. + Q_UNUSED(offer); + QWaylandDragOfferWrapper *self = static_cast(data); + self->mOfferedTypes.setData(QString::fromLatin1(mimeType), QByteArray()); +} + +void QWaylandDragOfferWrapper::pointerFocus(void *data, struct wl_drag_offer *offer, uint32_t time, + wl_surface *surface, + int32_t x, int32_t y, + int32_t surfaceX, int32_t surfaceY) +{ + qDebug() << "pointerFocus" << surface << x << y << surfaceX << surfaceY; + QWaylandDragOfferWrapper *self = static_cast(data); + QWaylandDrag *mgr = QWaylandDrag::instance(self->mDisplay); + + if (!surface) { + if (self->mFocusSurface) { + // This is a DragLeave. + QWindow *window = static_cast( + wl_surface_get_user_data(self->mFocusSurface))->window(); + QWindowSystemInterface::handleDrag(window, 0, QPoint()); + if (self->mAccepted) { + wl_drag_offer_reject(offer); + self->mAccepted = false; + } + if (!mgr->mCurrentDrag) // no drag -> this is not the source side -> offer can be destroyed + delete mgr->mCurrentOffer; + } else { + // Drag finished on source side without drop. + QDragManager::self()->stopDrag(); + showDragPixmap(false); + delete mgr->mCurrentDrag; + qDebug() << " *** DRAG OVER WITHOUT DROP"; + } + } + + self->mFocusSurface = surface; + + // This is a DragMove or DragEnter+DragMove. + if (surface) + self->sendEventToWindow(offer, time, surface, QPoint(surfaceX, surfaceY)); +} + +void QWaylandDragOfferWrapper::motion(void *data, struct wl_drag_offer *offer, uint32_t time, + int32_t x, int32_t y, + int32_t surfaceX, int32_t surfaceY) +{ + Q_UNUSED(x); + Q_UNUSED(y); + QWaylandDragOfferWrapper *self = static_cast(data); + if (!self->mFocusSurface) + return; +// qDebug() << "motion" << self->mFocusSurface << x << y << surfaceX << surfaceY; + self->sendEventToWindow(offer, time, self->mFocusSurface, QPoint(surfaceX, surfaceY)); +} + +void QWaylandDragOfferWrapper::sendEventToWindow(struct wl_drag_offer *offer, uint32_t time, + wl_surface *surface, const QPoint &pos) +{ + QWindow *window = static_cast(wl_surface_get_user_data(surface))->window(); + Qt::DropAction action = QWindowSystemInterface::handleDrag(window, &mOfferedTypes, pos); + bool accepted = (action != Qt::IgnoreAction && !mOfferedTypes.formats().isEmpty()); + if (accepted != mAccepted) { + mAccepted = accepted; + if (mAccepted) { + // What can we do, just accept the first type... + QByteArray ba = mOfferedTypes.formats().first().toLatin1(); + qDebug() << "wl_drag_offer_accept" << ba; + wl_drag_offer_accept(offer, time, ba.constData()); + } else { + qDebug() << "wl_drag_offer_reject"; + wl_drag_offer_reject(offer); + } + } + mLastEventPos = pos; +} + +void QWaylandDragOfferWrapper::drop(void *data, struct wl_drag_offer *offer) +{ + QWaylandDragOfferWrapper *self = static_cast(data); + if (!self->mAccepted) { + wl_drag_offer_reject(offer); + return; + } + + QWaylandDrag *mgr = QWaylandDrag::instance(self->mDisplay); + QMimeData *mimeData = QWaylandDrag::instance(self->mDisplay)->platformDropData(); + mimeData->clear(); + if (mgr->mCurrentDrag) { // means this offer is the client's own + QMimeData *localData = mgr->mCurrentDrag->mimeData(); + foreach (const QString &format, localData->formats()) + mimeData->setData(format, localData->data(format)); + QWindow *window = static_cast( + wl_surface_get_user_data(self->mFocusSurface))->window(); + QWindowSystemInterface::handleDrop(window, mimeData, self->mLastEventPos); + // Drag finished with drop (source == target). + QDragManager::self()->stopDrag(); + showDragPixmap(false); + delete mgr->mCurrentOffer; + qDebug() << " *** DRAG OVER WITH DROP, SOURCE == TARGET"; + } else { + // ### TODO + // This is a bit broken: The QMimeData will only contain the data for + // the first type. The Wayland protocol and QDropEvents/QMimeData do not + // match perfectly at the moment. + QString format = self->mOfferedTypes.formats().first(); + QByteArray mimeTypeBa = format.toLatin1(); + int pipefd[2]; + if (pipe(pipefd) == -1) { + qWarning("QWaylandDragOfferWrapper: pipe() failed"); + return; + } + fcntl(pipefd[0], F_SETFL, fcntl(pipefd[0], F_GETFL, 0) | O_NONBLOCK); + wl_drag_offer_receive(offer, pipefd[1]); + mgr->mPipeData.clear(); + mgr->mMimeFormat = format; + mgr->mPipeWriteEnd = pipefd[1]; + mgr->mPipeWatcher = new QSocketNotifier(pipefd[0], QSocketNotifier::Read); + QObject::connect(mgr->mPipeWatcher, SIGNAL(activated(int)), mgr, SLOT(pipeReadable(int))); + } +} + + +void QWaylandDrag::pipeReadable(int fd) +{ + if (mPipeWriteEnd) { + close(mPipeWriteEnd); + mPipeWriteEnd = 0; + } + char buf[256]; + int n; + while ((n = read(fd, &buf, sizeof buf)) > 0 || errno == EINTR) + if (n > 0) + mPipeData.append(buf, n); + if (n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) + return; + delete mPipeWatcher; + close(fd); + + QMimeData *mimeData = platformDropData(); + mimeData->setData(mMimeFormat, mPipeData); + foreach (const QString &format, mimeData->formats()) + qDebug() << " got type" << format << "with data" << mimeData->data(format); + + QWindow *window = static_cast( + wl_surface_get_user_data(mCurrentOffer->mFocusSurface))->window(); + QWindowSystemInterface::handleDrop(window, mimeData, mCurrentOffer->mLastEventPos); + + // Drag finished on target side with drop. + delete mCurrentOffer; + qDebug() << " *** DRAG OVER ON TARGET WITH DROP"; +} + +void QWaylandDrag::createDragOffer(uint32_t id) +{ + delete mCurrentOffer; + mCurrentOffer = new QWaylandDragOfferWrapper(mDisplay, mDropData, id); +} + +void QWaylandDrag::startDrag() +{ + QDragManager *manager = QDragManager::self(); + + // No need for the traditional desktop-oriented event handling in QDragManager. + manager->unmanageEvents(); + + delete mCurrentDrag; + mCurrentDrag = new QWaylandDragWrapper(mDisplay, manager->dropData()); +} diff --git a/src/plugins/platforms/wayland/qwaylanddnd.h b/src/plugins/platforms/wayland/qwaylanddnd.h new file mode 100644 index 0000000..ebafd96 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylanddnd.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWAYLANDDND_H +#define QWAYLANDDND_H + +#include +#include +#include "qwaylanddisplay.h" + +class QWaylandDragWrapper; +class QWaylandDragOfferWrapper; +class QSocketNotifier; + +class QWaylandDrag : public QObject, public QPlatformDrag +{ + Q_OBJECT + +public: + static QWaylandDrag *instance(QWaylandDisplay *display); + ~QWaylandDrag(); + void createDragOffer(uint32_t id); + + QMimeData *platformDropData(); + void startDrag(); + void move(const QMouseEvent *) { } + void drop(const QMouseEvent *) { } + void cancel() { } + +private slots: + void pipeReadable(int fd); + +private: + QWaylandDrag(QWaylandDisplay *display); + + QWaylandDisplay *mDisplay; + QMimeData *mDropData; + QWaylandDragWrapper *mCurrentDrag; + QWaylandDragOfferWrapper *mCurrentOffer; + int mPipeWriteEnd; + QSocketNotifier *mPipeWatcher; + QByteArray mPipeData; + QString mMimeFormat; + friend class QWaylandDragWrapper; + friend class QWaylandDragOfferWrapper; +}; + +#endif // QWAYLANDDND_H diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice.h b/src/plugins/platforms/wayland/qwaylandinputdevice.h index 008ecf1..05ebe05 100644 --- a/src/plugins/platforms/wayland/qwaylandinputdevice.h +++ b/src/plugins/platforms/wayland/qwaylandinputdevice.h @@ -63,6 +63,7 @@ public: void attach(QWaylandBuffer *buffer, int x, int y); void handleWindowDestroyed(QWaylandWindow *window); struct wl_input_device *wl_input_device() const { return mInputDevice; } + QWaylandWindow *pointerFocus() const { return mPointerFocus; } private: QWaylandDisplay *mQDisplay; diff --git a/src/plugins/platforms/wayland/qwaylandintegration.cpp b/src/plugins/platforms/wayland/qwaylandintegration.cpp index 5405a85..d3d7edf 100644 --- a/src/plugins/platforms/wayland/qwaylandintegration.cpp +++ b/src/plugins/platforms/wayland/qwaylandintegration.cpp @@ -46,6 +46,7 @@ #include "qwaylandshmwindow.h" #include "qwaylandnativeinterface.h" #include "qwaylandclipboard.h" +#include "qwaylanddnd.h" #include "QtPlatformSupport/private/qgenericunixfontdatabase_p.h" #include @@ -131,3 +132,8 @@ QPlatformClipboard *QWaylandIntegration::clipboard() const { return QWaylandClipboard::instance(mDisplay); } + +QPlatformDrag *QWaylandIntegration::drag() const +{ + return QWaylandDrag::instance(mDisplay); +} diff --git a/src/plugins/platforms/wayland/qwaylandintegration.h b/src/plugins/platforms/wayland/qwaylandintegration.h index 0dc3b61..3c8b996 100644 --- a/src/plugins/platforms/wayland/qwaylandintegration.h +++ b/src/plugins/platforms/wayland/qwaylandintegration.h @@ -69,6 +69,8 @@ public: QPlatformClipboard *clipboard() const; + QPlatformDrag *drag() const; + private: QPlatformFontDatabase *mFontDb; QWaylandDisplay *mDisplay; diff --git a/src/plugins/platforms/wayland/wayland.pro b/src/plugins/platforms/wayland/wayland.pro index 9f4563a..a46f508 100644 --- a/src/plugins/platforms/wayland/wayland.pro +++ b/src/plugins/platforms/wayland/wayland.pro @@ -24,7 +24,8 @@ SOURCES = main.cpp \ qwaylandwindow.cpp \ qwaylandscreen.cpp \ qwaylandshmwindow.cpp \ - qwaylandclipboard.cpp + qwaylandclipboard.cpp \ + qwaylanddnd.cpp HEADERS = qwaylandintegration.h \ qwaylandnativeinterface.h \ @@ -35,7 +36,8 @@ HEADERS = qwaylandintegration.h \ qwaylandshmbackingstore.h \ qwaylandbuffer.h \ qwaylandshmwindow.h \ - qwaylandclipboard.h + qwaylandclipboard.h \ + qwaylanddnd.h INCLUDEPATH += $$QMAKE_INCDIR_WAYLAND LIBS += $$QMAKE_LIBS_WAYLAND -- 2.7.4