Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Tablet events behavior with stylus button click
Qt 6.11 is out! See what's new in the release blog

Tablet events behavior with stylus button click

Scheduled Pinned Locked Moved Unsolved General and Desktop
3 Posts 1 Posters 519 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • Q Offline
    Q Offline
    qt-public-name
    wrote on last edited by
    #1

    Hello,

    Problem ?

    I am working on tablet input and I found out that there is an odd behavior with the tablet when using the stylus button (usually simulating a mouse button right click). Basically, some TabletRelease/MouseButtonRelease seems to be missing and the information in QEvent::button() and QEvent::buttons() are inconsistent.

    I made a simple application tracking tablet/mouse events, keeping a list of pressed buttons. I place the stylus on the tablet and press the stylus button and below is info about one of the events I receive:

    Event: 151972
      event type: TabletPress
      watched class: QLabel
      device type: Stylus
      button: LeftButton
      buttons: RightButton
      m_keys: 
      m_buttons: LeftButton, RightButton
    

    The problem here is that we have a TabletPress event with the button LeftButton but with buttons [RightButton], while in my private list m_buttons I have the accurate state of buttons pressed [LeftButton, RightButton].

    Another problem is that later, in the same conditions as written above, I lift the stylus from the tablet: the event MouseButtonRelease/TabletRelease for the LeftButton will not be sent. The event's buttons will be correct ([RightButton]) while my private list m_buttons will not ([LeftButton, RightButton]) .

    I tried to toggle the attribute Qt::AA_SynthesizeMouseForUnhandledTabletEvents but it does not change anything.

    Is this the expected behavior or am I missing something ?

    How to reproduce the problem

    • Place the stylus on the tablet
    • Press the stylus button (simulating a mouse right click button)
    • Lift the stylus from the tablet (the event with inconsistent button/buttons is sent)
    • Release the stylus button (there is no MouseButtonRelease/TabletRelease event sent here)

    Simple test application code

    I also added some fix in my code to be able to get the corresponding buttons state of the events in my private list.

    mainwindow.h

    #pragma once
    
    #include <QMainWindow>
    #include <QList>
    #include <QObject>
    #include <QEvent>
    #include <QSinglePointEvent>
    #include <QMetaEnum>
    #include <QTextEdit>
    #include <QPushButton>
    #include <QLabel>
    
    class TabletButtonInputDebugger : public QObject
    {
        Q_OBJECT
    
    public:
        TabletButtonInputDebugger(QObject* parent = nullptr);
    
        bool useFix() const;
        void setUseFix(bool useFix);
    
        void setDebugOutputTextEdit(QTextEdit* textEdit);
        void setDebugDownButtons(QLabel* label);
    
    protected:
        bool eventFilter(QObject* watched, QEvent* event) override;
    
    private:
        void updateInput(
            const QObject& watched,
            const QEvent& ev);
    
        void onKeyPress(
            const QObject& watched,
            const QKeyEvent& ev);
        void onKeyRelease(
            const QObject& watched,
            const QKeyEvent& ev);
    
        void onButtonPress(
            const QObject& watched,
            const QSinglePointEvent& ev);
        void onButtonRelease(
            const QObject& watched,
            const QSinglePointEvent& ev);
    
        void onButtonDoubleClick(
            const QObject& watched,
            const QSinglePointEvent& ev);
    
        void debugInput(
            const QObject& watched,
            const QInputEvent& event);
    
        void appendDebugLine(const QString& line);
    
        QList<Qt::Key> m_keys;
        QList<Qt::MouseButton> m_buttons;
    
        bool m_isStylusDown = false;
        bool m_isStylusDeviceUnstable = false;
        bool m_useFix = false;
    
        QTextEdit* m_debugOutputTextEdit = nullptr;
        QLabel* m_debugDownButtonsLabel = nullptr;
    };
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        MainWindow(QWidget *parent = nullptr);
    
    private:
        TabletButtonInputDebugger m_tbid;
    };
    
    

    main_window.cpp

    #include "mainwindow.h"
    
    #include <QSplitter>
    #include <QVBoxLayout>
    #include <QHBoxLayout>
    #include <QCheckBox>
    #include <QTimer>
    
    namespace
    {
    
    template <typename Enum>
    const char* enumValueToKey(Enum val)
    {
        const QMetaEnum metaEnum = QMetaEnum::fromType<Enum>();
        return metaEnum.valueToKey(val);
    }
    
    QString toString(QInputDevice::DeviceType deviceType)
    {
        switch (deviceType)
        {
            default:
            case QInputDevice::DeviceType::Unknown:
                return "Unknown";
            case QInputDevice::DeviceType::Mouse: return "Mouse";
            case QInputDevice::DeviceType::TouchScreen: return "TouchScreen";
            case QInputDevice::DeviceType::TouchPad: return "TouchPad";
            case QInputDevice::DeviceType::Puck: return "Puck";
            case QInputDevice::DeviceType::Stylus: return "Stylus";
            case QInputDevice::DeviceType::Airbrush: return "Airbrush";
            case QInputDevice::DeviceType::Keyboard: return "Keyboard";
        }
    }
    
    QString toString(Qt::MouseButton button)
    {
        if (button == Qt::LeftButton)
            return "LeftButton";
        if (button == Qt::RightButton)
            return "RightButton";
        if (button == Qt::MiddleButton)
            return "MiddleButton";
        if (button == Qt::BackButton)
            return "BackButton";
        if (button == Qt::ForwardButton)
            return "ForwardButton";
        if (button == Qt::TaskButton)
            return  "TaskButton";
    
        return "UnknownButton";
    }
    
    QString toString(Qt::MouseButtons buttons)
    {
        QStringList names;
    
        if (buttons & Qt::LeftButton)
            names << "LeftButton";
        if (buttons & Qt::RightButton)
            names << "RightButton";
        if (buttons & Qt::MiddleButton)
            names << "MiddleButton";
        if (buttons & Qt::BackButton)
            names << "BackButton";
        if (buttons & Qt::ForwardButton)
            names << "ForwardButton";
        if (buttons & Qt::TaskButton)
            names << "TaskButton";
        if (names.isEmpty())
            names << "NoButton";
    
        return names.join("|");
    }
    
    QString toString(Qt::Key key)
    {
        return QString(enumValueToKey(key));
    }
    
    template <typename T>
    QString join(const QList<T>& list, const QString& sep)
    {
        QString res;
    
        for (const T& e : list)
        {
            res += toString(e) + sep;
        }
    
        res.chop(sep.size());
    
        return res;
    }
    
    }
    
    TabletButtonInputDebugger::TabletButtonInputDebugger(QObject* parent) :
        QObject(parent)
    {
    
    }
    
    bool TabletButtonInputDebugger::useFix() const
    {
        return m_useFix;
    }
    
    void TabletButtonInputDebugger::setUseFix(bool useFix)
    {
        if (m_useFix == useFix)
        {
            return;
        }
    
        m_buttons.clear();
        m_useFix = useFix;
    }
    
    void TabletButtonInputDebugger::setDebugOutputTextEdit(QTextEdit* textEdit)
    {
        m_debugOutputTextEdit = textEdit;
    }
    
    void TabletButtonInputDebugger::setDebugDownButtons(QLabel* label)
    {
        m_debugDownButtonsLabel = label;
    }
    
    bool TabletButtonInputDebugger::eventFilter(QObject* watched, QEvent* event)
    {
        updateInput(*watched, *event);
        return QObject::eventFilter(watched, event);
    }
    
    void TabletButtonInputDebugger::updateInput(
        const QObject& watched,
        const QEvent& ev)
    {
        // Ignoring non-input events.
        if (!ev.isInputEvent())
        {
            return;
        }
    
        // Ignoring events on QWidgetWindow.
        if (!watched.isWidgetType())
        {
            return;
        }
    
        switch (ev.type())
        {
        case QEvent::KeyPress:
            onKeyPress(watched, static_cast<const QKeyEvent&>(ev));
            break;
        case QEvent::KeyRelease:
            onKeyRelease(watched, static_cast<const QKeyEvent&>(ev));
            break;
        case QEvent::MouseButtonPress:
        case QEvent::TabletPress:
            onButtonPress(watched, static_cast<const QSinglePointEvent&>(ev));
            break;
        case QEvent::MouseButtonRelease:
        case QEvent::TabletRelease:
            onButtonRelease(watched, static_cast<const QSinglePointEvent&>(ev));
            break;
        case QEvent::MouseButtonDblClick:
            onButtonDoubleClick(watched, static_cast<const QSinglePointEvent&>(ev));
            break;
        case QEvent::MouseMove:
        case QEvent::TabletMove:
            break;
        default:
            // debugInput(watched, ev);
            break;
        }
    }
    
    void TabletButtonInputDebugger::onKeyPress(
        const QObject& watched,
        const QKeyEvent& ev)
    {
        if (ev.isAutoRepeat()) { return; }
    
        const Qt::Key key = static_cast<Qt::Key>(ev.key());
        if (!m_keys.contains(key))
        {
            m_keys.push_back(key);
        }
    }
    
    void TabletButtonInputDebugger::onKeyRelease(
        const QObject& watched,
        const QKeyEvent& ev)
    {
        if (ev.isAutoRepeat()) { return; }
    
        m_keys.removeAll(ev.key());
    }
    
    void TabletButtonInputDebugger::onButtonPress(
        const QObject& watched,
        const QSinglePointEvent& ev)
    {
        if (m_useFix)
        {
            // If the stylus is down and we receive an event from another device,
            // we cannot track its down state anymore. It is unstable.
            if (m_isStylusDown &&
                ev.deviceType() != QInputDevice::DeviceType::Stylus)
            {
                m_isStylusDown = false;
                m_isStylusDeviceUnstable = true;
                m_buttons.clear();
    
                appendDebugLine(" -----------------");
                appendDebugLine("| Stylus unstable |");
                appendDebugLine(" -----------------");
                appendDebugLine("");
            }
            else
            {
                if (ev.deviceType() == QInputDevice::DeviceType::Stylus)
                {
                    if (m_isStylusDeviceUnstable)
                    {
                        // Ignoring those events.
                        return;
                    }
    
                    if (ev.button() == Qt::LeftButton)
                    {
                        m_isStylusDown = true;
                    }
                }
            }
        }
    
        if (!m_buttons.contains(ev.button()))
        {
            m_buttons.push_back(ev.button());
        }
    
        debugInput(watched, ev);
    }
    
    void TabletButtonInputDebugger::onButtonRelease(
        const QObject& watched,
        const QSinglePointEvent& ev)
    {
        if (m_useFix)
        {
            if (m_isStylusDeviceUnstable)
            {
                // If Qt tells that is that there are no buttons pressed,
                // the stylus is considered stable again.
                if (ev.buttons() == Qt::NoButton)
                {
                    appendDebugLine(" ---------------------");
                    appendDebugLine("| Stylus stable again |");
                    appendDebugLine(" ---------------------");
                    appendDebugLine("");
    
                    m_isStylusDeviceUnstable = false;
                    m_buttons.clear();
                }
            }
            else
            {
                if (ev.deviceType() == QInputDevice::DeviceType::Stylus &&
                    ev.button() == Qt::LeftButton)
                {
                    m_isStylusDown = false;
                }
            }
        }
    
        m_buttons.removeAll(ev.button());
    
        debugInput(watched, ev);
    }
    
    void TabletButtonInputDebugger::onButtonDoubleClick(
        const QObject& watched,
        const QSinglePointEvent& ev)
    {
        debugInput(watched, ev);
    }
    
    void TabletButtonInputDebugger::debugInput(
        const QObject& watched,
        const QInputEvent& event)
    {
        const auto eventTypeStr = enumValueToKey(event.type());
        const auto watchedClassName = watched.metaObject()->className();
        const auto deviceTypeStr = toString(event.deviceType());
        const auto m_keysStr = join(m_keys, ", ");
        const auto m_buttonsStr = join(m_buttons, ", ");
    
        appendDebugLine(QString() + "Event: " + QString::number(event.timestamp()));
        appendDebugLine(QString() + "  event type: " + eventTypeStr);
        appendDebugLine(QString() + "  watched class: " + watchedClassName);
        appendDebugLine(QString() + "  device type: " + deviceTypeStr);
    
        if (event.type() == QEvent::KeyPress ||
            event.type() == QEvent::KeyRelease)
        {
            const auto& ke = static_cast<const QKeyEvent&>(event);
            appendDebugLine(QString() + "  key: " + toString(static_cast<Qt::Key>(ke.key())));
        }
        if (event.isSinglePointEvent())
        {
            const auto& spe = static_cast<const QSinglePointEvent&>(event);
            appendDebugLine(QString() + "  button: " + toString(spe.button()));
            appendDebugLine(QString() + "  buttons: " + toString(spe.buttons()));
        }
    
        appendDebugLine(QString() + "  m_keys: " + m_keysStr);
        appendDebugLine(QString() + "  m_buttons: " + m_buttonsStr);
    
        if (m_debugDownButtonsLabel)
        {
            m_debugDownButtonsLabel->setText(m_buttonsStr);
        }
    
        appendDebugLine("");
    }
    
    void TabletButtonInputDebugger::appendDebugLine(const QString& line)
    {
        qDebug() << line;
    
        if (m_debugOutputTextEdit)
        {
            m_debugOutputTextEdit->append(line);
        }
    }
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent)
    {
        auto centralSplitterWidget = new QSplitter(this);
        setCentralWidget(centralSplitterWidget);
        centralSplitterWidget->setHandleWidth(20);
        centralSplitterWidget->setChildrenCollapsible(false);
    
        auto testAreaWidget = new QLabel(centralSplitterWidget);
        centralSplitterWidget->addWidget(testAreaWidget);
        testAreaWidget->setText("Test area");
        testAreaWidget->setAlignment(Qt::AlignCenter);
        testAreaWidget->setMouseTracking(true);
        testAreaWidget->setTabletTracking(true);
    
        auto middlePaneWidget = new QWidget(centralSplitterWidget);
        centralSplitterWidget->addWidget(middlePaneWidget);
        auto middlePaneLayout = new QVBoxLayout(middlePaneWidget);
    
        auto outputTextEdit = new QTextEdit(middlePaneWidget);
        middlePaneLayout->addWidget(outputTextEdit, 1);
        outputTextEdit->setReadOnly(true);
    
        auto clearOutputTextEditButton = new QPushButton(middlePaneWidget);
        middlePaneLayout->addWidget(clearOutputTextEditButton);
        clearOutputTextEditButton->setText("Clear");
        connect(
            clearOutputTextEditButton, &QPushButton::clicked,
            this, [outputTextEdit]() { outputTextEdit->clear(); });
    
        auto rightPaneWidget = new QWidget(centralSplitterWidget);
        centralSplitterWidget->addWidget(rightPaneWidget);
        auto rightPaneLayout = new QVBoxLayout(rightPaneWidget);
        rightPaneLayout->setAlignment(Qt::AlignTop);
        rightPaneLayout->addSpacerItem(new QSpacerItem(-1, -1));
    
        auto downButtonsLayout = new QHBoxLayout();
        rightPaneLayout->addLayout(downButtonsLayout);
    
        downButtonsLayout->addWidget(new QLabel("Buttons down: ", rightPaneWidget));
    
        auto downButtonsLabel = new QLabel(rightPaneWidget);
        downButtonsLayout->addWidget(downButtonsLabel, 1);
    
        auto toggleFixCheckBox = new QCheckBox(rightPaneWidget);
        rightPaneLayout->addWidget(toggleFixCheckBox);
        toggleFixCheckBox->setText("Use fix");
        toggleFixCheckBox->setChecked(m_tbid.useFix());
        connect(
            toggleFixCheckBox, &QCheckBox::toggled,
            this, [this](bool state) { m_tbid.setUseFix(state); });
    
        auto synthesizeMouseForUnhandledTabletEventsButton = new QCheckBox(rightPaneWidget);
        rightPaneLayout->addWidget(synthesizeMouseForUnhandledTabletEventsButton);
        synthesizeMouseForUnhandledTabletEventsButton->setText("AA_SynthesizeMouseForUnhandledTabletEventsButton");
        synthesizeMouseForUnhandledTabletEventsButton->setChecked(qApp->testAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents));
        connect(
            synthesizeMouseForUnhandledTabletEventsButton, &QCheckBox::toggled,
            this, [](bool state) { qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents, state); });
    
        // - Tablet debugger -
    
        testAreaWidget->installEventFilter(&m_tbid);
        // Also tested with the application, same problem.
        // qApp->installEventFilter(&m_tbid);
        m_tbid.setDebugOutputTextEdit(outputTextEdit);
        m_tbid.setDebugDownButtons(downButtonsLabel);
    }
    
    

    main.cpp

    #include "mainwindow.h"
    
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
    
        MainWindow w;
        w.show();
    
        return app.exec();
    }
    
    

    Info

    OS: Windows 11
    Qt: 6.9.1 (I also tested with Qt 6.7.2.)

    1 Reply Last reply
    0
    • Q Offline
      Q Offline
      qt-public-name
      wrote on last edited by
      #2

      I cannot edit my previous message (considered being a spam). I add corrections and additional info below:

      How to reproduce the problem

      • Place the stylus on the tablet
      • Press the stylus button (simulating a mouse right click button) (the event with inconsistent button/buttons is sent)
      • Lift the stylus from the tablet (there is no MouseButtonRelease/TabletRelease event sent here)
      • Release the stylus button

      Info

      OS: Windows 11
      Qt: 6.9.1 (I also tested with Qt 6.7.2.)
      Tablet: Gaomon M6

      1 Reply Last reply
      0
      • Q Offline
        Q Offline
        qt-public-name
        wrote on last edited by
        #3

        I filed it as a bug here.

        1 Reply Last reply
        0

        • Login

        • Login or register to search.
        • First post
          Last post
        0
        • Categories
        • Recent
        • Tags
        • Popular
        • Users
        • Groups
        • Search
        • Get Qt Extensions
        • Unsolved