Client: Register QPointingDevices for graphics tablet devices

Followup to 4d844d36404597713a63879dc24f051de0ad3e5b

Reference material: https://wayland.app/protocols/tablet-unstable-v2

It's not clear how to get the number of buttons on a stylus.

QWaylandTabletToolV2::zwp_tablet_tool_v2_removed() is called when a
stylus leaves proximity; but we avoid deleting the QWaylandTabletToolV2
instance too soon, because a proximity leave event may be in flight at
the same time, carrying a pointer to the stylus device. Instead, each
"dead" tool is deleted when another enters. The tablet protocol makes it
difficult to retain the same instances indefinitely, as we would
otherwise do: the zwp_tablet_tool_v2 is constructed elsewhere, a pointer
is given to the QWaylandTabletToolV2 ctor and retained. So a "dead" tool
may hold a dangling pointer; and at the time a new QWaylandTabletToolV2
instance is constructed, its details are not yet known, so it would not
be possible to look up and reuse an existing matching instance (as we
would otherwise probably do).

Task-number: QTBUG-72167
Task-number: QTBUG-112432
Task-number: QTBUG-115207
Pick-to: 6.8
Change-Id: I3423a0d0bd2aae5be4e61cc60da8c3362a80d10e
Reviewed-by: Liang Qi <liang.qi@qt.io>
This commit is contained in:
Shawn Rutledge 2023-07-12 14:22:44 +02:00
parent e168e1fc80
commit bb15139024
4 changed files with 219 additions and 86 deletions

View File

@ -385,6 +385,7 @@ QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version,
: QtWayland::wl_seat(display->wl_registry(), id, qMin(version, 9))
, mQDisplay(display)
, mDisplay(display->wl_display())
, mId(id)
{
#if QT_CONFIG(wayland_datadevice)
if (mQDisplay->dndSelectionHandler()) {

View File

@ -83,6 +83,7 @@ public:
QWaylandInputDevice(QWaylandDisplay *display, int version, uint32_t id);
~QWaylandInputDevice() override;
uint32_t id() const { return mId; }
uint32_t capabilities() const { return mCaps; }
QString seatname() const { return mSeatName; }
@ -144,6 +145,7 @@ protected:
QWaylandDisplay *mQDisplay = nullptr;
struct wl_display *mDisplay = nullptr;
uint32_t mId = -1;
uint32_t mCaps = 0;
QString mSeatName;

View File

@ -5,14 +5,21 @@
#include "qwaylandinputdevice_p.h"
#include "qwaylanddisplay_p.h"
#include "qwaylandsurface_p.h"
#include <QtGui/private/qpointingdevice_p.h>
QT_BEGIN_NAMESPACE
namespace QtWaylandClient {
using namespace Qt::StringLiterals;
Q_LOGGING_CATEGORY(lcQpaInputDevices, "qt.qpa.input.devices")
Q_DECLARE_LOGGING_CATEGORY(lcQpaWaylandInput)
QWaylandTabletManagerV2::QWaylandTabletManagerV2(QWaylandDisplay *display, uint id, uint version)
: zwp_tablet_manager_v2(display->wl_registry(), id, qMin(version, uint(1)))
{
qCDebug(lcQpaInputDevices, "new tablet manager: ID %d version %d", id, version);
}
QWaylandTabletManagerV2::~QWaylandTabletManagerV2()
@ -24,6 +31,7 @@ QWaylandTabletSeatV2::QWaylandTabletSeatV2(QWaylandTabletManagerV2 *manager, QWa
: QtWayland::zwp_tablet_seat_v2(manager->get_tablet_seat(seat->wl_seat()))
, m_seat(seat)
{
qCDebug(lcQpaInputDevices) << "new tablet seat" << seat->seatname() << "id" << seat->id();
}
QWaylandTabletSeatV2::~QWaylandTabletSeatV2()
@ -36,108 +44,205 @@ QWaylandTabletSeatV2::~QWaylandTabletSeatV2()
pad->destroy();
qDeleteAll(m_tablets);
qDeleteAll(m_tools);
qDeleteAll(m_deadTools);
qDeleteAll(m_pads);
destroy();
}
void QWaylandTabletSeatV2::zwp_tablet_seat_v2_tablet_added(zwp_tablet_v2 *id)
{
auto *tablet = new QWaylandTabletV2(id);
auto *tablet = new QWaylandTabletV2(id, m_seat->seatname());
qCDebug(lcQpaInputDevices) << "seat" << this << id << "has tablet" << tablet;
tablet->setParent(this);
m_tablets.push_back(tablet);
connect(tablet, &QWaylandTabletV2::destroyed, this, [this, tablet] { m_tablets.removeOne(tablet); });
}
void QWaylandTabletSeatV2::zwp_tablet_seat_v2_tool_added(zwp_tablet_tool_v2 *id)
{
qDeleteAll(m_deadTools);
auto *tool = new QWaylandTabletToolV2(this, id);
if (m_tablets.size() == 1) {
tool->setParent(m_tablets.first());
QPointingDevicePrivate *d = QPointingDevicePrivate::get(tool);
d->name = m_tablets.first()->name() + u" stylus";
} else {
qCDebug(lcQpaInputDevices) << "seat" << this << "has tool" << tool << "for one of these tablets:" << m_tablets;
// TODO identify which tablet if there are several; then tool->setParent(tablet)
}
m_tools.push_back(tool);
connect(tool, &QWaylandTabletToolV2::destroyed, this, [this, tool] { m_tools.removeOne(tool); });
connect(tool, &QWaylandTabletToolV2::destroyed, this, [this, tool] {
m_tools.removeOne(tool);
m_deadTools.removeOne(tool);
});
}
void QWaylandTabletSeatV2::zwp_tablet_seat_v2_pad_added(zwp_tablet_pad_v2 *id)
{
auto *pad = new QWaylandTabletPadV2(id);
if (m_tablets.size() == 1) {
pad->setParent(m_tablets.first());
QPointingDevicePrivate *d = QPointingDevicePrivate::get(pad);
d->name = m_tablets.first()->name() + u" touchpad";
} else {
qCDebug(lcQpaInputDevices) << "seat" << this << "has touchpad" << pad << "for one of these tablets:" << m_tablets;
// TODO identify which tablet if there are several
}
m_pads.push_back(pad);
connect(pad, &QWaylandTabletPadV2::destroyed, this, [this, pad] { m_pads.removeOne(pad); });
}
QWaylandTabletV2::QWaylandTabletV2(::zwp_tablet_v2 *tablet)
: QtWayland::zwp_tablet_v2(tablet)
QWaylandTabletV2::QWaylandTabletV2(::zwp_tablet_v2 *tablet, const QString &seatName)
: QPointingDevice(u"unknown"_s, -1, DeviceType::Stylus, PointerType::Pen,
Capability::Position | Capability::Hover,
1, 1)
, QtWayland::zwp_tablet_v2(tablet)
{
qCDebug(lcQpaInputDevices) << "new tablet on seat" << seatName;
QPointingDevicePrivate *d = QPointingDevicePrivate::get(this);
d->seatName = seatName;
}
void QWaylandTabletV2::zwp_tablet_v2_name(const QString &name)
{
QPointingDevicePrivate *d = QPointingDevicePrivate::get(this);
d->name = name;
}
void QWaylandTabletV2::zwp_tablet_v2_id(uint32_t vid, uint32_t pid)
{
QPointingDevicePrivate *d = QPointingDevicePrivate::get(this);
d->systemId = (quint64(vid) << 32) | pid;
qCDebug(lcQpaInputDevices) << "vid" << vid << "pid" << pid << "stored as systemId in" << this;
}
void QWaylandTabletV2::zwp_tablet_v2_path(const QString &path)
{
QPointingDevicePrivate *d = QPointingDevicePrivate::get(this);
d->busId = path;
}
void QWaylandTabletV2::zwp_tablet_v2_done()
{
QWindowSystemInterface::registerInputDevice(this);
}
void QWaylandTabletSeatV2::toolRemoved(QWaylandTabletToolV2 *tool)
{
m_tools.removeOne(tool);
m_deadTools.append(tool);
}
void QWaylandTabletV2::zwp_tablet_v2_removed()
{
destroy();
delete this;
deleteLater();
}
QWaylandTabletToolV2::QWaylandTabletToolV2(QWaylandTabletSeatV2 *tabletSeat, ::zwp_tablet_tool_v2 *tool)
: QtWayland::zwp_tablet_tool_v2(tool)
: QPointingDevice(u"tool"_s, -1, DeviceType::Stylus, PointerType::Pen,
Capability::Position | Capability::Hover,
1, 1)
, QtWayland::zwp_tablet_tool_v2(tool)
, m_tabletSeat(tabletSeat)
{
// TODO get the number of buttons somehow?
}
void QWaylandTabletToolV2::zwp_tablet_tool_v2_type(uint32_t tool_type)
{
m_toolType = type(tool_type);
QPointingDevicePrivate *d = QPointingDevicePrivate::get(this);
switch (tool_type) {
case type_airbrush:
case type_brush:
case type_pencil:
case type_pen:
d->pointerType = QPointingDevice::PointerType::Pen;
break;
case type_eraser:
d->pointerType = QPointingDevice::PointerType::Eraser;
break;
case type_mouse:
case type_lens:
d->pointerType = QPointingDevice::PointerType::Cursor;
break;
case type_finger:
d->pointerType = QPointingDevice::PointerType::Unknown;
break;
}
switch (tool_type) {
case type::type_airbrush:
d->deviceType = QInputDevice::DeviceType::Airbrush;
d->capabilities.setFlag(QInputDevice::Capability::TangentialPressure);
break;
case type::type_brush:
case type::type_pencil:
case type::type_pen:
case type::type_eraser:
d->deviceType = QInputDevice::DeviceType::Stylus;
break;
case type::type_lens:
d->deviceType = QInputDevice::DeviceType::Puck;
break;
case type::type_mouse:
case type::type_finger:
d->deviceType = QInputDevice::DeviceType::Unknown;
break;
}
}
void QWaylandTabletToolV2::zwp_tablet_tool_v2_hardware_serial(uint32_t hardware_serial_hi, uint32_t hardware_serial_lo)
{
m_uid = (quint64(hardware_serial_hi) << 32) + hardware_serial_lo;
QPointingDevicePrivate *d = QPointingDevicePrivate::get(this);
d->uniqueId = QPointingDeviceUniqueId::fromNumericId((quint64(hardware_serial_hi) << 32) + hardware_serial_lo);
}
void QWaylandTabletToolV2::zwp_tablet_tool_v2_hardware_id_wacom(uint32_t hardware_id_hi, uint32_t hardware_id_lo)
{
QPointingDevicePrivate *d = QPointingDevicePrivate::get(this);
d->systemId = (quint64(hardware_id_hi) << 32) + hardware_id_lo;
}
void QWaylandTabletToolV2::zwp_tablet_tool_v2_capability(uint32_t capability)
{
if (capability == capability_rotation)
m_hasRotation = true;
QPointingDevicePrivate *d = QPointingDevicePrivate::get(this);
switch (capability) {
case capability_tilt:
// no distinction... we have to assume it has both axes
d->capabilities.setFlag(QInputDevice::Capability::XTilt);
d->capabilities.setFlag(QInputDevice::Capability::YTilt);
break;
case capability_pressure:
d->capabilities.setFlag(QInputDevice::Capability::Pressure);
break;
case capability_distance:
d->capabilities.setFlag(QInputDevice::Capability::ZPosition);
break;
case capability_rotation:
d->capabilities.setFlag(QInputDevice::Capability::Rotation);
break;
case capability_slider:
// nothing to represent that so far
break;
case capability_wheel:
d->capabilities.setFlag(QInputDevice::Capability::Scroll);
d->capabilities.setFlag(QInputDevice::Capability::PixelScroll);
break;
}
qCDebug(lcQpaInputDevices) << capability << "->" << this;
}
void QWaylandTabletToolV2::zwp_tablet_tool_v2_done()
{
switch (m_toolType) {
case type::type_airbrush:
case type::type_brush:
case type::type_pencil:
case type::type_pen:
m_pointerType = QPointingDevice::PointerType::Pen;
break;
case type::type_eraser:
m_pointerType = QPointingDevice::PointerType::Eraser;
break;
case type::type_mouse:
case type::type_lens:
m_pointerType = QPointingDevice::PointerType::Cursor;
break;
case type::type_finger:
m_pointerType = QPointingDevice::PointerType::Unknown;
break;
}
switch (m_toolType) {
case type::type_airbrush:
m_tabletDevice = QInputDevice::DeviceType::Airbrush;
break;
case type::type_brush:
case type::type_pencil:
case type::type_pen:
case type::type_eraser:
m_tabletDevice = QInputDevice::DeviceType::Stylus;
break;
case type::type_lens:
m_tabletDevice = QInputDevice::DeviceType::Puck;
break;
case type::type_mouse:
case type::type_finger:
m_tabletDevice = QInputDevice::DeviceType::Unknown;
break;
}
QWindowSystemInterface::registerInputDevice(this);
}
void QWaylandTabletToolV2::zwp_tablet_tool_v2_removed()
{
destroy();
delete this;
m_tabletSeat->toolRemoved(this);
}
void QWaylandTabletToolV2::zwp_tablet_tool_v2_proximity_in(uint32_t serial, zwp_tablet_v2 *tablet, wl_surface *surface)
@ -226,27 +331,41 @@ void QWaylandTabletToolV2::zwp_tablet_tool_v2_button(uint32_t serial, uint32_t b
{
m_tabletSeat->seat()->mSerial = serial;
QPointingDevicePrivate *d = QPointingDevicePrivate::get(this);
Qt::MouseButton mouseButton = mouseButtonFromTablet(button);
if (state == button_state_pressed)
m_pending.buttons |= mouseButton;
else
m_pending.buttons &= ~mouseButton;
// ideally we'd get button count when the tool is discovered; seems to be a shortcoming in tablet-unstable-v2
// but if we get events from buttons we didn't know existed, increase it
if (mouseButton == Qt::RightButton)
d->buttonCount = qMax(d->buttonCount, 2);
else if (mouseButton == Qt::MiddleButton)
d->buttonCount = qMax(d->buttonCount, 3);
}
void QWaylandTabletToolV2::zwp_tablet_tool_v2_frame(uint32_t time)
{
if (m_pending.proximitySurface && !m_applied.proximitySurface) {
QWindowSystemInterface::handleTabletEnterProximityEvent(int(m_tabletDevice), int(m_pointerType), m_uid);
if (!m_pending.proximitySurface) {
qCWarning(lcQpaWayland) << "Can't send tablet event with no proximity surface, ignoring";
if (m_applied.enteredSurface) {
QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(nullptr, this, false);
m_pending = State(); // Don't leave pressure etc. lying around when we enter the next surface
}
return;
}
QWaylandWindow *waylandWindow = QWaylandWindow::fromWlSurface(m_pending.proximitySurface->object());
QWindow *window = waylandWindow->window();
if (!m_applied.proximitySurface) {
// TODO get position etc. as below
QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, this, true);
m_applied.proximitySurface = m_pending.proximitySurface;
}
if (!(m_pending == m_applied) && m_pending.proximitySurface) {
if (!m_pending.proximitySurface) {
qCWarning(lcQpaWayland) << "Can't send tablet event with no proximity surface, ignoring";
return;
}
QWaylandWindow *waylandWindow = QWaylandWindow::fromWlSurface(m_pending.proximitySurface->object());
QWindow *window = waylandWindow->window();
if (!(m_pending == m_applied)) {
ulong timestamp = time;
const QPointF localPosition = waylandWindow->mapFromWlSurface(m_pending.surfacePosition);
@ -263,15 +382,10 @@ void QWaylandTabletToolV2::zwp_tablet_tool_v2_frame(uint32_t time)
qreal rotation = m_pending.rotation;
int z = int(m_pending.distance);
QWindowSystemInterface::handleTabletEvent(
window, timestamp, localPosition, globalPosition, int(m_tabletDevice),
int(m_pointerType), buttons, pressure, xTilt, yTilt, tangentialPressure, rotation,
z, m_uid, m_tabletSeat->seat()->modifiers());
}
if (!m_pending.proximitySurface && m_applied.enteredSurface) {
QWindowSystemInterface::handleTabletLeaveProximityEvent(int(m_tabletDevice), int(m_pointerType), m_uid);
m_pending = State(); // Don't leave pressure etc. lying around when we enter the next surface
QWindowSystemInterface::handleTabletEvent(window, timestamp, this, localPosition, globalPosition,
buttons, pressure,
xTilt, yTilt, tangentialPressure, rotation, z,
m_tabletSeat->seat()->modifiers());
}
m_applied = m_pending;
@ -294,10 +408,30 @@ bool QWaylandTabletToolV2::State::operator==(const QWaylandTabletToolV2::State &
}
QWaylandTabletPadV2::QWaylandTabletPadV2(::zwp_tablet_pad_v2 *pad)
: QtWayland::zwp_tablet_pad_v2(pad)
: QPointingDevice(u"tablet touchpad"_s, -1, DeviceType::TouchPad, PointerType::Finger,
Capability::Position,
1, 1)
, QtWayland::zwp_tablet_pad_v2(pad)
{
}
void QWaylandTabletPadV2::zwp_tablet_pad_v2_path(const QString &path)
{
QPointingDevicePrivate *d = QPointingDevicePrivate::get(this);
d->busId = path;
}
void QWaylandTabletPadV2::zwp_tablet_pad_v2_buttons(uint32_t buttons)
{
QPointingDevicePrivate *d = QPointingDevicePrivate::get(this);
d->buttonCount = buttons;
}
void QWaylandTabletPadV2::zwp_tablet_pad_v2_done()
{
QWindowSystemInterface::registerInputDevice(this);
}
void QWaylandTabletPadV2::zwp_tablet_pad_v2_removed()
{
destroy();

View File

@ -54,6 +54,8 @@ public:
QWaylandInputDevice *seat() const { return m_seat; }
void toolRemoved(QWaylandTabletToolV2 *tool);
protected:
void zwp_tablet_seat_v2_tablet_added(struct ::zwp_tablet_v2 *id) override;
void zwp_tablet_seat_v2_tool_added(struct ::zwp_tablet_tool_v2 *id) override;
@ -63,24 +65,26 @@ private:
QWaylandInputDevice *m_seat;
QList<QWaylandTabletV2 *> m_tablets;
QList<QWaylandTabletToolV2 *> m_tools;
QList<QWaylandTabletToolV2 *> m_deadTools;
QList<QWaylandTabletPadV2 *> m_pads;
};
class Q_WAYLANDCLIENT_EXPORT QWaylandTabletV2 : public QObject, public QtWayland::zwp_tablet_v2
class Q_WAYLANDCLIENT_EXPORT QWaylandTabletV2 : public QPointingDevice, public QtWayland::zwp_tablet_v2
{
Q_OBJECT
public:
explicit QWaylandTabletV2(::zwp_tablet_v2 *tablet);
explicit QWaylandTabletV2(::zwp_tablet_v2 *tablet, const QString &seatName);
protected:
// void zwp_tablet_v2_name(const QString &name) override;
// void zwp_tablet_v2_id(uint32_t vid, uint32_t pid) override;
// void zwp_tablet_v2_path(const QString &path) override;
// void zwp_tablet_v2_done() override;
// callbacks which act as setters
void zwp_tablet_v2_name(const QString &name) override;
void zwp_tablet_v2_id(uint32_t vid, uint32_t pid) override;
void zwp_tablet_v2_path(const QString &path) override;
void zwp_tablet_v2_done() override;
void zwp_tablet_v2_removed() override;
};
class Q_WAYLANDCLIENT_EXPORT QWaylandTabletToolV2 : public QObject, public QtWayland::zwp_tablet_tool_v2
class Q_WAYLANDCLIENT_EXPORT QWaylandTabletToolV2 : public QPointingDevice, public QtWayland::zwp_tablet_tool_v2
{
Q_OBJECT
public:
@ -89,7 +93,7 @@ public:
protected:
void zwp_tablet_tool_v2_type(uint32_t tool_type) override;
void zwp_tablet_tool_v2_hardware_serial(uint32_t hardware_serial_hi, uint32_t hardware_serial_lo) override;
// void zwp_tablet_tool_v2_hardware_id_wacom(uint32_t hardware_id_hi, uint32_t hardware_id_lo) override;
void zwp_tablet_tool_v2_hardware_id_wacom(uint32_t hardware_id_hi, uint32_t hardware_id_lo) override;
void zwp_tablet_tool_v2_capability(uint32_t capability) override;
void zwp_tablet_tool_v2_done() override;
void zwp_tablet_tool_v2_removed() override;
@ -110,13 +114,6 @@ protected:
private:
QWaylandTabletSeatV2 *m_tabletSeat;
// Static state (sent before done event)
QPointingDevice::PointerType m_pointerType = QPointingDevice::PointerType::Unknown;
QInputDevice::DeviceType m_tabletDevice = QInputDevice::DeviceType::Unknown;
type m_toolType = type_pen;
bool m_hasRotation = false;
quint64 m_uid = 0;
// Accumulated state (applied on frame event)
struct State {
bool down = false;
@ -135,8 +132,7 @@ private:
} m_pending, m_applied;
};
// We don't actually use this, but need to handle the "removed" event to comply with the protocol
class Q_WAYLANDCLIENT_EXPORT QWaylandTabletPadV2 : public QObject, public QtWayland::zwp_tablet_pad_v2
class Q_WAYLANDCLIENT_EXPORT QWaylandTabletPadV2 : public QPointingDevice, public QtWayland::zwp_tablet_pad_v2
{
Q_OBJECT
public:
@ -144,9 +140,9 @@ public:
protected:
// void zwp_tablet_pad_v2_group(struct ::zwp_tablet_pad_group_v2 *pad_group) override;
// void zwp_tablet_pad_v2_path(const QString &path) override;
// void zwp_tablet_pad_v2_buttons(uint32_t buttons) override;
// void zwp_tablet_pad_v2_done() override;
void zwp_tablet_pad_v2_path(const QString &path) override;
void zwp_tablet_pad_v2_buttons(uint32_t buttons) override;
void zwp_tablet_pad_v2_done() override;
// void zwp_tablet_pad_v2_button(uint32_t time, uint32_t button, uint32_t state) override;
// void zwp_tablet_pad_v2_enter(uint32_t serial, struct ::zwp_tablet_v2 *tablet, struct ::wl_surface *surface) override;
// void zwp_tablet_pad_v2_leave(uint32_t serial, struct ::wl_surface *surface) override;