Move tablet tool cursor tracking into TabletInputRedirection

It belongs in the TabletInputRedirection class, not in an event filter.

Because of that, the event filters are expected to pass the tool events
down the chain in order to update the cursor position, but there are
cases where we **must** intercept tool events.
wilder/Plasma/6.3
Vlad Zahorodnii 2 years ago
parent f22a04171b
commit 4681e9819f
  1. 247
      src/input.cpp
  2. 237
      src/tablet_input.cpp
  3. 8
      src/tablet_input.h

@ -2070,57 +2070,6 @@ public:
}
};
static SeatInterface *findSeat()
{
auto server = waylandServer();
if (!server) {
return nullptr;
}
return server->seat();
}
class SurfaceCursor : public Cursor
{
public:
explicit SurfaceCursor(TabletToolV2Interface *tool)
: Cursor()
{
setParent(tool);
connect(tool, &TabletToolV2Interface::cursorChanged, this, [this](const TabletCursorSourceV2 &cursor) {
if (auto surfaceCursor = std::get_if<TabletSurfaceCursorV2 *>(&cursor)) {
// If the cursor is unset, fallback to the cross cursor.
if ((*surfaceCursor) && (*surfaceCursor)->enteredSerial()) {
if (!m_surfaceSource) {
m_surfaceSource = std::make_unique<SurfaceCursorSource>();
}
m_surfaceSource->update((*surfaceCursor)->surface(), (*surfaceCursor)->hotspot());
setSource(m_surfaceSource.get());
return;
}
}
QByteArray shape;
if (auto shapeCursor = std::get_if<QByteArray>(&cursor)) {
shape = *shapeCursor;
} else {
shape = QByteArrayLiteral("cross");
}
static WaylandCursorImage defaultCursor;
if (!m_shapeSource) {
m_shapeSource = std::make_unique<ShapeCursorSource>();
}
m_shapeSource->setTheme(defaultCursor.theme());
m_shapeSource->setShape(shape);
setSource(m_shapeSource.get());
});
}
private:
std::unique_ptr<ShapeCursorSource> m_shapeSource;
std::unique_ptr<SurfaceCursorSource> m_surfaceSource;
};
/**
* Handles input coming from a tablet device (e.g. wacom) often with a pen
*/
@ -2130,172 +2079,6 @@ public:
TabletInputFilter()
: InputEventFilter(InputFilterOrder::Tablet)
{
const auto devices = input()->devices();
for (InputDevice *device : devices) {
integrateDevice(device);
}
connect(input(), &InputRedirection::deviceAdded, this, &TabletInputFilter::integrateDevice);
connect(input(), &InputRedirection::deviceRemoved, this, &TabletInputFilter::removeDevice);
auto tabletNextOutput = new QAction(this);
tabletNextOutput->setProperty("componentName", QStringLiteral("kwin"));
tabletNextOutput->setText(i18n("Move the tablet to the next output"));
tabletNextOutput->setObjectName(QStringLiteral("Move Tablet to Next Output"));
KGlobalAccel::setGlobalShortcut(tabletNextOutput, QList<QKeySequence>());
connect(tabletNextOutput, &QAction::triggered, this, &TabletInputFilter::trackNextOutput);
}
static TabletSeatV2Interface *findTabletSeat()
{
auto server = waylandServer();
if (!server) {
return nullptr;
}
TabletManagerV2Interface *manager = server->tabletManagerV2();
return manager->seat(findSeat());
}
void integrateDevice(InputDevice *inputDevice)
{
auto device = qobject_cast<LibInput::Device *>(inputDevice);
if (!device || (!device->isTabletTool() && !device->isTabletPad())) {
return;
}
TabletSeatV2Interface *tabletSeat = findTabletSeat();
if (!tabletSeat) {
qCCritical(KWIN_CORE) << "Could not find tablet seat";
return;
}
struct udev_device *const udev_device = libinput_device_get_udev_device(device->device());
const char *devnode = udev_device_get_syspath(udev_device);
auto deviceGroup = libinput_device_get_device_group(device->device());
auto tablet = static_cast<TabletV2Interface *>(libinput_device_group_get_user_data(deviceGroup));
if (!tablet) {
tablet = tabletSeat->addTablet(device->vendor(), device->product(), device->sysName(), device->name(), {QString::fromUtf8(devnode)});
libinput_device_group_set_user_data(deviceGroup, tablet);
}
if (device->isTabletPad()) {
const int buttonsCount = libinput_device_tablet_pad_get_num_buttons(device->device());
const int ringsCount = libinput_device_tablet_pad_get_num_rings(device->device());
const int stripsCount = libinput_device_tablet_pad_get_num_strips(device->device());
const int modes = libinput_device_tablet_pad_get_num_mode_groups(device->device());
auto firstGroup = libinput_device_tablet_pad_get_mode_group(device->device(), 0);
tabletSeat->addTabletPad(device->sysName(), device->name(), {QString::fromUtf8(devnode)}, buttonsCount, ringsCount, stripsCount, modes, libinput_tablet_pad_mode_group_get_mode(firstGroup), tablet);
}
}
static void trackNextOutput()
{
const auto outputs = workspace()->outputs();
if (outputs.isEmpty()) {
return;
}
int tabletToolCount = 0;
InputDevice *changedDevice = nullptr;
const auto devices = input()->devices();
for (const auto device : devices) {
if (!device->isTabletTool()) {
continue;
}
tabletToolCount++;
if (device->outputName().isEmpty()) {
device->setOutputName(outputs.constFirst()->name());
changedDevice = device;
continue;
}
auto it = std::find_if(outputs.begin(), outputs.end(), [device](const auto &output) {
return output->name() == device->outputName();
});
++it;
auto nextOutput = it == outputs.end() ? outputs.first() : *it;
device->setOutputName(nextOutput->name());
changedDevice = device;
}
const QString message = tabletToolCount == 1 ? i18n("Tablet moved to %1", changedDevice->outputName()) : i18n("Tablets switched outputs");
OSD::show(message, QStringLiteral("input-tablet"), 5000);
}
void removeDevice(InputDevice *inputDevice)
{
auto device = qobject_cast<LibInput::Device *>(inputDevice);
if (device) {
auto deviceGroup = libinput_device_get_device_group(device->device());
libinput_device_group_set_user_data(deviceGroup, nullptr);
TabletSeatV2Interface *tabletSeat = findTabletSeat();
if (tabletSeat) {
tabletSeat->removeDevice(device->sysName());
} else {
qCCritical(KWIN_CORE) << "Could not find tablet to remove" << device->sysName();
}
}
}
TabletToolV2Interface::Type getType(const TabletToolId &tabletToolId)
{
using Type = TabletToolV2Interface::Type;
switch (tabletToolId.m_toolType) {
case InputRedirection::Pen:
return Type::Pen;
case InputRedirection::Eraser:
return Type::Eraser;
case InputRedirection::Brush:
return Type::Brush;
case InputRedirection::Pencil:
return Type::Pencil;
case InputRedirection::Airbrush:
return Type::Airbrush;
case InputRedirection::Finger:
return Type::Finger;
case InputRedirection::Mouse:
return Type::Mouse;
case InputRedirection::Lens:
return Type::Lens;
case InputRedirection::Totem:
return Type::Totem;
}
return Type::Pen;
}
TabletToolV2Interface *createTool(const TabletToolId &tabletToolId)
{
TabletSeatV2Interface *tabletSeat = findTabletSeat();
const auto f = [](InputRedirection::Capability cap) {
switch (cap) {
case InputRedirection::Tilt:
return TabletToolV2Interface::Tilt;
case InputRedirection::Pressure:
return TabletToolV2Interface::Pressure;
case InputRedirection::Distance:
return TabletToolV2Interface::Distance;
case InputRedirection::Rotation:
return TabletToolV2Interface::Rotation;
case InputRedirection::Slider:
return TabletToolV2Interface::Slider;
case InputRedirection::Wheel:
return TabletToolV2Interface::Wheel;
}
return TabletToolV2Interface::Wheel;
};
QList<TabletToolV2Interface::Capability> ifaceCapabilities;
ifaceCapabilities.resize(tabletToolId.m_capabilities.size());
std::transform(tabletToolId.m_capabilities.constBegin(), tabletToolId.m_capabilities.constEnd(), ifaceCapabilities.begin(), f);
TabletToolV2Interface *tool = tabletSeat->addTool(getType(tabletToolId), tabletToolId.m_serialId, tabletToolId.m_uniqueId, ifaceCapabilities, tabletToolId.deviceSysName);
const auto cursor = new SurfaceCursor(tool);
Cursors::self()->addCursor(cursor);
m_cursorByTool[tool] = cursor;
return tool;
}
bool tabletToolEvent(TabletEvent *event) override
@ -2304,25 +2087,7 @@ public:
return false;
}
TabletSeatV2Interface *tabletSeat = findTabletSeat();
if (!tabletSeat) {
qCCritical(KWIN_CORE) << "Could not find tablet manager";
return false;
}
auto tool = tabletSeat->toolByHardwareSerial(event->tabletId().m_serialId, getType(event->tabletId()));
if (!tool) {
tool = createTool(event->tabletId());
}
switch (event->type()) {
case QEvent::TabletEnterProximity:
case QEvent::TabletPress:
case QEvent::TabletMove:
m_cursorByTool[tool]->setPos(event->globalPosition());
break;
default:
break;
}
TabletToolV2Interface *tool = input()->tablet()->ensureTabletTool(event->tabletId());
// NOTE: tablet will be nullptr as the device is removed (see ::removeDevice) but events from the tool
// may still happen (e.g. Release or ProximityOut events)
@ -2415,11 +2180,7 @@ public:
bool tabletToolButtonEvent(uint button, bool pressed, const TabletToolId &tabletToolId, std::chrono::microseconds time) override
{
TabletSeatV2Interface *tabletSeat = findTabletSeat();
auto tool = tabletSeat->toolByHardwareSerial(tabletToolId.m_serialId, getType(tabletToolId));
if (!tool) {
tool = createTool(tabletToolId);
}
TabletToolV2Interface *tool = input()->tablet()->ensureTabletTool(tabletToolId);
if (!tool->isClientSupported()) {
return false;
}
@ -2430,7 +2191,7 @@ public:
TabletPadV2Interface *findAndAdoptPad(const TabletPadId &tabletPadId) const
{
Window *window = workspace()->activeWindow();
auto seat = findTabletSeat();
TabletSeatV2Interface *seat = waylandServer()->tabletManagerV2()->seat(waylandServer()->seat());
if (!window || !window->surface() || !seat->isClientSupported(window->surface()->client())) {
return nullptr;
}
@ -2492,8 +2253,6 @@ public:
strip->sendFrame(std::chrono::duration_cast<std::chrono::milliseconds>(time).count());
return true;
}
QHash<TabletToolV2Interface *, Cursor *> m_cursorByTool;
};
static AbstractDropHandler *dropHandler(Window *window)

@ -7,23 +7,71 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "tablet_input.h"
#include "backends/libinput/device.h"
#include "cursorsource.h"
#include "decorations/decoratedclient.h"
#include "input_event.h"
#include "input_event_spy.h"
#include "osd.h"
#include "pointer_input.h"
#include "wayland/seat.h"
#include "wayland/surface.h"
#include "wayland/tablet_v2.h"
#include "wayland_server.h"
#include "window.h"
#include "workspace.h"
// KDecoration
#include <KDecoration2/Decoration>
// Qt
#include <KGlobalAccel>
#include <KLocalizedString>
#include <QAction>
#include <QHoverEvent>
#include <QWindow>
namespace KWin
{
class SurfaceCursor : public Cursor
{
public:
explicit SurfaceCursor(TabletToolV2Interface *tool)
: Cursor()
{
setParent(tool);
connect(tool, &TabletToolV2Interface::cursorChanged, this, [this](const TabletCursorSourceV2 &cursor) {
if (auto surfaceCursor = std::get_if<TabletSurfaceCursorV2 *>(&cursor)) {
// If the cursor is unset, fallback to the cross cursor.
if ((*surfaceCursor) && (*surfaceCursor)->enteredSerial()) {
if (!m_surfaceSource) {
m_surfaceSource = std::make_unique<SurfaceCursorSource>();
}
m_surfaceSource->update((*surfaceCursor)->surface(), (*surfaceCursor)->hotspot());
setSource(m_surfaceSource.get());
return;
}
}
QByteArray shape;
if (auto shapeCursor = std::get_if<QByteArray>(&cursor)) {
shape = *shapeCursor;
} else {
shape = QByteArrayLiteral("cross");
}
static WaylandCursorImage defaultCursor;
if (!m_shapeSource) {
m_shapeSource = std::make_unique<ShapeCursorSource>();
}
m_shapeSource->setTheme(defaultCursor.theme());
m_shapeSource->setShape(shape);
setSource(m_shapeSource.get());
});
}
private:
std::unique_ptr<ShapeCursorSource> m_shapeSource;
std::unique_ptr<SurfaceCursorSource> m_surfaceSource;
};
TabletInputRedirection::TabletInputRedirection(InputRedirection *parent)
: InputDeviceHandler(parent)
{
@ -43,6 +91,176 @@ void TabletInputRedirection::init()
connect(waylandServer(), &QObject::destroyed, this, [this] {
setInited(false);
});
const auto devices = input()->devices();
for (InputDevice *device : devices) {
integrateDevice(device);
}
connect(input(), &InputRedirection::deviceAdded, this, &TabletInputRedirection::integrateDevice);
connect(input(), &InputRedirection::deviceRemoved, this, &TabletInputRedirection::removeDevice);
auto tabletNextOutput = new QAction(this);
tabletNextOutput->setProperty("componentName", QStringLiteral("kwin"));
tabletNextOutput->setText(i18n("Move the tablet to the next output"));
tabletNextOutput->setObjectName(QStringLiteral("Move Tablet to Next Output"));
KGlobalAccel::setGlobalShortcut(tabletNextOutput, QList<QKeySequence>());
connect(tabletNextOutput, &QAction::triggered, this, &TabletInputRedirection::trackNextOutput);
}
static TabletSeatV2Interface *findTabletSeat()
{
auto server = waylandServer();
if (!server) {
return nullptr;
}
TabletManagerV2Interface *manager = server->tabletManagerV2();
return manager->seat(waylandServer()->seat());
}
void TabletInputRedirection::integrateDevice(InputDevice *inputDevice)
{
auto device = qobject_cast<LibInput::Device *>(inputDevice);
if (!device || (!device->isTabletTool() && !device->isTabletPad())) {
return;
}
TabletSeatV2Interface *tabletSeat = findTabletSeat();
if (!tabletSeat) {
qCCritical(KWIN_CORE) << "Could not find tablet seat";
return;
}
struct udev_device *const udev_device = libinput_device_get_udev_device(device->device());
const char *devnode = udev_device_get_syspath(udev_device);
auto deviceGroup = libinput_device_get_device_group(device->device());
auto tablet = static_cast<TabletV2Interface *>(libinput_device_group_get_user_data(deviceGroup));
if (!tablet) {
tablet = tabletSeat->addTablet(device->vendor(), device->product(), device->sysName(), device->name(), {QString::fromUtf8(devnode)});
libinput_device_group_set_user_data(deviceGroup, tablet);
}
if (device->isTabletPad()) {
const int buttonsCount = libinput_device_tablet_pad_get_num_buttons(device->device());
const int ringsCount = libinput_device_tablet_pad_get_num_rings(device->device());
const int stripsCount = libinput_device_tablet_pad_get_num_strips(device->device());
const int modes = libinput_device_tablet_pad_get_num_mode_groups(device->device());
auto firstGroup = libinput_device_tablet_pad_get_mode_group(device->device(), 0);
tabletSeat->addTabletPad(device->sysName(), device->name(), {QString::fromUtf8(devnode)}, buttonsCount, ringsCount, stripsCount, modes, libinput_tablet_pad_mode_group_get_mode(firstGroup), tablet);
}
}
void TabletInputRedirection::removeDevice(InputDevice *inputDevice)
{
auto device = qobject_cast<LibInput::Device *>(inputDevice);
if (device) {
auto deviceGroup = libinput_device_get_device_group(device->device());
libinput_device_group_set_user_data(deviceGroup, nullptr);
TabletSeatV2Interface *tabletSeat = findTabletSeat();
if (tabletSeat) {
tabletSeat->removeDevice(device->sysName());
} else {
qCCritical(KWIN_CORE) << "Could not find tablet to remove" << device->sysName();
}
}
}
void TabletInputRedirection::trackNextOutput()
{
const auto outputs = workspace()->outputs();
if (outputs.isEmpty()) {
return;
}
int tabletToolCount = 0;
InputDevice *changedDevice = nullptr;
const auto devices = input()->devices();
for (const auto device : devices) {
if (!device->isTabletTool()) {
continue;
}
tabletToolCount++;
if (device->outputName().isEmpty()) {
device->setOutputName(outputs.constFirst()->name());
changedDevice = device;
continue;
}
auto it = std::find_if(outputs.begin(), outputs.end(), [device](const auto &output) {
return output->name() == device->outputName();
});
++it;
auto nextOutput = it == outputs.end() ? outputs.first() : *it;
device->setOutputName(nextOutput->name());
changedDevice = device;
}
const QString message = tabletToolCount == 1 ? i18n("Tablet moved to %1", changedDevice->outputName()) : i18n("Tablets switched outputs");
OSD::show(message, QStringLiteral("input-tablet"), 5000);
}
static TabletToolV2Interface::Type getType(const TabletToolId &tabletToolId)
{
using Type = TabletToolV2Interface::Type;
switch (tabletToolId.m_toolType) {
case InputRedirection::Pen:
return Type::Pen;
case InputRedirection::Eraser:
return Type::Eraser;
case InputRedirection::Brush:
return Type::Brush;
case InputRedirection::Pencil:
return Type::Pencil;
case InputRedirection::Airbrush:
return Type::Airbrush;
case InputRedirection::Finger:
return Type::Finger;
case InputRedirection::Mouse:
return Type::Mouse;
case InputRedirection::Lens:
return Type::Lens;
case InputRedirection::Totem:
return Type::Totem;
}
return Type::Pen;
}
TabletToolV2Interface *TabletInputRedirection::ensureTabletTool(const TabletToolId &tabletToolId)
{
TabletSeatV2Interface *tabletSeat = findTabletSeat();
if (auto tool = tabletSeat->toolByHardwareSerial(tabletToolId.m_serialId, getType(tabletToolId))) {
return tool;
}
const auto f = [](InputRedirection::Capability cap) {
switch (cap) {
case InputRedirection::Tilt:
return TabletToolV2Interface::Tilt;
case InputRedirection::Pressure:
return TabletToolV2Interface::Pressure;
case InputRedirection::Distance:
return TabletToolV2Interface::Distance;
case InputRedirection::Rotation:
return TabletToolV2Interface::Rotation;
case InputRedirection::Slider:
return TabletToolV2Interface::Slider;
case InputRedirection::Wheel:
return TabletToolV2Interface::Wheel;
}
return TabletToolV2Interface::Wheel;
};
QList<TabletToolV2Interface::Capability> ifaceCapabilities;
ifaceCapabilities.resize(tabletToolId.m_capabilities.size());
std::transform(tabletToolId.m_capabilities.constBegin(), tabletToolId.m_capabilities.constEnd(), ifaceCapabilities.begin(), f);
TabletToolV2Interface *tool = tabletSeat->addTool(getType(tabletToolId), tabletToolId.m_serialId, tabletToolId.m_uniqueId, ifaceCapabilities, tabletToolId.deviceSysName);
const auto cursor = new SurfaceCursor(tool);
Cursors::self()->addCursor(cursor);
m_cursorByTool[tool] = cursor;
return tool;
}
void TabletInputRedirection::tabletToolEvent(KWin::InputRedirection::TabletEventType type, const QPointF &pos,
@ -69,6 +287,17 @@ void TabletInputRedirection::tabletToolEvent(KWin::InputRedirection::TabletEvent
break;
}
auto tool = ensureTabletTool(tabletToolId);
switch (t) {
case QEvent::TabletEnterProximity:
case QEvent::TabletPress:
case QEvent::TabletMove:
m_cursorByTool[tool]->setPos(pos);
break;
default:
break;
}
update();
workspace()->setActiveOutput(pos);

@ -17,8 +17,10 @@
namespace KWin
{
class Cursor;
class Window;
class TabletToolId;
class TabletToolV2Interface;
namespace Decoration
{
@ -61,10 +63,15 @@ public:
return m_lastPosition;
}
TabletToolV2Interface *ensureTabletTool(const TabletToolId &id);
private:
void cleanupDecoration(Decoration::DecoratedClientImpl *old,
Decoration::DecoratedClientImpl *now) override;
void focusUpdate(Window *focusOld, Window *focusNow) override;
void integrateDevice(InputDevice *device);
void removeDevice(InputDevice *device);
void trackNextOutput();
bool m_tipDown = false;
bool m_tipNear = false;
@ -72,6 +79,7 @@ private:
QPointF m_lastPosition;
QMetaObject::Connection m_decorationGeometryConnection;
QMetaObject::Connection m_decorationDestroyedConnection;
QHash<TabletToolV2Interface *, Cursor *> m_cursorByTool;
};
}

Loading…
Cancel
Save