3

I'm wondering how can I write a code to monitor the mouse buttons globally. This would be for OS X, and I'd like to try writing it in Qt/C++.

To begin with I don't know how to capture those global events. The monitor application would not display a GUI, it'd simply be a process that runs in the background and detects mouse buttons being clicked.

In the second part of the program I would like to launch hot-keys depending of the mouse key pressed.

My final idea is make a free program like steerMouse, just to figure out how it could be done.

I'm asking for a guidance of where to start - how can I detect the mouse button events globally?

4

2 回答 2

8

仅使用 Qt 是不可能的。还有一个问题详细说明了这些问题。它归结为:

  1. 安装一个事件过滤器QApplication将让您在光标位于任何应用程序窗口上时接收鼠标事件,但不能在它之外。这对你的情况没有帮助。

  2. 如果一个小部件使用 抓取鼠标grabMouse(),它将全局接收所有鼠标事件,但与其他应用程序的交互变得不可能。

因此,您需要使用特定于平台的 API 来执行此操作 - 这意味着 Cocoa 和使用 Objective C/C++ 编写。有一个问题的答案很好,除了 Qt 集成之外,它几乎提供了我们需要的一切。

如下所示,缺少的部分是将独立代码与 Qt 集成。这段代码显示了一个空的小部件,只是为了证明我们正确地处理了我们的应用程序及其外部的鼠标事件。

这是一个使用 Cocoa 的完整的工作示例。它需要进入一个.mm文件;不要忘记将它添加到OBJECTIVE_SOURCES您的 qmake 项目文件中(不要添加到SOURCES!)。

不幸的是,没有一个函数/方法可以从 转换NSEventQMouseEvent. 最好的办法是从qnsview.mm. 这是不幸的,但这是 Qt 平台抽象设计的结果:平台代码最终调用QWindowSystemInterface::handleMouseEvent(....)以将事件发布到应用程序。

#include <QApplication>
#include <QAbstractNativeEventFilter>
#include <QTextStream>
#include <QWidget>
#include <cstdio>
#import <AppKit/AppKit.h>

QTextStream out(stdout);

class MyEventFilter : public QAbstractNativeEventFilter {
public:
    bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) {
        Q_UNUSED(eventType) Q_UNUSED(result)
        NSEvent * event = (NSEvent*)message;
        switch ([event type]) {
        case NSLeftMouseDown:
            out << "Lv"; break;
        case NSLeftMouseUp:
            out << "L^"; break;
        case NSRightMouseDown:
            out << "Rv"; break;
        case NSRightMouseUp:
            out << "R^"; break;
        case NSOtherMouseDown:
            out << [event buttonNumber] << "v"; break;
        case NSOtherMouseUp:
            out << [event buttonNumber] << "^"; break;
        default:
            return false;
        }
        out << endl;
        return false;
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QSharedPointer<QAbstractNativeEventFilter> filter(new MyEventFilter);
    const int mask =
            NSLeftMouseDownMask | NSLeftMouseUpMask |
            NSRightMouseDownMask | NSRightMouseUpMask |
            NSOtherMouseDownMask | NSOtherMouseUpMask;
    // The global monitoring handler is *not* called for events sent to our application
    id monitorId = [NSEvent addGlobalMonitorForEventsMatchingMask:mask handler:^(NSEvent* event) {
        filter->nativeEventFilter("NSEvent", event, 0);
    }];
    // We also need to handle events coming to our application
    a.installNativeEventFilter(filter.data());
    QWidget w;
    w.show();
    int rc = a.exec();
    [NSEvent removeMonitor:monitorId];
    return rc;
}
于 2013-10-07T17:28:06.860 回答
1

听起来你想在 OSX 上挂钩全局鼠标事件。

我已经在 Windows 中完成了它,并取得了巨大的成功。我知道要寻找什么。

这是我在快速搜索后能找到的最好的东西:

https://code.google.com/p/jnativehook/

https://code.google.com/p/jnativehook/source/browse/branches/1.1/src/native/osx/NativeThread.c

基本上,JNativeHook 执行以下操作:

它创建 ac 线程,并正确回调处理鼠标的系统函数。由于鼠标(和键盘)由系统处理,回调获取信息。然后通过回调将信息转发到代码的 java 端。

您需要创建一个线程,将其正确连接到系统,然后将信息发送到您想要记录或显示它的位置。超过 90% 的工作是在上面的 NativeThread.c 链接中完成的。这是它的一些关键部分。

第 305 到 552 行有以下内容:

switch (type) {
//...
case kCGEventLeftMouseDown:
        button = kVK_LBUTTON;
        SetModifierMask(kCGEventFlagMaskButtonLeft);
        goto BUTTONDOWN;

case kCGEventRightMouseDown:
        button = kVK_RBUTTON;
        SetModifierMask(kCGEventFlagMaskButtonRight);
        goto BUTTONDOWN;

case kCGEventOtherMouseDown:
        button = CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber);

        if (button == kVK_MBUTTON) {
                SetModifierMask(kCGEventFlagMaskButtonCenter);
        }
        else if (button == kVK_XBUTTON1) {
                SetModifierMask(kCGEventFlagMaskXButton1);
        }
        else if (button == kVK_XBUTTON2) {
                SetModifierMask(kCGEventFlagMaskXButton2);
        }
BUTTONDOWN:
        #ifdef DEBUG
        fprintf(stdout, "LowLevelProc(): Button Pressed (%i)\n", (unsigned int) button);
        #endif

        // Track the number of clicks.
        #ifdef DEBUG
        fprintf(stdout, "LowLevelProc(): Click Time (%lli)\n", (CGEventGetTimestamp(event) - click_time)  / 1000000);
        #endif

        if ((long) (CGEventGetTimestamp(event) - click_time) / 1000000 <= GetMultiClickTime()) {
                click_count++;
        }
        else {
                click_count = 1;
        }
        click_time = CGEventGetTimestamp(event);

        event_point = CGEventGetLocation(event);
        jbutton = NativeToJButton(button);
        jmodifiers = NativeToJEventMask(GetModifiers());

        // Fire mouse pressed event.
        objMouseEvent = (*env)->NewObject(
                                                                env,
                                                                clsMouseEvent,
                                                                idMouseButtonEvent,
                                                                org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_PRESSED,
                                                                (jlong) event_time,
                                                                jmodifiers,
                                                                (jint) event_point.x,
                                                                (jint) event_point.y,
                                                                (jint) click_count,
                                                                jbutton);
        (*env)->CallVoidMethod(env, objGlobalScreen, idDispatchEvent, objMouseEvent);
        (*env)->DeleteLocalRef(env, objMouseEvent);
        break;

case kCGEventLeftMouseUp:
        button = kVK_LBUTTON;
        UnsetModifierMask(kCGEventFlagMaskButtonLeft);
        goto BUTTONUP;

case kCGEventRightMouseUp:
        button = kVK_RBUTTON;
        UnsetModifierMask(kCGEventFlagMaskButtonRight);
        goto BUTTONUP;

case kCGEventOtherMouseUp:
        button = CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber);

        if (button == kVK_MBUTTON) {
                UnsetModifierMask(kCGEventFlagMaskButtonCenter);
        }
        else if (button == kVK_XBUTTON1) {
                UnsetModifierMask(kCGEventFlagMaskXButton1);
        }
        else if (button == kVK_XBUTTON2) {
                UnsetModifierMask(kCGEventFlagMaskXButton2);
        }

BUTTONUP:
        #ifdef DEBUG
        fprintf(stdout, "LowLevelProc(): Button Released (%i)\n", (unsigned int) button);
        #endif

        event_point = CGEventGetLocation(event);
        jbutton = NativeToJButton(button);
        jmodifiers = NativeToJEventMask(GetModifiers());

        // Fire mouse released event.
        objMouseEvent = (*env)->NewObject(
                                                                env,
                                                                clsMouseEvent,
                                                                idMouseButtonEvent,
                                                                org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_RELEASED,
                                                                (jlong) event_time,
                                                                jmodifiers,
                                                                (jint) event_point.x,
                                                                (jint) event_point.y,
                                                                (jint) click_count,
                                                                jbutton);
        (*env)->CallVoidMethod(env, objGlobalScreen, idDispatchEvent, objMouseEvent);
        (*env)->DeleteLocalRef(env, objMouseEvent);

        if (mouse_dragged != true) {
                // Fire mouse clicked event.
                objMouseEvent = (*env)->NewObject(
                                                                        env,
                                                                        clsMouseEvent,
                                                                        idMouseButtonEvent,
                                                                        org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_CLICKED,
                                                                        (jlong) event_time,
                                                                        jmodifiers,
                                                                        (jint) event_point.x,
                                                                        (jint) event_point.y,
                                                                        (jint) click_count,
                                                                        jbutton);
                (*env)->CallVoidMethod(env, objGlobalScreen, idDispatchEvent, objMouseEvent);
                (*env)->DeleteLocalRef(env, objMouseEvent);
        }
        break;


case kCGEventLeftMouseDragged:
case kCGEventRightMouseDragged:
case kCGEventOtherMouseDragged:
        event_point = CGEventGetLocation(event);

        #ifdef DEBUG
        fprintf(stdout, "LowLevelProc(): Motion Notified (%f, %f)\n", event_point.x, event_point.y);
        #endif

        // Reset the click count.
        if (click_count != 0 && (long) (CGEventGetTimestamp(event) - click_time) / 1000000 > GetMultiClickTime()) {
                click_count = 0;
        }
        jmodifiers = NativeToJEventMask(GetModifiers());

        // Set the mouse dragged flag.
        mouse_dragged = true;

        // Fire mouse dragged event.
        objMouseEvent = (*env)->NewObject(
                                                                env,
                                                                clsMouseEvent,
                                                                idMouseMotionEvent,
                                                                org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_DRAGGED,
                                                                (jlong) event_time,
                                                                jmodifiers,
                                                                (jint) event_point.x,
                                                                (jint) event_point.y,
                                                                (jint) click_count);
        (*env)->CallVoidMethod(env, objGlobalScreen, idDispatchEvent, objMouseEvent);
        (*env)->DeleteLocalRef(env, objMouseEvent);
        break;

case kCGEventMouseMoved:
        event_point = CGEventGetLocation(event);
        #ifdef DEBUG
        fprintf(stdout, "LowLevelProc(): Motion Notified (%f, %f)\n", event_point.x, event_point.y);
        #endif

        // Reset the click count.
        if (click_count != 0 && (long) (CGEventGetTimestamp(event) - click_time) / 1000000 > GetMultiClickTime()) {
                click_count = 0;
        }
        jmodifiers = NativeToJEventMask(GetModifiers());

        // Set the mouse dragged flag.
        mouse_dragged = false;

        // Fire mouse moved event.
        objMouseEvent = (*env)->NewObject(
                                                                env,
                                                                clsMouseEvent,
                                                                idMouseMotionEvent,
                                                                org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_MOVED,
                                                                (jlong) event_time,
                                                                jmodifiers,
                                                                (jint) event_point.x,
                                                                (jint) event_point.y,
                                                                (jint) click_count);
        (*env)->CallVoidMethod(env, objGlobalScreen, idDispatchEvent, objMouseEvent);
        (*env)->DeleteLocalRef(env, objMouseEvent);
        break;

case kCGEventScrollWheel:
        event_point = CGEventGetLocation(event);

        // TODO Figure out of kCGScrollWheelEventDeltaAxis2 causes mouse events with zero rotation.
        if (CGEventGetIntegerValueField(event, kCGScrollWheelEventIsContinuous) == 0) {
                jscrollType = (jint)  org_jnativehook_mouse_NativeMouseWheelEvent_WHEEL_UNIT_SCROLL;
        }
        else {
                jscrollType = (jint)  org_jnativehook_mouse_NativeMouseWheelEvent_WHEEL_BLOCK_SCROLL;
        }

        // Scrolling data uses a fixed-point 16.16 signed integer format (Ex: 1.0 = 0x00010000).
        jwheelRotation = (jint) CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis1) * -1;

        /* TODO Figure out the scroll wheel amounts are correct.  I
        * suspect that Apples Java implementation maybe reporting a
        * static "1" inaccurately.
        */
        jscrollAmount = (jint) CGEventGetIntegerValueField(event, kCGScrollWheelEventPointDeltaAxis1) * -1;

        #ifdef DEBUG
        fprintf(stdout, "LowLevelProc(): Mouse Wheel Moved (%i, %i, %i)\n", (int) jscrollType, (int) jscrollAmount, (int) jwheelRotation);
        #endif

        // Track the number of clicks.
        if ((long) (CGEventGetTimestamp(event) - click_time) / 1000000 <= GetMultiClickTime()) {
                click_count++;
        }
        else {
                click_count = 1;
        }
        click_time = CGEventGetTimestamp(event);

        jmodifiers = NativeToJEventMask(GetModifiers());

        // Fire mouse wheel event.
        objMouseWheelEvent = (*env)->NewObject(
                                                                        env,
                                                                        clsMouseWheelEvent,
                                                                        idMouseWheelEvent,
                                                                        org_jnativehook_mouse_NativeMouseEvent_NATIVE_MOUSE_WHEEL,
                                                                        (jlong) event_time,
                                                                        jmodifiers,
                                                                        (jint) event_point.x,
                                                                        (jint) event_point.y,
                                                                        (jint) click_count,
                                                                        jscrollType,
                                                                        jscrollAmount,
                                                                        jwheelRotation);
        (*env)->CallVoidMethod(env, objGlobalScreen, idDispatchEvent, objMouseWheelEvent);
        (*env)->DeleteLocalRef(env, objMouseWheelEvent);
        break;

#ifdef DEBUG
default:
        fprintf(stderr, "LowLevelProc(): Unhandled Event Type: 0x%X\n", type);
        break;
#endif
}

那应该让你开始。

希望有帮助。

于 2013-10-07T17:30:01.820 回答