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:
parent
7f21a684f7
commit
df531a6f3c
@ -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)
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -7,6 +7,7 @@ SUBDIRS += \
|
||||
iviapplication \
|
||||
output \
|
||||
seatv4 \
|
||||
seatv5 \
|
||||
surface \
|
||||
wl_connect \
|
||||
xdgoutput \
|
||||
|
4
tests/auto/wayland/seatv5/seatv5.pro
Normal file
4
tests/auto/wayland/seatv5/seatv5.pro
Normal file
@ -0,0 +1,4 @@
|
||||
include (../shared/shared.pri)
|
||||
|
||||
TARGET = tst_seatv5
|
||||
SOURCES += tst_seatv5.cpp
|
387
tests/auto/wayland/seatv5/tst_seatv5.cpp
Normal file
387
tests/auto/wayland/seatv5/tst_seatv5.cpp
Normal 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"
|
@ -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);
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user