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
Forum Updated to NodeBB v4.3 + New Features

Tablet events behavior with stylus button click

Scheduled Pinned Locked Moved Unsolved General and Desktop
3 Posts 1 Posters 122 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 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 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 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