diff --git a/ChangeLog b/ChangeLog index baffb175d..0c4cee3c2 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 d66deff11..db0423815 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 f302a9de0..7180e09d3 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 3b6d7c449..e1962691c 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 caa6f6567..ad7fc0c44 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 89fc0e1d9..d062dac86 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 27644d47a..79200063e 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 000000000..6faaa60f6 --- /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 97c425e07..de4cff163 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 b89e20d33..9bde550a6 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; }; }