Fix crash when dragging with touch without first having pointer focus

Also add drag-and-drop tests for both touch and mouse.

Task-number: QTBUG-54756
Change-Id: Ibfff48b1f2377022a8624e28e9f638076187ddca
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
This commit is contained in:
Johan Klokkhammer Helsing 2016-08-01 09:43:59 +02:00 committed by Johan Helsing
parent 8184ff6bfb
commit 1e36cce9fe
6 changed files with 285 additions and 0 deletions

View File

@ -107,7 +107,10 @@ void QWaylandDataDevice::startDrag(QMimeData *mimeData, QWaylandWindow *icon)
{
m_dragSource.reset(new QWaylandDataSource(m_display->dndSelectionHandler(), mimeData));
connect(m_dragSource.data(), &QWaylandDataSource::cancelled, this, &QWaylandDataDevice::dragSourceCancelled);
QWaylandWindow *origin = m_display->currentInputDevice()->pointerFocus();
if (!origin)
origin = m_display->currentInputDevice()->touchFocus();
start_drag(m_dragSource->object(), origin->object(), icon->object(), m_display->currentInputDevice()->serial());
}

View File

@ -150,6 +150,47 @@ void MockCompositor::sendTouchFrame(const QSharedPointer<MockSurface> &surface)
processCommand(command);
}
void MockCompositor::sendDataDeviceDataOffer(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceDataOffer, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::sendDataDeviceEnter(const QSharedPointer<MockSurface> &surface, const QPoint& position)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceEnter, m_compositor);
command.parameters << QVariant::fromValue(surface) << QVariant::fromValue(position);
processCommand(command);
}
void MockCompositor::sendDataDeviceMotion(const QPoint &position)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceMotion, m_compositor);
command.parameters << QVariant::fromValue(position);
processCommand(command);
}
void MockCompositor::sendDataDeviceDrop(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceDrop, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::sendDataDeviceLeave(const QSharedPointer<MockSurface> &surface)
{
Command command = makeCommand(Impl::Compositor::sendDataDeviceLeave, m_compositor);
command.parameters << QVariant::fromValue(surface);
processCommand(command);
}
void MockCompositor::waitForStartDrag()
{
Command command = makeCommand(Impl::Compositor::waitForStartDrag, m_compositor);
processCommand(command);
}
QSharedPointer<MockSurface> MockCompositor::surface()
{
QSharedPointer<MockSurface> result;
@ -228,6 +269,7 @@ namespace Impl {
Compositor::Compositor()
: m_display(wl_display_create())
, m_startDragSeen(false)
, m_time(0)
{
wl_list_init(&m_outputResources);

View File

@ -85,6 +85,15 @@ public:
static void sendTouchUp(void *data, const QList<QVariant> &parameters);
static void sendTouchMotion(void *data, const QList<QVariant> &parameters);
static void sendTouchFrame(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceDataOffer(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceEnter(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceMotion(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceDrop(void *data, const QList<QVariant> &parameters);
static void sendDataDeviceLeave(void *data, const QList<QVariant> &parameters);
static void waitForStartDrag(void *data, const QList<QVariant> &parameters);
public:
bool m_startDragSeen;
private:
static void bindCompositor(wl_client *client, void *data, uint32_t version, uint32_t id);
@ -156,6 +165,12 @@ public:
void sendTouchMotion(const QSharedPointer<MockSurface> &surface, const QPoint &position, int id);
void sendTouchUp(const QSharedPointer<MockSurface> &surface, int id);
void sendTouchFrame(const QSharedPointer<MockSurface> &surface);
void sendDataDeviceDataOffer(const QSharedPointer<MockSurface> &surface);
void sendDataDeviceEnter(const QSharedPointer<MockSurface> &surface, const QPoint &position);
void sendDataDeviceMotion(const QPoint &position);
void sendDataDeviceDrop(const QSharedPointer<MockSurface> &surface);
void sendDataDeviceLeave(const QSharedPointer<MockSurface> &surface);
void waitForStartDrag();
QSharedPointer<MockSurface> surface();

View File

@ -144,6 +144,70 @@ void Compositor::sendTouchFrame(void *data, const QList<QVariant> &parameters)
compositor->m_touch->sendFrame(surface);
}
void Compositor::sendDataDeviceDataOffer(void *data, const QList<QVariant> &parameters)
{
Compositor *compositor = static_cast<Compositor *>(data);
Surface *surface = resolveSurface(parameters.first());
Q_ASSERT(compositor);
Q_ASSERT(surface);
compositor->m_data_device_manager->dataDevice()->sendDataOffer(surface->resource()->client());
}
void Compositor::sendDataDeviceEnter(void *data, const QList<QVariant> &parameters)
{
Compositor *compositor = static_cast<Compositor *>(data);
Surface *surface = resolveSurface(parameters.first());
QPoint position = parameters.at(1).toPoint();
Q_ASSERT(compositor);
Q_ASSERT(surface);
compositor->m_data_device_manager->dataDevice()->sendEnter(surface, position);
}
void Compositor::sendDataDeviceMotion(void *data, const QList<QVariant> &parameters)
{
Compositor *compositor = static_cast<Compositor *>(data);
Q_ASSERT(compositor);
QPoint position = parameters.first().toPoint();
compositor->m_data_device_manager->dataDevice()->sendMotion(position);
}
void Compositor::sendDataDeviceDrop(void *data, const QList<QVariant> &parameters)
{
Compositor *compositor = static_cast<Compositor *>(data);
Surface *surface = resolveSurface(parameters.first());
Q_ASSERT(compositor);
Q_ASSERT(surface);
compositor->m_data_device_manager->dataDevice()->sendDrop(surface);
}
void Compositor::sendDataDeviceLeave(void *data, const QList<QVariant> &parameters)
{
Compositor *compositor = static_cast<Compositor *>(data);
Surface *surface = resolveSurface(parameters.first());
Q_ASSERT(compositor);
Q_ASSERT(surface);
compositor->m_data_device_manager->dataDevice()->sendLeave(surface);
}
void Compositor::waitForStartDrag(void *data, const QList<QVariant> &parameters)
{
Compositor *compositor = static_cast<Compositor *>(data);
Q_ASSERT(compositor);
while (!compositor->m_startDragSeen) {
wl_display_flush_clients(compositor->m_display);
wl_event_loop_dispatch(compositor->m_loop, 100);
}
compositor->m_startDragSeen = false;
}
Seat::Seat(Compositor *compositor, struct ::wl_display *display)
: wl_seat(display, 2)
, m_compositor(compositor)
@ -312,18 +376,65 @@ void Touch::sendFrame(Surface *surface)
wl_touch_send_frame(resource->handle);
}
DataOffer::DataOffer()
: wl_data_offer()
{
}
DataDevice::DataDevice(Compositor *compositor)
: wl_data_device()
, m_compositor(compositor)
, m_dataOffer(nullptr)
, m_focus(nullptr)
{
}
void DataDevice::sendDataOffer(wl_client *client)
{
m_dataOffer = new QtWaylandServer::wl_data_offer(client, 0, 1);
Resource *resource = resourceMap().value(client);
send_data_offer(resource->handle, m_dataOffer->resource()->handle);
}
void DataDevice::sendEnter(Surface *surface, const QPoint& position)
{
uint serial = m_compositor->nextSerial();
m_focus = surface;
Resource *resource = resourceMap().value(surface->resource()->client());
send_enter(resource->handle, serial, surface->resource()->handle, position.x(), position.y(), m_dataOffer->resource()->handle);
}
void DataDevice::sendMotion(const QPoint &position)
{
uint32_t time = m_compositor->time();
Resource *resource = resourceMap().value(m_focus->resource()->client());
send_motion(resource->handle, time, position.x(), position.y());
}
void DataDevice::sendDrop(Surface *surface)
{
Resource *resource = resourceMap().value(surface->resource()->client());
send_drop(resource->handle);
}
void DataDevice::sendLeave(Surface *surface)
{
Resource *resource = resourceMap().value(surface->resource()->client());
send_leave(resource->handle);
}
DataDevice::~DataDevice()
{
}
void DataDevice::data_device_start_drag(QtWaylandServer::wl_data_device::Resource *resource, wl_resource *source, wl_resource *origin, wl_resource *icon, uint32_t serial)
{
m_compositor->m_startDragSeen = true;
}
DataDeviceManager::DataDeviceManager(Compositor *compositor, wl_display *display)
: wl_data_device_manager(display, 1)
, m_compositor(compositor)
@ -336,6 +447,11 @@ DataDeviceManager::~DataDeviceManager()
}
DataDevice *DataDeviceManager::dataDevice() const
{
return m_data_device.data();
}
void DataDeviceManager::data_device_manager_get_data_device(Resource *resource, uint32_t id, struct ::wl_resource *seat)
{
if (!m_data_device)
@ -343,4 +459,9 @@ void DataDeviceManager::data_device_manager_get_data_device(Resource *resource,
m_data_device->add(resource->client(), id, 1);
}
void DataDeviceManager::data_device_manager_create_data_source(QtWaylandServer::wl_data_device_manager::Resource *resource, uint32_t id)
{
new QtWaylandServer::wl_data_source(resource->client(), id, 1);
}
}

View File

@ -127,14 +127,30 @@ private:
Compositor *m_compositor;
};
class DataOffer : public QtWaylandServer::wl_data_offer
{
public:
DataOffer();
};
class DataDevice : public QtWaylandServer::wl_data_device
{
public:
DataDevice(Compositor *compositor);
void sendDataOffer(wl_client *client);
void sendEnter(Surface *surface, const QPoint &position);
void sendMotion(const QPoint &position);
void sendDrop(Surface *surface);
void sendLeave(Surface *surface);
~DataDevice();
protected:
void data_device_start_drag(Resource *resource, struct ::wl_resource *source, struct ::wl_resource *origin, struct ::wl_resource *icon, uint32_t serial) override;
private:
Compositor *m_compositor;
QtWaylandServer::wl_data_offer *m_dataOffer;
Surface* m_focus;
};
class DataDeviceManager : public QtWaylandServer::wl_data_device_manager
@ -142,9 +158,11 @@ class DataDeviceManager : public QtWaylandServer::wl_data_device_manager
public:
DataDeviceManager(Compositor *compositor, struct ::wl_display *display);
~DataDeviceManager();
DataDevice *dataDevice() const;
protected:
void data_device_manager_get_data_device(Resource *resource, uint32_t id, struct ::wl_resource *seat) Q_DECL_OVERRIDE;
void data_device_manager_create_data_source(Resource *resource, uint32_t id) override;
private:
Compositor *m_compositor;

View File

@ -36,6 +36,10 @@
#include <QBackingStore>
#include <QPainter>
#include <QScreen>
#include <QWindow>
#include <QMimeData>
#include <QPixmap>
#include <QDrag>
#include <QtTest/QtTest>
@ -141,6 +145,8 @@ private slots:
void createDestroyWindow();
void events();
void backingStore();
void touchDrag();
void mouseDrag();
private:
MockCompositor *compositor;
@ -251,6 +257,86 @@ void tst_WaylandClient::backingStore()
QTRY_VERIFY(surface->image.isNull());
}
class DndWindow : public QWindow
{
Q_OBJECT
public:
DndWindow(QWindow *parent = 0)
: QWindow(parent)
, dragStarted(false)
{
QImage cursorImage(64,64,QImage::Format_ARGB32);
cursorImage.fill(Qt::blue);
m_dragIcon = QPixmap::fromImage(cursorImage);
}
~DndWindow(){}
bool dragStarted;
protected:
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE
{
if (dragStarted)
return;
dragStarted = true;
QByteArray dataBytes;
QMimeData *mimeData = new QMimeData;
mimeData->setData("application/x-dnditemdata", dataBytes);
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(m_dragIcon);
drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction);
}
private:
QPixmap m_dragIcon;
};
void tst_WaylandClient::touchDrag()
{
DndWindow window;
window.show();
QSharedPointer<MockSurface> surface;
QTRY_VERIFY(surface = compositor->surface());
compositor->setKeyboardFocus(surface);
QTRY_COMPARE(QGuiApplication::focusWindow(), &window);
const int id = 0;
compositor->sendTouchDown(surface, QPoint(10, 10), id);
compositor->sendTouchMotion(surface, QPoint(20, 20), id);
compositor->sendTouchFrame(surface);
compositor->waitForStartDrag();
compositor->sendDataDeviceDataOffer(surface);
compositor->sendDataDeviceEnter(surface, QPoint(20, 20));
compositor->sendDataDeviceMotion( QPoint(21, 21));
compositor->sendDataDeviceDrop(surface);
compositor->sendDataDeviceLeave(surface);
QTRY_VERIFY(window.dragStarted);
}
void tst_WaylandClient::mouseDrag()
{
DndWindow window;
window.show();
QSharedPointer<MockSurface> surface;
QTRY_VERIFY(surface = compositor->surface());
compositor->setKeyboardFocus(surface);
QTRY_COMPARE(QGuiApplication::focusWindow(), &window);
compositor->sendMousePress(surface, QPoint(10, 10));
compositor->sendDataDeviceDataOffer(surface);
compositor->sendDataDeviceEnter(surface, QPoint(20, 20));
compositor->sendDataDeviceMotion( QPoint(21, 21));
compositor->waitForStartDrag();
compositor->sendDataDeviceDrop(surface);
compositor->sendDataDeviceLeave(surface);
QTRY_VERIFY(window.dragStarted);
}
int main(int argc, char **argv)
{
setenv("XDG_RUNTIME_DIR", ".", 1);