From 9ed28b7f9848c9dc954589bab02e57012f273267 Mon Sep 17 00:00:00 2001 From: askmeaboutloom Date: Sun, 24 Nov 2024 00:51:58 +0100 Subject: [PATCH] Ignore spontaneous tablet events in canvas instead Rather than doing it with an event filter, because that apparently doesn't work properly and still lets spontaneous events through. They're now ignored in the canvas controller/canvas view instead. --- ChangeLog | 1 + src/desktop/CMakeLists.txt | 1 + src/desktop/main.h | 1 + src/desktop/scene/canvasview.cpp | 50 +++++++++++++++++------ src/desktop/scene/canvasview.h | 3 ++ src/desktop/tabletinput.cpp | 58 +++++---------------------- src/desktop/tabletinput.h | 16 ++++++-- src/desktop/utils/tabletfilter.h | 39 ++++++++++++++++++ src/desktop/view/canvascontroller.cpp | 40 ++++++++++++++---- src/desktop/view/canvascontroller.h | 3 ++ 10 files changed, 141 insertions(+), 71 deletions(-) create mode 100644 src/desktop/utils/tabletfilter.h diff --git a/ChangeLog b/ChangeLog index baffb175d5..0c4cee3c25 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,7 @@ Unreleased Version 2.2.2-pre * Fix: Don't carry over HUD button presses to dock UI in small screen mode. Thanks Meru for reporting. * Feature: Make list action buttons in settings dialog clearer, adding an edit button for canvas shortcuts. Thanks Maffi for suggesting. * Fix: Work around broken transparency when copying images between canvases on Wayland. Thanks Absolute Goober for reporting. + * Fix: Properly ignore system tablet events when using KisTablet drivers on Windows. Thanks Doc for reporting. 2024-11-06 Version 2.2.2-beta.4 * Fix: Solve rendering glitches with selection outlines that happen on some systems. Thanks xxxx for reporting. diff --git a/src/desktop/CMakeLists.txt b/src/desktop/CMakeLists.txt index d66deff118..db04238155 100644 --- a/src/desktop/CMakeLists.txt +++ b/src/desktop/CMakeLists.txt @@ -316,6 +316,7 @@ target_sources(drawpile PRIVATE utils/qtguicompat.h utils/recents.cpp utils/recents.h + utils/tabletfilter.h utils/touchhandler.cpp utils/touchhandler.h utils/widgetutils.cpp diff --git a/src/desktop/main.h b/src/desktop/main.h index f302a9de0b..7180e09d3a 100644 --- a/src/desktop/main.h +++ b/src/desktop/main.h @@ -93,6 +93,7 @@ class DrawpileApp final : public QApplication { void setDockTitleBarsHidden(bool hidden); void focusCanvas(); void shortcutsChanged(); + void tabletDriverChanged(); protected: bool event(QEvent *e) override; diff --git a/src/desktop/scene/canvasview.cpp b/src/desktop/scene/canvasview.cpp index 3b6d7c4492..e1962691c3 100644 --- a/src/desktop/scene/canvasview.cpp +++ b/src/desktop/scene/canvasview.cpp @@ -206,7 +206,12 @@ CanvasView::CanvasView(QWidget *parent) setAcceptDrops(true); setFrameShape(QFrame::NoFrame); - auto &settings = dpApp().settings(); + DrawpileApp &app = dpApp(); + connect( + &app, &DrawpileApp::tabletDriverChanged, this, + &CanvasView::resetTabletFilter, Qt::QueuedConnection); + + desktop::settings::Settings &settings = app.settings(); settings.bindCanvasViewBackgroundColor(this, [this](QColor color) { color.setAlpha(255); setBackgroundBrush(color); @@ -1024,6 +1029,8 @@ void CanvasView::enterEvent(compat::EnterEvent *event) oldfocus->inherits("QPlainTextEdit"))) { setFocus(Qt::MouseFocusReason); } + + m_tabletFilter.reset(); } void CanvasView::leaveEvent(QEvent *event) @@ -1037,6 +1044,7 @@ void CanvasView::leaveEvent(QEvent *event) m_scene->removeHover(); updateOutline(); resetCursor(); + m_tabletFilter.reset(); } void CanvasView::focusInEvent(QFocusEvent *event) @@ -1211,6 +1219,11 @@ void CanvasView::startTabletEventTimer() } } +void CanvasView::resetTabletFilter() +{ + m_tabletFilter.reset(); +} + void CanvasView::penPressEvent( QEvent *event, long long timeMsec, const QPointF &pos, qreal pressure, qreal xtilt, qreal ytilt, qreal rotation, Qt::MouseButton button, @@ -2006,16 +2019,20 @@ bool CanvasView::viewportEvent(QEvent *event) QTabletEvent *tabev = static_cast(event); const auto tabPos = compat::tabPosF(*tabev); Qt::KeyboardModifiers modifiers = getTabletModifiers(tabev); + bool ignore = m_tabletFilter.shouldIgnore(tabev); DP_EVENT_LOG( "tablet_press spontaneous=%d x=%f y=%f pressure=%f xtilt=%d " "ytilt=%d rotation=%f buttons=0x%x modifiers=0x%x pendown=%d " - "touching=%d effectivemodifiers=0x%u", - tabev->spontaneous(), tabPos.x(), tabPos.y(), tabev->pressure(), - compat::cast_6(tabev->xTilt()), + "touching=%d effectivemodifiers=0x%u ignore=%d", + int(tabev->spontaneous()), tabPos.x(), tabPos.y(), + tabev->pressure(), compat::cast_6(tabev->xTilt()), compat::cast_6(tabev->yTilt()), qDegreesToRadians(tabev->rotation()), unsigned(tabev->buttons()), unsigned(tabev->modifiers()), m_pendown, m_touch->isTouching(), - unsigned(modifiers)); + unsigned(modifiers), int(ignore)); + if(ignore) { + return true; + } Qt::MouseButton button; bool eraserOverride; @@ -2057,16 +2074,20 @@ bool CanvasView::viewportEvent(QEvent *event) const auto tabPos = compat::tabPosF(*tabev); Qt::KeyboardModifiers modifiers = getTabletModifiers(tabev); Qt::MouseButtons buttons = tabev->buttons(); + bool ignore = m_tabletFilter.shouldIgnore(tabev); DP_EVENT_LOG( "tablet_move spontaneous=%d x=%f y=%f pressure=%f xtilt=%d " "ytilt=%d rotation=%f buttons=0x%x modifiers=0x%x pendown=%d " - "touching=%d effectivemodifiers=0x%u", - tabev->spontaneous(), tabPos.x(), tabPos.y(), tabev->pressure(), - compat::cast_6(tabev->xTilt()), + "touching=%d effectivemodifiers=0x%u ignore=%d", + int(tabev->spontaneous()), tabPos.x(), tabPos.y(), + tabev->pressure(), compat::cast_6(tabev->xTilt()), compat::cast_6(tabev->yTilt()), qDegreesToRadians(tabev->rotation()), unsigned(buttons), unsigned(tabev->modifiers()), m_pendown, m_touch->isTouching(), - unsigned(modifiers)); + unsigned(modifiers), int(ignore)); + if(ignore) { + return true; + } if(!tabletinput::passPenEvents()) { tabev->accept(); @@ -2091,12 +2112,17 @@ bool CanvasView::viewportEvent(QEvent *event) QTabletEvent *tabev = static_cast(event); const auto tabPos = compat::tabPosF(*tabev); Qt::KeyboardModifiers modifiers = getTabletModifiers(tabev); + bool ignore = m_tabletFilter.shouldIgnore(tabev); DP_EVENT_LOG( "tablet_release spontaneous=%d x=%f y=%f buttons=0x%x pendown=%d " - "touching=%d effectivemodifiers=0x%u", - tabev->spontaneous(), tabPos.x(), tabPos.y(), + "touching=%d effectivemodifiers=0x%u ignore=%d", + int(tabev->spontaneous()), tabPos.x(), tabPos.y(), unsigned(tabev->buttons()), m_pendown, m_touch->isTouching(), - unsigned(modifiers)); + unsigned(modifiers), int(ignore)); + if(ignore) { + return true; + } + updateCursorPos(tabPos.toPoint()); if(!tabletinput::passPenEvents()) { tabev->accept(); diff --git a/src/desktop/scene/canvasview.h b/src/desktop/scene/canvasview.h index caa6f6567d..ad7fc0c440 100644 --- a/src/desktop/scene/canvasview.h +++ b/src/desktop/scene/canvasview.h @@ -2,6 +2,7 @@ #ifndef DESKTOP_SCENE_CANVASVIEW #define DESKTOP_SCENE_CANVASVIEW #include "desktop/utils/qtguicompat.h" +#include "desktop/utils/tabletfilter.h" #include "desktop/view/lock.h" #include "libclient/canvas/canvasshortcuts.h" #include "libclient/canvas/point.h" @@ -274,6 +275,7 @@ private slots: class SetDragParams; void startTabletEventTimer(); + void resetTabletFilter(); // unified mouse/stylus event handlers void penPressEvent( @@ -457,6 +459,7 @@ private slots: #ifdef Q_OS_LINUX bool m_waylandWorkarounds; #endif + TabletFilter m_tabletFilter; }; } diff --git a/src/desktop/tabletinput.cpp b/src/desktop/tabletinput.cpp index 89fc0e1d9c..d062dac86a 100644 --- a/src/desktop/tabletinput.cpp +++ b/src/desktop/tabletinput.cpp @@ -20,45 +20,12 @@ static Mode currentMode = Mode::Uninitialized; #ifdef Q_OS_WIN -class SpontaneousTabletEventFilter final : public QObject { -public: - explicit SpontaneousTabletEventFilter(QObject *parent) - : QObject{parent} - { - } - -protected: - bool eventFilter(QObject *watched, QEvent *event) override final - { - switch(event->type()) { - case QEvent::TabletEnterProximity: - case QEvent::TabletLeaveProximity: - case QEvent::TabletMove: - case QEvent::TabletPress: - case QEvent::TabletRelease: - // KisTablet uses QApplication::sendEvent, which means its events - // are never set to be spontaneous. We use that to filter out Qt's - // own tablet events, which in turn are always spontaneous. - if(event->spontaneous()) { - return false; - } - [[fallthrough]]; - default: - return QObject::eventFilter(watched, event); - } - } -}; - static KisTabletSupportWin8 *kisTabletSupportWin8; -static SpontaneousTabletEventFilter *spontaneousTabletEventFilter; +static bool shouldPassPenEvents = true; static void resetKisTablet(DrawpileApp &app) { - if(spontaneousTabletEventFilter) { - app.removeEventFilter(spontaneousTabletEventFilter); - delete spontaneousTabletEventFilter; - spontaneousTabletEventFilter = nullptr; - } + shouldPassPenEvents = true; if(kisTabletSupportWin8) { app.removeNativeEventFilter(kisTabletSupportWin8); delete kisTabletSupportWin8; @@ -67,17 +34,11 @@ static void resetKisTablet(DrawpileApp &app) KisTabletSupportWin::quit(); } -static void installSpontaneousTabletEventFilter(DrawpileApp &app) -{ - spontaneousTabletEventFilter = new SpontaneousTabletEventFilter{&app}; - app.installEventFilter(spontaneousTabletEventFilter); -} - static void enableKisTabletWinink(DrawpileApp &app) { kisTabletSupportWin8 = new KisTabletSupportWin8; if(kisTabletSupportWin8->init()) { - installSpontaneousTabletEventFilter(app); + shouldPassPenEvents = false; app.installNativeEventFilter(kisTabletSupportWin8); currentMode = Mode::KisTabletWinink; } else { @@ -87,10 +48,10 @@ static void enableKisTabletWinink(DrawpileApp &app) } } -static void enableKisTabletWintab(DrawpileApp &app, bool relativePenModeHack) +static void enableKisTabletWintab(bool relativePenModeHack) { if(KisTabletSupportWin::init()) { - installSpontaneousTabletEventFilter(app); + shouldPassPenEvents = false; KisTabletSupportWin::enableRelativePenModeHack(relativePenModeHack); if(relativePenModeHack) { currentMode = Mode::KisTabletWintabRelativePenHack; @@ -148,10 +109,10 @@ void init(DrawpileApp &app) enableKisTabletWinink(app); break; case Mode::KisTabletWintab: - enableKisTabletWintab(app, false); + enableKisTabletWintab(false); break; case Mode::KisTabletWintabRelativePenHack: - enableKisTabletWintab(app, true); + enableKisTabletWintab(true); break; # if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) case Mode::Qt5: @@ -171,6 +132,7 @@ void init(DrawpileApp &app) case Mode::Uninitialized: break; } + emit app.tabletDriverChanged(); }); #else // Nothing to do on other platforms. @@ -201,9 +163,7 @@ const char *current() #ifdef Q_OS_WIN bool passPenEvents() { - // The spontaneous event filter is installed if and only if a KisTablet - // input mode is currently active. - return !spontaneousTabletEventFilter; + return shouldPassPenEvents; } #endif diff --git a/src/desktop/tabletinput.h b/src/desktop/tabletinput.h index 27644d47a0..79200063e4 100644 --- a/src/desktop/tabletinput.h +++ b/src/desktop/tabletinput.h @@ -1,12 +1,17 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef TABLETINPUT_H -#define TABLETINPUT_H - +#ifndef DESTKOP_TABLETINPUT_H +#define DESTKOP_TABLETINPUT_H #include #include class DrawpileApp; +#ifdef Q_OS_WIN +# define TABLETINPUT_CONSTEXPR_OR_INLINE inline +#else +# define TABLETINPUT_CONSTEXPR_OR_INLINE constexpr +#endif + namespace tabletinput { Q_NAMESPACE @@ -60,6 +65,11 @@ constexpr bool passPenEvents() } #endif +TABLETINPUT_CONSTEXPR_OR_INLINE bool ignoreSpontaneous() +{ + return !passPenEvents(); +} + } #endif diff --git a/src/desktop/utils/tabletfilter.h b/src/desktop/utils/tabletfilter.h new file mode 100644 index 0000000000..6faaa60f69 --- /dev/null +++ b/src/desktop/utils/tabletfilter.h @@ -0,0 +1,39 @@ +#ifndef DESKTOP_UTILS_TABLETFILTER_H +#define DESKTOP_UTILS_TABLETFILTER_H +#include "desktop/tabletinput.h" +#include + +class TabletFilter { +public: + TABLETINPUT_CONSTEXPR_OR_INLINE void reset() + { +#ifdef Q_OS_WIN + m_ignoreSpontaneous = false; +#endif + } + + TABLETINPUT_CONSTEXPR_OR_INLINE bool shouldIgnore(const QTabletEvent *event) + { +#ifdef Q_OS_WIN + bool spontaneous = event->spontaneous(); + if(m_ignoreSpontaneous) { + return spontaneous; + } else { + if(!spontaneous && tabletinput::ignoreSpontaneous()) { + m_ignoreSpontaneous = true; + } + return false; + } +#else + Q_UNUSED(event); + return false; +#endif + } + +#ifdef Q_OS_WIN +private: + bool m_ignoreSpontaneous = false; +#endif +}; + +#endif diff --git a/src/desktop/view/canvascontroller.cpp b/src/desktop/view/canvascontroller.cpp index 97c425e07e..de4cff1635 100644 --- a/src/desktop/view/canvascontroller.cpp +++ b/src/desktop/view/canvascontroller.cpp @@ -174,7 +174,12 @@ CanvasController::CanvasController(CanvasScene *scene, QWidget *parent) QGuiApplication::platformName() == QStringLiteral("wayland")) #endif { - desktop::settings::Settings &settings = dpApp().settings(); + DrawpileApp &app = dpApp(); + connect( + &app, &DrawpileApp::tabletDriverChanged, this, + &CanvasController::resetTabletFilter, Qt::QueuedConnection); + + desktop::settings::Settings &settings = app.settings(); settings.bindCanvasViewBackgroundColor( this, &CanvasController::setClearColor); settings.bindRenderSmooth(this, &CanvasController::setRenderSmooth); @@ -561,6 +566,7 @@ void CanvasController::handleEnter() m_showOutline = true; m_scene->setCursorOnCanvas(true); updateOutline(); + m_tabletFilter.reset(); } void CanvasController::handleLeave() @@ -572,6 +578,7 @@ void CanvasController::handleLeave() m_scene->removeHover(); updateOutline(); resetCursor(); + m_tabletFilter.reset(); } void CanvasController::handleFocusIn() @@ -668,13 +675,18 @@ void CanvasController::handleTabletMove(QTabletEvent *event) qreal yTilt = event->yTilt(); Qt::KeyboardModifiers modifiers = getTabletModifiers(event); Qt::MouseButtons buttons = event->buttons(); + bool ignore = m_tabletFilter.shouldIgnore(event); DP_EVENT_LOG( "tablet_move spontaneous=%d x=%f y=%f pressure=%f xtilt=%f " "ytilt=%f rotation=%f buttons=0x%x modifiers=0x%x penstate=%d " - "touching=%d effectivemodifiers=0x%u", + "touching=%d effectivemodifiers=0x%u ignore=%d", int(event->spontaneous()), posf.x(), posf.y(), pressure, xTilt, yTilt, rotation, unsigned(buttons), unsigned(event->modifiers()), - int(m_penState), int(m_touch->isTouching()), unsigned(modifiers)); + int(m_penState), int(m_touch->isTouching()), unsigned(modifiers), + int(ignore)); + if(ignore) { + return; + } // Under Windows Ink, some tablets report bogus zero-pressure inputs. // We accept them so that they don't result in synthesized mouse events, @@ -704,13 +716,18 @@ void CanvasController::handleTabletPress(QTabletEvent *event) qreal xTilt = event->xTilt(); qreal yTilt = event->yTilt(); Qt::KeyboardModifiers modifiers = getTabletModifiers(event); + bool ignore = m_tabletFilter.shouldIgnore(event); DP_EVENT_LOG( "tablet_press spontaneous=%d x=%f y=%f pressure=%f xtilt=%f " "ytilt=%f rotation=%f buttons=0x%x modifiers=0x%x penstate=%d " - "touching=%d effectivemodifiers=0x%u", + "touching=%d effectivemodifiers=0x%u ignore=%d", int(event->spontaneous()), posf.x(), posf.y(), pressure, xTilt, yTilt, rotation, unsigned(buttons), unsigned(event->modifiers()), - int(m_penState), int(m_touch->isTouching()), unsigned(modifiers)); + int(m_penState), int(m_touch->isTouching()), unsigned(modifiers), + int(ignore)); + if(ignore) { + return; + } Qt::MouseButton button; bool eraserOverride; @@ -744,12 +761,16 @@ void CanvasController::handleTabletRelease(QTabletEvent *event) QPointF posf = tabletPosF(event); Qt::KeyboardModifiers modifiers = getTabletModifiers(event); + bool ignore = m_tabletFilter.shouldIgnore(event); DP_EVENT_LOG( "tablet_release spontaneous=%d x=%f y=%f buttons=0x%x penstate=%d " - "touching=%d effectivemodifiers=0x%u", + "touching=%d effectivemodifiers=0x%u ignore=%d", int(event->spontaneous()), posf.x(), posf.y(), unsigned(event->buttons()), int(m_penState), - int(m_touch->isTouching()), unsigned(modifiers)); + int(m_touch->isTouching()), unsigned(modifiers), int(ignore)); + if(ignore) { + return; + } penReleaseEvent( QDateTime::currentMSecsSinceEpoch(), posf, event->button(), @@ -1327,6 +1348,11 @@ void CanvasController::startTabletEventTimer() } } +void CanvasController::resetTabletFilter() +{ + m_tabletFilter.reset(); +} + void CanvasController::penMoveEvent( long long timeMsec, const QPointF &posf, qreal pressure, qreal xtilt, qreal ytilt, qreal rotation, Qt::KeyboardModifiers modifiers) diff --git a/src/desktop/view/canvascontroller.h b/src/desktop/view/canvascontroller.h index b89e20d339..9bde550a68 100644 --- a/src/desktop/view/canvascontroller.h +++ b/src/desktop/view/canvascontroller.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #ifndef DESKTOP_VIEW_CANVASCONTROLLER_H #define DESKTOP_VIEW_CANVASCONTROLLER_H +#include "desktop/utils/tabletfilter.h" #include "desktop/view/lock.h" #include "libclient/canvas/canvasshortcuts.h" #include "libclient/canvas/point.h" @@ -215,6 +216,7 @@ class CanvasController : public QObject { void setShowTransformNotices(bool showTransformNotices); void setTabletEventTimerDelay(int tabletEventTimerDelay); void startTabletEventTimer(); + void resetTabletFilter(); void penMoveEvent( long long timeMsec, const QPointF &posf, qreal pressure, qreal xtilt, @@ -428,6 +430,7 @@ class CanvasController : public QObject { #ifdef Q_OS_LINUX bool m_waylandWorkarounds; #endif + TabletFilter m_tabletFilter; }; }