Как поймать нажатие 3+ дополнительных клавиш мыши?

Ya Zaregalsya

Известный
Автор темы
387
135
Приобрёл мышь с кучей кнопок (ЛКМ, ПКМ, СКМ и пять дополнительных). Так сложилось, что для нормального её использования мне необходимо переставить левый и правый клики на 3-ю и 4-ю доп. кнопки мыши. Так сейчас выглядят настройки драйвера:
KSB6M.png

Button 4-8 — это те самые доп. кнопки (далее будем называть их X/XBUTTON), всего их пять. Button 4 — это X1; Button 8 — это X5. Необходимо назначить клики именно на X3 и X4, ни одна другая для этой задачи не подходит. Собственно, в настройках это и сделано.

Но вот проблема. Как оказалось, Windows полноценно поддерживает не более двух дополнительных кнопок. Из-за этого X1 и X2 работают во всех приложениях, а X3, X4 и X5 работают только в половине, остальные их игнорируют.

Задача: Поймать нажатия этих кнопок в GTA SA. Хотелось бы конечно заставить их работать во всех приложениях, но если бы это было так легко, то это сделал бы производитель.

Препятствия: У меня почти нет опыта в работе с API Windows и драйверами, а решение проблемы находится за стенами интерфейсов, с которыми я имел дело. Вот есть список кодов клавиш, там есть X1 и X2, но нет X3, X4, X5. И всё, тут лоб врезается в стену.

1609146047111.png
Надежда: Как минимум в ГТА это точно возможно сделать. Очень много приложений работают с этими кнопками без нареканий. Далее несколько примеров.

Доп. кнопки поддерживаются: блокнот, раб. стол, Google Chrome, Visual Studio, Blender, NotePad++, MagicTXD.
Доп. кнопки не поддерживаются: диспетчер задач, редактор реестра, SciTe4AutoHotKey, клиент SAMP, GTA SA.

Код некоторых из них размещён в открытом доступе. Моих знаний, увы, пока недостаточно, чтобы быстро разобраться, за счёт чего они это делают. Но если более опытный разработчик взглянет на эти примеры, то наверно сможет выяснить ответ, MagicTXD — наиболее легковесный и близкий к ГТА образец. А пока что вместо мыши кирпич.
 

Ya Zaregalsya

Известный
Автор темы
387
135
Прогресс:

• Граф. интерфейс пользователя в MagicTXD реализован не голыми руками через какой-нибудь OpenGL, а через всем известный фреймворк Qt.

• У Qt есть своя вики, находим там страницу, где описаны все идентификаторы основного пространства имён. Первым делом внимание падает на перечисление MouseButton. Там указаны заветные дополнительные кнопки. Здесь уже прослеживается описанная выше логика: из всех доп. кнопок первые две — особенные.

• Там же указаны ExtraButton3 и ExtraButton4. Пока оставим в стороне почему третья кнопка имеет дополнительное название, но это неожиданно.

• Тут вступает в дело нехватка моей квалификации. Я никогда не работал с указателями в виде литералов, только под видом переменных. Пока предположу, что они обозначают смещение от какого-то другого адреса: XAddress + 0x00000020 = ExtraButton 3. Дальше нужно искать XAddress.
 

Ya Zaregalsya

Известный
Автор темы
387
135
Исходники Qt опубликованы на ГитХабе. Файлы по искомой теме:
qwindow.cpp/.h;
qeventloop.cpp/.h;
qabstracteventdistatcher.cpp/.h;
qcoreapplication.cpp/.h;
qguiapplication.cpp/.h;
qwindowsysteminterface.cpp/.h/
_p.h (судя по всему, здесь Qt захватывает события из Windows);
qcoreevent.cpp/.h (здесь реализован класс QEvent);
qevent.cpp/.h (QInputEvent, QMouseEvent);
qinputdevice.cpp/.h;

qwindowsmousehandler.cpp/.h;

https://code.woboq.org/qt5/qtbase/s...entLoop4execE6QFlagsINS_17ProcessEventsFlagEE

https://code.woboq.org/qt5/qtbase/src/gui/kernel/qguiapplication.cpp.html#1896 (а здесь Qt уже разбирает события Windows);

https://code.woboq.org/qt5/qtbase/s...SystemInterfacePrivate::WindowSystemEventList

https://code.woboq.org/qt5/qtbase/s...dowSystemInterfacePrivate17WindowSystemEventE


QEvent
|
QInputEvent
|
QPointerEvent
|
QSinglePointEvent
|
QMouseEvent

/*!
\class QEvent
\inmodule QtCore
\brief The QEvent class is the base class of all
event classes. Event objects contain event parameters.

\ingroup events

Qt's main event loop (QCoreApplication::exec()) fetches native
window system events from the event queue, translates them into
QEvents, and sends the translated events to \l{QObject}s.

In general, events come from the underlying window system
(spontaneous() returns \c true), but it is also possible to manually
send events using QCoreApplication::sendEvent() and

QCoreApplication::postEvent() (spontaneous() returns \c false).


\l {QObject}{QObjects} receive events by having their QObject::event() function
called. The function can be reimplemented in subclasses to
customize event handling and add additional event types;
QWidget::event() is a notable example. By default, events are
dispatched to event handlers like QObject::timerEvent() and
QWidget::mouseMoveEvent(). QObject::installEventFilter() allows an
object to intercept events destined for another object.

The basic QEvent contains only an event type parameter and an
"accept" flag. The accept flag set with accept(), and cleared
with ignore(). It is set by default, but don't rely on this as
subclasses may choose to clear it in their constructor.

Subclasses of QEvent contain additional parameters that describe
the particular event.

\sa QObject::event(), QObject::installEventFilter(),
QCoreApplication::sendEvent(),
QCoreApplication::postEvent(), QCoreApplication::processEvents()
*/

Detailed Description​

This class is used by non-GUI applications to provide their event loop. For non-GUI application that uses Qt, there should be exactly one QCoreApplication object. For GUI applications, see QGuiApplication. For applications that use the Qt Widgets module, see QApplication.

QCoreApplication contains the main event loop, where all events from the operating system (e.g., timer and network events) and other sources are processed and dispatched. It also handles the application's initialization and finalization, as well as system-wide and application-wide settings.


The Event Loop and Event Handling​

The event loop is started with a call to exec(). Long-running operations can call processEvents() to keep the application responsive.

In general, we recommend that you create a QCoreApplication, QGuiApplication or a QApplication object in your main() function as early as possible. exec() will not return until the event loop exits; e.g., when quit() is called.

Several static convenience functions are also provided. The QCoreApplication object is available from instance(). Events can be sent with sendEvent() or posted to an event queue with postEvent(). Pending events can be removed with removePostedEvents() or dispatched with sendPostedEvents().

The class provides a quit() slot and an aboutToQuit() signal.

C++:
void QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e)
{
    Q_TRACE_SCOPE(QGuiApplicationPrivate_processWindowSystemEvent, e->type);
    switch(e->type) {
    case QWindowSystemInterfacePrivate::Mouse:
        QGuiApplicationPrivate::processMouseEvent(static_cast<QWindowSystemInterfacePrivate::MouseEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Wheel:
        QGuiApplicationPrivate::processWheelEvent(static_cast<QWindowSystemInterfacePrivate::WheelEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Key:
        QGuiApplicationPrivate::processKeyEvent(static_cast<QWindowSystemInterfacePrivate::KeyEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Touch:
        QGuiApplicationPrivate::processTouchEvent(static_cast<QWindowSystemInterfacePrivate::TouchEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::GeometryChange:
        QGuiApplicationPrivate::processGeometryChangeEvent(static_cast<QWindowSystemInterfacePrivate::GeometryChangeEvent*>(e));
        break;
    case QWindowSystemInterfacePrivate::Enter:
        QGuiApplicationPrivate::processEnterEvent(static_cast<QWindowSystemInterfacePrivate::EnterEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Leave:
        QGuiApplicationPrivate::processLeaveEvent(static_cast<QWindowSystemInterfacePrivate::LeaveEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::ActivatedWindow:
        QGuiApplicationPrivate::processActivatedEvent(static_cast<QWindowSystemInterfacePrivate::ActivatedWindowEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::WindowStateChanged:
        QGuiApplicationPrivate::processWindowStateChangedEvent(static_cast<QWindowSystemInterfacePrivate::WindowStateChangedEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::WindowScreenChanged:
        QGuiApplicationPrivate::processWindowScreenChangedEvent(static_cast<QWindowSystemInterfacePrivate::WindowScreenChangedEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::SafeAreaMarginsChanged:
        QGuiApplicationPrivate::processSafeAreaMarginsChangedEvent(static_cast<QWindowSystemInterfacePrivate::SafeAreaMarginsChangedEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::ApplicationStateChanged: {
        QWindowSystemInterfacePrivate::ApplicationStateChangedEvent * changeEvent = static_cast<QWindowSystemInterfacePrivate::ApplicationStateChangedEvent *>(e);
        QGuiApplicationPrivate::setApplicationState(changeEvent->newState, changeEvent->forcePropagate); }
        break;
    case QWindowSystemInterfacePrivate::FlushEvents: {
        QWindowSystemInterfacePrivate::FlushEventsEvent *flushEventsEvent = static_cast<QWindowSystemInterfacePrivate::FlushEventsEvent *>(e);
        QWindowSystemInterface::deferredFlushWindowSystemEvents(flushEventsEvent->flags); }
        break;
    case QWindowSystemInterfacePrivate::Close:
        QGuiApplicationPrivate::processCloseEvent(
                static_cast<QWindowSystemInterfacePrivate::CloseEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::ScreenOrientation:
        QGuiApplicationPrivate::processScreenOrientationChange(
                static_cast<QWindowSystemInterfacePrivate::ScreenOrientationEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::ScreenGeometry:
        QGuiApplicationPrivate::processScreenGeometryChange(
                static_cast<QWindowSystemInterfacePrivate::ScreenGeometryEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::ScreenLogicalDotsPerInch:
        QGuiApplicationPrivate::processScreenLogicalDotsPerInchChange(
                static_cast<QWindowSystemInterfacePrivate::ScreenLogicalDotsPerInchEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::ScreenRefreshRate:
        QGuiApplicationPrivate::processScreenRefreshRateChange(
                static_cast<QWindowSystemInterfacePrivate::ScreenRefreshRateEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::ThemeChange:
        QGuiApplicationPrivate::processThemeChanged(
                    static_cast<QWindowSystemInterfacePrivate::ThemeChangeEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Expose:
        QGuiApplicationPrivate::processExposeEvent(static_cast<QWindowSystemInterfacePrivate::ExposeEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Tablet:
        QGuiApplicationPrivate::processTabletEvent(
                    static_cast<QWindowSystemInterfacePrivate::TabletEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::TabletEnterProximity:
        QGuiApplicationPrivate::processTabletEnterProximityEvent(
                    static_cast<QWindowSystemInterfacePrivate::TabletEnterProximityEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::TabletLeaveProximity:
        QGuiApplicationPrivate::processTabletLeaveProximityEvent(
                    static_cast<QWindowSystemInterfacePrivate::TabletLeaveProximityEvent *>(e));
        break;
#ifndef QT_NO_GESTURES
    case QWindowSystemInterfacePrivate::Gesture:
        QGuiApplicationPrivate::processGestureEvent(
                    static_cast<QWindowSystemInterfacePrivate::GestureEvent *>(e));
        break;
#endif
    case QWindowSystemInterfacePrivate::PlatformPanel:
        QGuiApplicationPrivate::processPlatformPanelEvent(
                    static_cast<QWindowSystemInterfacePrivate::PlatformPanelEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::FileOpen:
        QGuiApplicationPrivate::processFileOpenEvent(
                    static_cast<QWindowSystemInterfacePrivate::FileOpenEvent *>(e));
        break;
#ifndef QT_NO_CONTEXTMENU
        case QWindowSystemInterfacePrivate::ContextMenu:
        QGuiApplicationPrivate::processContextMenuEvent(
                    static_cast<QWindowSystemInterfacePrivate::ContextMenuEvent *>(e));
        break;
#endif
    case QWindowSystemInterfacePrivate::EnterWhatsThisMode:
        QGuiApplication::postEvent(QGuiApplication::instance(), new QEvent(QEvent::EnterWhatsThisMode));
        break;
    default:
        qWarning() << "Unknown user input event type:" << e->type;
        break;
    }
}


C++:
void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent *e)
{
    QEvent::Type type = QEvent::None;
    Qt::MouseButton button = Qt::NoButton;
    QWindow *window = e->window.data();
    bool positionChanged = QGuiApplicationPrivate::lastCursorPosition != e->globalPos;
    bool mouseMove = false;
    bool mousePress = false;
    if (e->enhancedMouseEvent()) {
        type = e->buttonType;
        button = e->button;
        if (type == QEvent::NonClientAreaMouseMove || type == QEvent::MouseMove)
            mouseMove = true;
        else if (type == QEvent::NonClientAreaMouseButtonPress || type == QEvent::MouseButtonPress)
            mousePress = true;
        if (!mouseMove && positionChanged) {
            QWindowSystemInterfacePrivate::MouseEvent moveEvent(window, e->timestamp,
                e->localPos, e->globalPos, e->buttons ^ button, e->modifiers, Qt::NoButton,
                e->nonClientArea ? QEvent::NonClientAreaMouseMove : QEvent::MouseMove,
                e->source, e->nonClientArea);
            if (e->synthetic())
                moveEvent.flags |= QWindowSystemInterfacePrivate::WindowSystemEvent::Synthetic;
            processMouseEvent(&moveEvent); // mouse move excluding state change
            processMouseEvent(e); // the original mouse event
            return;
        }
    } else {
        Qt::MouseButtons stateChange = e->buttons ^ mouse_buttons;
        if (positionChanged && (stateChange != Qt::NoButton)) {
            QWindowSystemInterfacePrivate::MouseEvent moveEvent(window, e->timestamp, e->localPos,
                e->globalPos, mouse_buttons, e->modifiers, Qt::NoButton, QEvent::None, e->source,
                e->nonClientArea);
            if (e->synthetic())
                moveEvent.flags |= QWindowSystemInterfacePrivate::WindowSystemEvent::Synthetic;
            processMouseEvent(&moveEvent); // mouse move excluding state change
            processMouseEvent(e); // the original mouse event
            return;
        }
        // In the compatibility path we deduce event type and button that caused the event
        if (positionChanged) {
            mouseMove = true;
            type = e->nonClientArea ? QEvent::NonClientAreaMouseMove : QEvent::MouseMove;
        } else {
            // Check to see if a new button has been pressed/released.
            for (uint mask = Qt::LeftButton; mask <= Qt::MaxMouseButton; mask <<= 1) {
                if (stateChange & mask) {
                    button = Qt::MouseButton(mask);
                    break;
                }
            }
            if (button == Qt::NoButton) {
                // Ignore mouse events that don't change the current state. This shouldn't
                // really happen, getting here can only mean that the stored button state
                // is out of sync with the actual physical button state.
                return;
            }
            if (button & e->buttons) {
                mousePress = true;
                type = e->nonClientArea ? QEvent::NonClientAreaMouseButtonPress
                                        : QEvent::MouseButtonPress;
             } else {
                type = e->nonClientArea ? QEvent::NonClientAreaMouseButtonRelease
                                        : QEvent::MouseButtonRelease;
            }
        }
    }
    modifier_buttons = e->modifiers;
    QPointF localPoint = e->localPos;
    QPointF globalPoint = e->globalPos;
    bool doubleClick = false;
    if (mouseMove) {
        QGuiApplicationPrivate::lastCursorPosition = globalPoint;
        const auto doubleClickDistance = e->source == Qt::MouseEventNotSynthesized ?
                    mouseDoubleClickDistance : touchDoubleTapDistance;
        if (qAbs(globalPoint.x() - mousePressX) > doubleClickDistance ||
            qAbs(globalPoint.y() - mousePressY) > doubleClickDistance)
            mousePressButton = Qt::NoButton;
    } else {
        mouse_buttons = e->buttons;
        if (mousePress) {
            ulong doubleClickInterval = static_cast<ulong>(QGuiApplication::styleHints()->mouseDoubleClickInterval());
            doubleClick = e->timestamp - mousePressTime < doubleClickInterval && button == mousePressButton;
            mousePressTime = e->timestamp;
            mousePressButton = button;
            const QPoint point = QGuiApplicationPrivate::lastCursorPosition.toPoint();
            mousePressX = point.x();
            mousePressY = point.y();
        }
    }
    if (e->nullWindow()) {
        window = QGuiApplication::topLevelAt(globalPoint.toPoint());
        if (window) {
            // Moves and the release following a press must go to the same
            // window, even if the cursor has moved on over another window.
            if (e->buttons != Qt::NoButton) {
                if (!currentMousePressWindow)
                    currentMousePressWindow = window;
                else
                    window = currentMousePressWindow;
            } else if (currentMousePressWindow) {
                window = currentMousePressWindow;
                currentMousePressWindow = 0;
            }
            QPointF delta = globalPoint - globalPoint.toPoint();
            localPoint = window->mapFromGlobal(globalPoint.toPoint()) + delta;
        }
    }
    if (!window)
        return;
#ifndef QT_NO_CURSOR
    if (!e->synthetic()) {
        if (const QScreen *screen = window->screen())
            if (QPlatformCursor *cursor = screen->handle()->cursor()) {
                const QPointF nativeLocalPoint = QHighDpi::toNativePixels(localPoint, screen);
                const QPointF nativeGlobalPoint = QHighDpi::toNativePixels(globalPoint, screen);
                QMouseEvent ev(type, nativeLocalPoint, nativeLocalPoint, nativeGlobalPoint,
                                          button, e->buttons, e->modifiers, e->source);
                ev.setTimestamp(e->timestamp);
                cursor->pointerEvent(ev);
            }
    }
#endif
    QMouseEvent ev(type, localPoint, localPoint, globalPoint, button, e->buttons, e->modifiers, e->source);
    ev.setTimestamp(e->timestamp);
    if (window->d_func()->blockedByModalWindow && !qApp->d_func()->popupActive()) {
        // a modal window is blocking this window, don't allow mouse events through
        return;
    }
    if (doubleClick && (ev.type() == QEvent::MouseButtonPress)) {
        // QtBUG-25831, used to suppress delivery in qwidgetwindow.cpp
        setMouseEventFlags(&ev, ev.flags() | Qt::MouseEventCreatedDoubleClick);
    }
    QGuiApplication::sendSpontaneousEvent(window, &ev);
    e->eventAccepted = ev.isAccepted();
    if (!e->synthetic() && !ev.isAccepted()
        && !e->nonClientArea
        && qApp->testAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents)) {
        if (!m_fakeTouchDevice) {
            m_fakeTouchDevice = new QTouchDevice;
            QWindowSystemInterface::registerTouchDevice(m_fakeTouchDevice);
        }
        QList<QWindowSystemInterface::TouchPoint> points;
        QWindowSystemInterface::TouchPoint point;
        point.id = 1;
        point.area = QRectF(globalPoint.x() - 2, globalPoint.y() - 2, 4, 4);
        // only translate left button related events to
        // avoid strange touch event sequences when several
        // buttons are pressed
        if (type == QEvent::MouseButtonPress && button == Qt::LeftButton) {
            point.state = Qt::TouchPointPressed;
        } else if (type == QEvent::MouseButtonRelease && button == Qt::LeftButton) {
            point.state = Qt::TouchPointReleased;
        } else if (type == QEvent::MouseMove && (e->buttons & Qt::LeftButton)) {
            point.state = Qt::TouchPointMoved;
        } else {
            return;
        }
        points << point;
        QEvent::Type type;
        QList<QTouchEvent::TouchPoint> touchPoints =
                QWindowSystemInterfacePrivate::fromNativeTouchPoints(points, window, QTouchDevicePrivate::get(m_fakeTouchDevice)->id, &type);
        QWindowSystemInterfacePrivate::TouchEvent fake(window, e->timestamp, type, m_fakeTouchDevice, touchPoints, e->modifiers);
        fake.flags |= QWindowSystemInterfacePrivate::WindowSystemEvent::Synthetic;
        processTouchEvent(&fake);
    }
    if (doubleClick) {
        mousePressButton = Qt::NoButton;
        if (!e->window.isNull() || e->nullWindow()) { // QTBUG-36364, check if window closed in response to press
            const QEvent::Type doubleClickType = e->nonClientArea ? QEvent::NonClientAreaMouseButtonDblClick : QEvent::MouseButtonDblClick;
            QMouseEvent dblClickEvent(doubleClickType, localPoint, localPoint, globalPoint,
                                      button, e->buttons, e->modifiers, e->source);
            dblClickEvent.setTimestamp(e->timestamp);
            QGuiApplication::sendSpontaneousEvent(window, &dblClickEvent);
        }
    }
}
 
Последнее редактирование:

Ya Zaregalsya

Известный
Автор темы
387
135
Я провозился с этими кнопками месяц, потратил 60-70 часов времени, разочаровался в IT, в человечестве, проклял всю Кремневую долину и японскую нацию, готовился изучать микроконтроллеры и писать драйвер с нуля.

Оказывается, нужно было всего лишь запустить софт для мыши от имени администратора.

Спасибо всем специалистам Бластхака за неоценимую помощь!
 
  • Нравится
Реакции: Nishikinov