Client: Implement wl_pointer version 5

Version 5 adds frame events, which groups multiple pointer events together,
enabling diagonal scrolling, leave and enter without an intermediate state, and
together with the new axis_source, axis_discrete and axis_stop also adds what
needed to differentiate between wheel events and touchpad scrolling.

This patch adds scroll phases and pixel deltas to QWaylandInputDevice, and
makes sure handleWheelEvent is called accordingly.

[ChangeLog][QPA plugin] Pixel delta is now set for mouse scrolling events if
originating from an appropriate device such as a touch pad (requires compositor
support for wl_seat version 5 or later).

Fixes: QTBUG-69876
Fixes: QTBUG-63720
Change-Id: I094a1ef0365893bee135cae7e6df89fafdafa2f2
Reviewed-by: Giulio Camuffo <giulio.camuffo@kdab.com>
This commit is contained in:
Robin Burchell 2017-07-30 21:50:41 +02:00 committed by Johan Helsing
parent 7f21a684f7
commit df531a6f3c
9 changed files with 816 additions and 74 deletions

View File

@ -78,6 +78,8 @@ QT_BEGIN_NAMESPACE
namespace QtWaylandClient {
Q_LOGGING_CATEGORY(lcQpaWaylandInput, "qt.qpa.wayland.input");
QWaylandInputDevice::Keyboard::Keyboard(QWaylandInputDevice *p)
: mParent(p)
{
@ -200,10 +202,10 @@ QWaylandInputDevice::Touch::~Touch()
}
QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, uint32_t id)
: QtWayland::wl_seat(display->wl_registry(), id, qMin(version, 4))
: QtWayland::wl_seat(display->wl_registry(), id, qMin(version, 5))
, mQDisplay(display)
, mDisplay(display->wl_display())
, mVersion(qMin(version, 4))
, mVersion(qMin(version, 5))
{
#if QT_CONFIG(wayland_datadevice)
if (mQDisplay->dndSelectionHandler()) {
@ -446,8 +448,9 @@ void QWaylandInputDevice::setCursor(const QSharedPointer<QWaylandBuffer> &buffer
class EnterEvent : public QWaylandPointerEvent
{
public:
EnterEvent(const QPointF &l, const QPointF &g)
: QWaylandPointerEvent(QWaylandPointerEvent::Enter, 0, l, g, nullptr, Qt::NoModifier)
EnterEvent(QWaylandWindow *surface, const QPointF &local, const QPointF &global)
: QWaylandPointerEvent(QWaylandPointerEvent::Enter, Qt::NoScrollPhase, surface, 0,
local, global, nullptr, Qt::NoModifier)
{}
};
@ -471,12 +474,19 @@ void QWaylandInputDevice::Pointer::pointer_enter(uint32_t serial, struct wl_surf
#endif
QWaylandWindow *grab = QWaylandWindow::mouseGrab();
if (!grab) {
EnterEvent evt(mSurfacePos, mGlobalPos);
window->handleMouse(mParent, evt);
}
if (!grab)
setFrameEvent(new EnterEvent(window, mSurfacePos, mGlobalPos));
}
class LeaveEvent : public QWaylandPointerEvent
{
public:
LeaveEvent(QWaylandWindow *surface, const QPointF &localPos, const QPointF &globalPos)
: QWaylandPointerEvent(QWaylandPointerEvent::Leave, Qt::NoScrollPhase, surface, 0,
localPos, globalPos, nullptr, Qt::NoModifier)
{}
};
void QWaylandInputDevice::Pointer::pointer_leave(uint32_t time, struct wl_surface *surface)
{
// The event may arrive after destroying the window, indicated by
@ -486,7 +496,7 @@ void QWaylandInputDevice::Pointer::pointer_leave(uint32_t time, struct wl_surfac
if (!QWaylandWindow::mouseGrab()) {
QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface);
window->handleMouseLeave(mParent);
setFrameEvent(new LeaveEvent(window, mSurfacePos, mGlobalPos));
}
mFocus = nullptr;
mButtons = Qt::NoButton;
@ -497,8 +507,10 @@ void QWaylandInputDevice::Pointer::pointer_leave(uint32_t time, struct wl_surfac
class MotionEvent : public QWaylandPointerEvent
{
public:
MotionEvent(ulong t, const QPointF &l, const QPointF &g, Qt::MouseButtons b, Qt::KeyboardModifiers m)
: QWaylandPointerEvent(QWaylandPointerEvent::Motion, t, l, g, b, m)
MotionEvent(QWaylandWindow *surface, ulong timestamp, const QPointF &localPos,
const QPointF &globalPos, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers)
: QWaylandPointerEvent(QWaylandPointerEvent::Motion, Qt::NoScrollPhase, surface,
timestamp, localPos, globalPos, buttons, modifiers)
{
}
};
@ -527,14 +539,33 @@ void QWaylandInputDevice::Pointer::pointer_motion(uint32_t time, wl_fixed_t surf
// so we just set it outside of the window boundaries.
pos = QPointF(-1, -1);
global = grab->window()->mapToGlobal(pos.toPoint());
MotionEvent e(time, pos, global, mButtons, mParent->modifiers());
grab->handleMouse(mParent, e);
} else {
MotionEvent e(time, mSurfacePos, mGlobalPos, mButtons, mParent->modifiers());
window->handleMouse(mParent, e);
window = grab;
}
setFrameEvent(new MotionEvent(window, time, pos, global, mButtons, mParent->modifiers()));
}
class PressEvent : public QWaylandPointerEvent
{
public:
PressEvent(QWaylandWindow *surface, ulong timestamp, const QPointF &localPos,
const QPointF &globalPos, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers)
: QWaylandPointerEvent(QWaylandPointerEvent::Press, Qt::NoScrollPhase, surface,
timestamp, localPos, globalPos, buttons, modifiers)
{
}
};
class ReleaseEvent : public QWaylandPointerEvent
{
public:
ReleaseEvent(QWaylandWindow *surface, ulong timestamp, const QPointF &localPos,
const QPointF &globalPos, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers)
: QWaylandPointerEvent(QWaylandPointerEvent::Release, Qt::NoScrollPhase, surface,
timestamp, localPos, globalPos, buttons, modifiers)
{
}
};
void QWaylandInputDevice::Pointer::pointer_button(uint32_t serial, uint32_t time,
uint32_t button, uint32_t state)
{
@ -580,21 +611,26 @@ void QWaylandInputDevice::Pointer::pointer_button(uint32_t serial, uint32_t time
mParent->mQDisplay->setLastInputDevice(mParent, serial, window);
QWaylandWindow *grab = QWaylandWindow::mouseGrab();
QPointF pos = mSurfacePos;
QPointF global = mGlobalPos;
if (grab && grab != mFocus) {
QPointF pos = QPointF(-1, -1);
QPointF global = grab->window()->mapToGlobal(pos.toPoint());
MotionEvent e(time, pos, global, mButtons, mParent->modifiers());
grab->handleMouse(mParent, e);
} else if (window) {
MotionEvent e(time, mSurfacePos, mGlobalPos, mButtons, mParent->modifiers());
window->handleMouse(mParent, e);
pos = QPointF(-1, -1);
global = grab->window()->mapToGlobal(pos.toPoint());
window = grab;
}
if (state)
setFrameEvent(new PressEvent(window, time, pos, global, mButtons, mParent->modifiers()));
else
setFrameEvent(new ReleaseEvent(window, time, pos, global, mButtons, mParent->modifiers()));
}
void QWaylandInputDevice::Pointer::releaseButtons()
{
mButtons = Qt::NoButton;
MotionEvent e(mParent->mTime, mSurfacePos, mGlobalPos, mButtons, mParent->modifiers());
MotionEvent e(mFocus, mParent->mTime, mSurfacePos, mGlobalPos, mButtons, mParent->modifiers());
if (mFocus)
mFocus->handleMouse(mParent, e);
}
@ -602,8 +638,11 @@ void QWaylandInputDevice::Pointer::releaseButtons()
class WheelEvent : public QWaylandPointerEvent
{
public:
WheelEvent(ulong t, const QPointF &l, const QPointF &g, const QPoint &pd, const QPoint &ad, Qt::KeyboardModifiers m)
: QWaylandPointerEvent(QWaylandPointerEvent::Wheel, t, l, g, pd, ad, m)
WheelEvent(QWaylandWindow *surface, Qt::ScrollPhase phase, ulong timestamp, const QPointF &local,
const QPointF &global, const QPoint &pixelDelta, const QPoint &angleDelta,
Qt::MouseEventSource source, Qt::KeyboardModifiers modifiers)
: QWaylandPointerEvent(QWaylandPointerEvent::Wheel, phase, surface, timestamp,
local, global, pixelDelta, angleDelta, source, modifiers)
{
}
};
@ -611,28 +650,247 @@ public:
void QWaylandInputDevice::Pointer::pointer_axis(uint32_t time, uint32_t axis, int32_t value)
{
QWaylandWindow *window = mFocus;
if (!window) {
// We destroyed the pointer focus surface, but the server didn't get the message yet...
// or the server didn't send an enter event first. In either case, ignore the event.
return;
}
QPoint pixelDelta;
QPoint angleDelta;
//normalize value and inverse axis
int valueDelta = wl_fixed_to_int(value) * -12;
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
pixelDelta = QPoint();
angleDelta.setX(valueDelta);
} else {
pixelDelta = QPoint();
angleDelta.setY(valueDelta);
// Get the delta and convert it into the expected range
switch (axis) {
case WL_POINTER_AXIS_VERTICAL_SCROLL:
mFrameData.delta.ry() += wl_fixed_to_double(value);
qCDebug(lcQpaWaylandInput) << "wl_pointer.axis vertical:" << mFrameData.delta.y();
break;
case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
mFrameData.delta.rx() += wl_fixed_to_double(value);
qCDebug(lcQpaWaylandInput) << "wl_pointer.axis horizontal:" << mFrameData.delta.x();
break;
default:
//TODO: is this really needed?
qCWarning(lcQpaWaylandInput) << "wl_pointer.axis: Unknown axis:" << axis;
return;
}
WheelEvent e(time, mSurfacePos, mGlobalPos, pixelDelta, angleDelta, mParent->modifiers());
window->handleMouse(mParent, e);
mParent->mTime = time;
if (mParent->mVersion < WL_POINTER_FRAME_SINCE_VERSION) {
qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version";
flushFrameEvent();
}
}
void QWaylandInputDevice::Pointer::pointer_frame()
{
flushFrameEvent();
}
void QWaylandInputDevice::Pointer::pointer_axis_source(uint32_t source)
{
switch (source) {
case axis_source_wheel:
qCDebug(lcQpaWaylandInput) << "Axis source wheel";
break;
case axis_source_finger:
qCDebug(lcQpaWaylandInput) << "Axis source finger";
break;
case axis_source_continuous:
qCDebug(lcQpaWaylandInput) << "Axis source continuous";
break;
}
mFrameData.axisSource = axis_source(source);
}
void QWaylandInputDevice::Pointer::pointer_axis_stop(uint32_t time, uint32_t axis)
{
QWaylandWindow *window = mFocus;
if (window == nullptr)
return;
mParent->mTime = time;
switch (axis) {
case axis_vertical_scroll:
qCDebug(lcQpaWaylandInput) << "Received vertical wl_pointer.axis_stop";
mFrameData.delta.setY(0); //TODO: what's the point of doing this?
break;
case axis_horizontal_scroll:
qCDebug(lcQpaWaylandInput) << "Received horizontal wl_pointer.axis_stop";
mFrameData.delta.setX(0);
break;
default:
qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_stop: Unknown axis: " << axis
<< "This is most likely a compositor bug";
return;
}
// May receive axis_stop for events we haven't sent a ScrollBegin for because
// most axis_sources do not mandate an axis_stop event to be sent.
if (!mScrollBeginSent) {
// TODO: For now, we just ignore these events, but we could perhaps take this as an
// indication that this compositor will in fact send axis_stop events for these sources
// and send a ScrollBegin the next time an axis_source event with this type is encountered.
return;
}
QWaylandWindow *target = QWaylandWindow::mouseGrab();
if (!target)
target = mFocus;
Qt::KeyboardModifiers mods = mParent->modifiers();
WheelEvent wheelEvent(mFocus, Qt::ScrollEnd, mParent->mTime, mSurfacePos, mGlobalPos,
QPoint(), QPoint(), Qt::MouseEventNotSynthesized, mods);
target->handleMouse(mParent, wheelEvent);
mScrollBeginSent = false;
mScrollDeltaRemainder = QPointF();
}
void QWaylandInputDevice::Pointer::pointer_axis_discrete(uint32_t axis, int32_t value)
{
QWaylandWindow *window = mFocus;
if (window == nullptr)
return;
switch (axis) {
case axis_vertical_scroll:
qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_discrete vertical:" << value;
mFrameData.discreteDelta.ry() += value;
break;
case axis_horizontal_scroll:
qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_discrete horizontal:" << value;
mFrameData.discreteDelta.rx() += value;
break;
default:
//TODO: is this really needed?
qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_discrete: Unknown axis:" << axis;
return;
}
}
void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event)
{
qCDebug(lcQpaWaylandInput) << "Setting frame event " << event->type;
if (mFrameData.event && mFrameData.event->type != event->type) {
qCDebug(lcQpaWaylandInput) << "Flushing; previous was " << mFrameData.event->type;
flushFrameEvent();
}
mFrameData.event = event;
if (mParent->mVersion < WL_POINTER_FRAME_SINCE_VERSION) {
qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version";
flushFrameEvent();
}
}
void QWaylandInputDevice::Pointer::FrameData::resetScrollData()
{
discreteDelta = QPoint();
delta = QPointF();
axisSource = axis_source_wheel;
}
bool QWaylandInputDevice::Pointer::FrameData::hasPixelDelta() const
{
switch (axisSource) {
case axis_source_wheel_tilt: // sideways tilt of the wheel
case axis_source_wheel:
// In the case of wheel events, a pixel delta doesn't really make sense,
// and will make Qt think this is a continuous scroll event when it isn't,
// so just ignore it.
return false;
case axis_source_finger:
case axis_source_continuous:
return !delta.isNull();
}
}
QPoint QWaylandInputDevice::Pointer::FrameData::pixelDeltaAndError(QPointF *accumulatedError) const
{
if (!hasPixelDelta())
return QPoint();
Q_ASSERT(accumulatedError);
// Add accumulated rounding error before rounding again
QPoint pixelDelta = (delta + *accumulatedError).toPoint();
*accumulatedError += delta - pixelDelta;
Q_ASSERT(qAbs(accumulatedError->x()) < 1.0);
Q_ASSERT(qAbs(accumulatedError->y()) < 1.0);
return pixelDelta;
}
QPoint QWaylandInputDevice::Pointer::FrameData::angleDelta() const
{
if (discreteDelta.isNull()) {
// If we didn't get any discrete events, then we need to fall back to
// the continuous information.
return (delta * -12).toPoint(); //TODO: why multiply by 12?
}
// The angle delta is in eights of degrees, and our docs says most mice have
// 1 click = 15 degrees. It's also in the opposite direction of surface space.
return -discreteDelta * 15 * 8;
}
Qt::MouseEventSource QWaylandInputDevice::Pointer::FrameData::wheelEventSource() const
{
switch (axisSource) {
case axis_source_wheel_tilt: // sideways tilt of the wheel
case axis_source_wheel:
return Qt::MouseEventNotSynthesized;
case axis_source_finger:
case axis_source_continuous:
default: // Whatever other sources might be added are probably not mouse wheels
return Qt::MouseEventSynthesizedBySystem;
}
}
void QWaylandInputDevice::Pointer::flushScrollEvent()
{
QPoint angleDelta = mFrameData.angleDelta();
// Angle delta is required for Qt wheel events, so don't try to send events if it's zero
if (!angleDelta.isNull()) {
QWaylandWindow *target = QWaylandWindow::mouseGrab();
if (!target)
target = mFocus;
if (isDefinitelyTerminated(mFrameData.axisSource) && !mScrollBeginSent) {
qCDebug(lcQpaWaylandInput) << "Flushing scroll event sending ScrollBegin";
target->handleMouse(mParent, WheelEvent(mFocus, Qt::ScrollBegin, mParent->mTime,
mSurfacePos, mGlobalPos, QPoint(), QPoint(),
Qt::MouseEventNotSynthesized,
mParent->modifiers()));
mScrollBeginSent = true;
mScrollDeltaRemainder = QPointF();
}
Qt::ScrollPhase phase = mScrollBeginSent ? Qt::ScrollUpdate : Qt::NoScrollPhase;
QPoint pixelDelta = mFrameData.pixelDeltaAndError(&mScrollDeltaRemainder);
Qt::MouseEventSource source = mFrameData.wheelEventSource();
qCDebug(lcQpaWaylandInput) << "Flushing scroll event" << phase << pixelDelta << angleDelta;
target->handleMouse(mParent, WheelEvent(mFocus, phase, mParent->mTime, mSurfacePos, mGlobalPos,
pixelDelta, angleDelta, source, mParent->modifiers()));
}
mFrameData.resetScrollData();
}
void QWaylandInputDevice::Pointer::flushFrameEvent()
{
if (mFrameData.event) {
mFrameData.event->surface->handleMouse(mParent, *mFrameData.event);
delete mFrameData.event;
mFrameData.event = nullptr;
}
//TODO: do modifiers get passed correctly here?
flushScrollEvent();
}
bool QWaylandInputDevice::Pointer::isDefinitelyTerminated(QtWayland::wl_pointer::axis_source source) const
{
return source == axis_source_finger;
}
void QWaylandInputDevice::Keyboard::keyboard_keymap(uint32_t format, int32_t fd, uint32_t size)

View File

@ -262,6 +262,10 @@ public:
void pointer_axis(uint32_t time,
uint32_t axis,
wl_fixed_t value) override;
void pointer_axis_source(uint32_t source) override;
void pointer_axis_stop(uint32_t time, uint32_t axis) override;
void pointer_axis_discrete(uint32_t axis, int32_t value) override;
void pointer_frame() override;
void releaseButtons();
@ -278,6 +282,30 @@ public:
wl_buffer *mCursorBuffer = nullptr;
Qt::CursorShape mCursorShape = Qt::BitmapCursor;
#endif
struct FrameData {
QWaylandPointerEvent *event = nullptr;
QPointF delta;
QPoint discreteDelta;
axis_source axisSource = axis_source_wheel;
void resetScrollData();
bool hasPixelDelta() const;
QPoint pixelDeltaAndError(QPointF *accumulatedError) const;
QPoint pixelDelta() const { return hasPixelDelta() ? delta.toPoint() : QPoint(); }
QPoint angleDelta() const;
Qt::MouseEventSource wheelEventSource() const;
} mFrameData;
bool mScrollBeginSent = false;
QPointF mScrollDeltaRemainder;
void setFrameEvent(QWaylandPointerEvent *event);
void flushScrollEvent();
void flushFrameEvent();
private: //TODO: should other methods be private as well?
bool isDefinitelyTerminated(axis_source source) const;
};
class Q_WAYLAND_CLIENT_EXPORT QWaylandInputDevice::Touch : public QtWayland::wl_touch
@ -313,38 +341,58 @@ public:
class QWaylandPointerEvent
{
Q_GADGET
public:
enum Type {
Enter,
Leave,
Motion,
Press,
Release,
Wheel
};
inline QWaylandPointerEvent(Type t, ulong ts, const QPointF &l, const QPointF &g, Qt::MouseButtons b, Qt::KeyboardModifiers m)
: type(t)
, timestamp(ts)
, local(l)
, global(g)
, buttons(b)
, modifiers(m)
Q_ENUM(Type)
inline QWaylandPointerEvent(Type type, Qt::ScrollPhase phase, QWaylandWindow *surface,
ulong timestamp, const QPointF &localPos, const QPointF &globalPos,
Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers)
: type(type)
, phase(phase)
, timestamp(timestamp)
, local(localPos)
, global(globalPos)
, buttons(buttons)
, modifiers(modifiers)
, surface(surface)
{}
inline QWaylandPointerEvent(Type t, ulong ts, const QPointF &l, const QPointF &g, const QPoint &pd, const QPoint &ad, Qt::KeyboardModifiers m)
: type(t)
, timestamp(ts)
, local(l)
, global(g)
, modifiers(m)
, pixelDelta(pd)
, angleDelta(ad)
inline QWaylandPointerEvent(Type type, Qt::ScrollPhase phase, QWaylandWindow *surface,
ulong timestamp, const QPointF &local, const QPointF &global,
const QPoint &pixelDelta, const QPoint &angleDelta,
Qt::MouseEventSource source,
Qt::KeyboardModifiers modifiers)
: type(type)
, phase(phase)
, timestamp(timestamp)
, local(local)
, global(global)
, modifiers(modifiers)
, pixelDelta(pixelDelta)
, angleDelta(angleDelta)
, source(source)
, surface(surface)
{}
Type type;
ulong timestamp;
Qt::ScrollPhase phase = Qt::NoScrollPhase;
ulong timestamp = 0;
QPointF local;
QPointF global;
Qt::MouseButtons buttons;
Qt::KeyboardModifiers modifiers;
QPoint pixelDelta;
QPoint angleDelta;
Qt::MouseEventSource source = Qt::MouseEventNotSynthesized;
QWaylandWindow *surface = nullptr;
};
}

View File

@ -846,6 +846,18 @@ QWaylandWindow *QWaylandWindow::transientParent() const
void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e)
{
if (e.type == QWaylandPointerEvent::Leave) {
if (mWindowDecoration) {
if (mMouseEventsInContentArea)
QWindowSystemInterface::handleLeaveEvent(window());
} else {
QWindowSystemInterface::handleLeaveEvent(window());
}
#if QT_CONFIG(cursor)
restoreMouseCursor(inputDevice);
#endif
return;
}
if (mWindowDecoration) {
handleMouseEventWithDecoration(inputDevice, e);
@ -854,11 +866,15 @@ void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylan
case QWaylandPointerEvent::Enter:
QWindowSystemInterface::handleEnterEvent(window(), e.local, e.global);
break;
case QWaylandPointerEvent::Press:
case QWaylandPointerEvent::Release:
case QWaylandPointerEvent::Motion:
QWindowSystemInterface::handleMouseEvent(window(), e.timestamp, e.local, e.global, e.buttons, e.modifiers);
break;
case QWaylandPointerEvent::Wheel:
QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, e.local, e.global, e.pixelDelta, e.angleDelta, e.modifiers);
QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, e.local, e.global,
e.pixelDelta, e.angleDelta, e.modifiers,
e.phase, e.source, false);
break;
}
}
@ -872,20 +888,6 @@ void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylan
#endif
}
void QWaylandWindow::handleMouseLeave(QWaylandInputDevice *inputDevice)
{
if (mWindowDecoration) {
if (mMouseEventsInContentArea) {
QWindowSystemInterface::handleLeaveEvent(window());
}
} else {
QWindowSystemInterface::handleLeaveEvent(window());
}
#if QT_CONFIG(cursor)
restoreMouseCursor(inputDevice);
#endif
}
bool QWaylandWindow::touchDragDecoration(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, Qt::TouchPointState state, Qt::KeyboardModifiers mods)
{
if (!mWindowDecoration)
@ -927,12 +929,18 @@ void QWaylandWindow::handleMouseEventWithDecoration(QWaylandInputDevice *inputDe
case QWaylandPointerEvent::Enter:
QWindowSystemInterface::handleEnterEvent(window(), localTranslated, globalTranslated);
break;
case QWaylandPointerEvent::Press:
case QWaylandPointerEvent::Release:
case QWaylandPointerEvent::Motion:
QWindowSystemInterface::handleMouseEvent(window(), e.timestamp, localTranslated, globalTranslated, e.buttons, e.modifiers);
break;
case QWaylandPointerEvent::Wheel:
QWindowSystemInterface::handleWheelEvent(window(), e.timestamp, localTranslated, globalTranslated, e.pixelDelta, e.angleDelta, e.modifiers);
case QWaylandPointerEvent::Wheel: {
QWindowSystemInterface::handleWheelEvent(window(), e.timestamp,
localTranslated, globalTranslated,
e.pixelDelta, e.angleDelta, e.modifiers,
e.phase, e.source, false);
break;
}
}
mMouseEventsInContentArea = true;

View File

@ -158,7 +158,6 @@ public:
QWaylandAbstractDecoration *decoration() const;
void handleMouse(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e);
void handleMouseLeave(QWaylandInputDevice *inputDevice);
bool touchDragDecoration(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global,
Qt::TouchPointState state, Qt::KeyboardModifiers mods);

View File

@ -7,6 +7,7 @@ SUBDIRS += \
iviapplication \
output \
seatv4 \
seatv5 \
surface \
wl_connect \
xdgoutput \

View File

@ -0,0 +1,4 @@
include (../shared/shared.pri)
TARGET = tst_seatv5
SOURCES += tst_seatv5.cpp

View File

@ -0,0 +1,387 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "mockcompositor.h"
#include <QtGui/QRasterWindow>
#include <QtGui/QOpenGLWindow>
using namespace MockCompositor;
class SeatV5Compositor : public DefaultCompositor {
public:
explicit SeatV5Compositor()
{
exec([this] {
m_config.autoConfigure = true;
removeAll<Seat>();
uint capabilities = MockCompositor::Seat::capability_pointer;
int version = 5;
add<Seat>(capabilities, version);
});
}
Pointer *pointer()
{
auto *seat = get<Seat>();
Q_ASSERT(seat);
return seat->m_pointer;
}
};
class tst_seatv5 : public QObject, private SeatV5Compositor
{
Q_OBJECT
private slots:
void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); }
void bindsToSeat();
void createsPointer();
void setsCursorOnEnter();
void usesEnterSerial();
void simpleAxis_data();
void simpleAxis();
void fingerScroll();
void fingerScrollSlow();
void wheelDiscreteScroll();
};
void tst_seatv5::bindsToSeat()
{
QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().size(), 1);
QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().first()->version(), 5);
}
void tst_seatv5::createsPointer()
{
QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().size(), 1);
QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 5);
}
void tst_seatv5::setsCursorOnEnter()
{
QRasterWindow window;
window.resize(64, 64);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *surface = xdgSurface()->m_surface;
pointer()->sendEnter(surface, {0, 0});
pointer()->sendFrame(surface->resource()->client());
});
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
}
void tst_seatv5::usesEnterSerial()
{
QSignalSpy setCursorSpy(exec([=] { return pointer(); }), &Pointer::setCursor);
QRasterWindow window;
window.resize(64, 64);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
uint enterSerial = exec([=] {
return pointer()->sendEnter(xdgSurface()->m_surface, {0, 0});
});
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
QTRY_COMPARE(setCursorSpy.count(), 1);
QCOMPARE(setCursorSpy.takeFirst().at(0).toUInt(), enterSerial);
}
class WheelWindow : QRasterWindow {
public:
WheelWindow()
{
resize(64, 64);
show();
}
void wheelEvent(QWheelEvent *event) override
{
QRasterWindow::wheelEvent(event);
// qDebug() << event << "angleDelta" << event->angleDelta() << "pixelDelta" << event->pixelDelta();
if (event->phase() == Qt::ScrollUpdate || event->phase() == Qt::NoScrollPhase) {
// Angle delta should always be provided (says docs, but QPA sends compatibility events
// for Qt4 with zero angleDelta, and with a delta)
QVERIFY(!event->angleDelta().isNull() || event->delta());
} else {
// Shouldn't have deltas in the other phases
QCOMPARE(event->angleDelta(), QPoint(0, 0));
QCOMPARE(event->pixelDelta(), QPoint(0, 0));
}
// The axis vector of the event is already in surface space, so there is now way to tell
// whether it is inverted or not.
QCOMPARE(event->inverted(), false);
// We didn't press any buttons
QCOMPARE(event->buttons(), Qt::NoButton);
if (!event->angleDelta().isNull()) {
if (event->orientation() == Qt::Horizontal)
QCOMPARE(event->delta(), event->angleDelta().x());
else
QCOMPARE(event->delta(), event->angleDelta().y());
}
m_events.append(Event{event});
}
struct Event // Because I didn't find a convenient way to copy it entirely
{
explicit Event() = default;
explicit Event(const QWheelEvent *event)
: phase(event->phase())
, pixelDelta(event->pixelDelta())
, angleDelta(event->angleDelta())
, orientation(event->orientation())
, source(event->source())
{
}
const Qt::ScrollPhase phase{};
const QPoint pixelDelta;
const QPoint angleDelta; // eights of a degree, positive is upwards, left
const Qt::Orientation orientation{};
const Qt::MouseEventSource source{};
};
QVector<Event> m_events;
};
void tst_seatv5::simpleAxis_data()
{
QTest::addColumn<uint>("axis");
QTest::addColumn<qreal>("value");
QTest::addColumn<Qt::Orientation>("orientation");
QTest::addColumn<QPoint>("angleDelta");
// Directions in regular windows/linux terms (no "natural" scrolling)
QTest::newRow("down") << uint(Pointer::axis_vertical_scroll) << 1.0 << Qt::Vertical << QPoint{0, -12};
QTest::newRow("up") << uint(Pointer::axis_vertical_scroll) << -1.0 << Qt::Vertical << QPoint{0, 12};
QTest::newRow("left") << uint(Pointer::axis_horizontal_scroll) << 1.0 << Qt::Horizontal << QPoint{-12, 0};
QTest::newRow("right") << uint(Pointer::axis_horizontal_scroll) << -1.0 << Qt::Horizontal << QPoint{12, 0};
QTest::newRow("up big") << uint(Pointer::axis_vertical_scroll) << -10.0 << Qt::Vertical << QPoint{0, 120};
}
void tst_seatv5::simpleAxis()
{
QFETCH(uint, axis);
QFETCH(qreal, value);
QFETCH(Qt::Orientation, orientation);
QFETCH(QPoint, angleDelta);
WheelWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *p = pointer();
p->sendEnter(xdgToplevel()->surface(), {32, 32});
p->sendFrame(client());
p->sendAxis(
client(),
Pointer::axis(axis),
value // Length of vector in surface-local space. i.e. positive is downwards
);
p->sendFrame(client());
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::NoScrollPhase);
// Pixel delta should only be set if we know it's a high-res input device (which we don't)
QCOMPARE(e.pixelDelta, QPoint(0, 0));
// There has been no information about what created the event.
// Documentation says not synthesized is appropriate in such cases
QCOMPARE(e.source, Qt::MouseEventNotSynthesized);
QCOMPARE(e.orientation, orientation);
QCOMPARE(e.angleDelta, angleDelta);
}
// Sending axis_stop is not mandatory when axis source != finger
}
void tst_seatv5::fingerScroll()
{
WheelWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *p = pointer();
auto *c = client();
p->sendEnter(xdgToplevel()->surface(), {32, 32});
p->sendFrame(c);
p->sendAxisSource(c, Pointer::axis_source_finger);
p->sendAxis(c, Pointer::axis_vertical_scroll, 10);
p->sendFrame(c);
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::ScrollBegin);
QCOMPARE(e.angleDelta, QPoint());
QCOMPARE(e.pixelDelta, QPoint());
}
QTRY_VERIFY(!window.m_events.empty());
// For some reason we send two ScrollBegins, one for each direction, not sure if this is really
// necessary, (could be removed from QtBase, hence the conditional below.
if (window.m_events.first().phase == Qt::ScrollBegin) {
auto e = window.m_events.takeFirst();
QCOMPARE(e.angleDelta, QPoint());
QCOMPARE(e.pixelDelta, QPoint());
}
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::ScrollUpdate);
QCOMPARE(e.orientation, Qt::Vertical);
// QCOMPARE(e.angleDelta, angleDelta); // TODO: what should this be?
QCOMPARE(e.pixelDelta, QPoint(0, 10));
QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel
}
QTRY_VERIFY(window.m_events.empty());
// Scroll horizontally as well
exec([=] {
pointer()->sendAxisSource(client(), Pointer::axis_source_finger);
pointer()->sendAxis(client(), Pointer::axis_horizontal_scroll, 10);
pointer()->sendFrame(client());
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::ScrollUpdate);
QCOMPARE(e.orientation, Qt::Horizontal);
QCOMPARE(e.pixelDelta, QPoint(10, 0));
QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel
}
// Scroll diagonally
exec([=] {
pointer()->sendAxisSource(client(), Pointer::axis_source_finger);
pointer()->sendAxis(client(), Pointer::axis_horizontal_scroll, 10);
pointer()->sendAxis(client(), Pointer::axis_vertical_scroll, 10);
pointer()->sendFrame(client());
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::ScrollUpdate);
QCOMPARE(e.pixelDelta, QPoint(10, 10));
QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel
}
// For diagonal events, Qt sends an additional compatibility ScrollUpdate event
if (window.m_events.first().phase == Qt::ScrollUpdate) {
auto e = window.m_events.takeFirst();
QCOMPARE(e.angleDelta, QPoint());
QCOMPARE(e.pixelDelta, QPoint());
}
QVERIFY(window.m_events.empty());
// Sending axis_stop is mandatory when axis source == finger
exec([=] {
pointer()->sendAxisStop(client(), Pointer::axis_vertical_scroll);
pointer()->sendFrame(client());
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::ScrollEnd);
}
}
void tst_seatv5::fingerScrollSlow()
{
WheelWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *p = pointer();
auto *c = client();
p->sendEnter(xdgToplevel()->surface(), {32, 32});
p->sendFrame(c);
// Send 10 really small updates
for (int i = 0; i < 10; ++i) {
p->sendAxisSource(c, Pointer::axis_source_finger);
p->sendAxis(c, Pointer::axis_vertical_scroll, 0.1);
p->sendFrame(c);
}
p->sendAxisStop(c, Pointer::axis_vertical_scroll);
p->sendFrame(c);
});
QTRY_VERIFY(!window.m_events.empty());
QPoint accumulated;
while (window.m_events.first().phase != Qt::ScrollEnd) {
auto e = window.m_events.takeFirst();
accumulated += e.pixelDelta;
QTRY_VERIFY(!window.m_events.empty());
}
QCOMPARE(accumulated.y(), 1);
}
void tst_seatv5::wheelDiscreteScroll()
{
WheelWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *p = pointer();
auto *c = client();
p->sendEnter(xdgToplevel()->surface(), {32, 32});
p->sendFrame(c);
p->sendAxisSource(c, Pointer::axis_source_wheel);
p->sendAxisDiscrete(c, Pointer::axis_vertical_scroll, 1); // 1 click downwards
p->sendAxis(c, Pointer::axis_vertical_scroll, 1.0);
p->sendFrame(c);
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::NoScrollPhase);
QCOMPARE(e.orientation, Qt::Vertical);
// According to the docs the angle delta is in eights of a degree and most mice have
// 1 click = 15 degrees. The angle delta should therefore be:
// 15 degrees / (1/8 eights per degrees) = 120 eights of degrees.
QCOMPARE(e.angleDelta, QPoint(0, -120));
// Click scrolls are not continuous and should not have a pixel delta
QCOMPARE(e.pixelDelta, QPoint(0, 0));
}
}
QCOMPOSITOR_TEST_MAIN(tst_seatv5)
#include "tst_seatv5.moc"

View File

@ -312,6 +312,39 @@ void Pointer::sendAxis(wl_client *client, axis axis, qreal value)
send_axis(r->handle, time, axis, val);
}
void Pointer::sendAxisDiscrete(wl_client *client, QtWaylandServer::wl_pointer::axis axis, int discrete)
{
// TODO: assert v5 or newer
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_axis_discrete(r->handle, axis, discrete);
}
void Pointer::sendAxisSource(wl_client *client, QtWaylandServer::wl_pointer::axis_source source)
{
// TODO: assert v5 or newer
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_axis_source(r->handle, source);
}
void Pointer::sendAxisStop(wl_client *client, QtWaylandServer::wl_pointer::axis axis)
{
// TODO: assert v5 or newer
auto time = m_seat->m_compositor->currentTimeMilliseconds();
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_axis_stop(r->handle, time, axis);
}
void Pointer::sendFrame(wl_client *client)
{
//TODO: assert version 5 or newer?
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
send_frame(r->handle);
}
void Pointer::pointer_set_cursor(Resource *resource, uint32_t serial, wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y)
{
Q_UNUSED(resource);

View File

@ -282,6 +282,10 @@ public:
void sendMotion(wl_client *client, const QPointF &position);
uint sendButton(wl_client *client, uint button, uint state);
void sendAxis(wl_client *client, axis axis, qreal value);
void sendAxisDiscrete(wl_client *client, axis axis, int discrete);
void sendAxisSource(wl_client *client, axis_source source);
void sendAxisStop(wl_client *client, axis axis);
void sendFrame(wl_client *client);
Seat *m_seat = nullptr;
uint m_enterSerial = 0;