【问题标题】:How to capture keyboard & mouse events even when the Qt app is minimized / in background / out of focus?即使 Qt 应用程序最小化/在后台/失焦,如何捕获键盘和鼠标事件?
【发布时间】:2021-09-26 18:55:15
【问题描述】:

这里的要求只是检查是否按下了任何键或发生了任何鼠标点击或移动。不需要捕获其他具体细节。
我也可以每隔 1 秒进行一次轮询,看看是否发生了任何鼠标或按键事件。

QAbstractNativeEventFilter 有帮助吗?
任何其他独立于平台的 C++ 库也将很有用。


以下是仅在应用处于焦点时捕获鼠标和键盘事件的示例代码:

#include<QApplication>
#include<QDebug>
#include<QKeyEvent>
#include<QWidget>

struct Widget : public QWidget
{
  Widget ()
  {
    installEventFilter(this);
    grabKeyboard();
    grabMouse();
    setMouseTracking(true);
  }
  ~Widget () { qDebug() << "~Event()"; }

  bool eventFilter (QObject* const pObject,
                    QEvent* const pEvent) override
  {
    qDebug() << "Event: " << pEvent->type();
    if(pEvent->type() == QEvent::KeyPress)
    {
      QKeyEvent* const pKeyEvent = static_cast<QKeyEvent*>(pEvent);
      qDebug() << "Key event: " << pKeyEvent->key();
    }
    return false; //QObject::eventFilter(pObject, pEvent);
  }
};


int main (int argc, char *argv[])
{
  QApplication application(argc, argv);
  Widget widget;
  widget.show();
  return application.exec();
}

【问题讨论】:

  • 您真正想要的是一个计时器,结合特定于平台的方式来查询最后的用户输入。 QTimer 以与平台无关的方式解决了前者。后者没有与平台无关的实现。在 Windows 上,您会调用 GetLastInputInfo。在 Linux 上,您可以调用无数可用的不同实现中的任何一个。 Linux 上没有用于此的标准 API。
  • 欢迎您发表自己的答案。不过,您的问题应该仍然是一个严格的问题。

标签: c++ qt mouseevent keypress capture


【解决方案1】:

为以下 3 个平台编写了最少的工作代码。使用 Qt 是可选的。

#include<iostream>
#include<thread>
#include<chrono>

struct Activity
{
  Activity ();
};

// You may need to put Qt equivalent part here; The core logic is after main()
#include<QGuiApplication>
int
main (int argc, char *argv[])
{  // Qt specific; but it can be anything here
  QGuiApplication application(argc, argv);
  Activity activity;
  return application.exec();
}

#ifdef __linux__  // add in .pro file "linux: QT += x11extras" and "linux: LIBS += -lX11 -lXtst"
#include<X11/Xlib.h>
#include<X11/extensions/record.h>

#define CHECK(EVENT) if(*pDatum == EVENT) std::cout << #EVENT
void Handle (XPointer, XRecordInterceptData *pRecord)
{
  using XRecordDatum = char;
  std::cout << pRecord->category << "---" << pRecord->data;
  if(auto* const pDatum = reinterpret_cast<XRecordDatum*>(pRecord->data))
  { CHECK(KeyPress); else CHECK(KeyRelease); else CHECK(ButtonPress); else CHECK(ButtonRelease); }
  ::XRecordFreeData(pRecord);
}

Activity::Activity ()
{
  if(auto* const pDisplay = XOpenDisplay(nullptr))
  {
    XRecordClientSpec clients = XRecordAllClients;
    auto* pRange = ::XRecordAllocRange();
    pRange->device_events = XRecordRange8{KeyPress, ButtonRelease};
    auto context = ::XRecordCreateContext(pDisplay, 0, &clients, 1, &pRange, 1);
    ::XRecordEnableContextAsync(pDisplay, context, Handle, nullptr); // use with/without `...Async()`

    //  ::XRecordProcessReplies(pDisplay);
    ::XFlush(pDisplay);
    ::XSync(pDisplay, true);
  }
// Use below functions by putting variables in global scope to stop capturing
// Also refer: https://stackoverflow.com/questions/69711608/why-xrecorddisablecontext-is-not-working
// ::XRecordDisableContext(pDisplay, context);
//  ::XRecordFreeContext(pDisplay, context);
//  ::XFree(pRange);
}
#endif

#ifdef WIN32
#include<Windows.h> // add in .pro file "win32: LIBS += -luser32"

HHOOK sHookKeyboard, sHookMouse;
Activity::Activity ()
{
    sHookKeyboard = ::SetWindowsHookExA(WH_KEYBOARD_LL,
                     [] (int i, WPARAM w, LPARAM l)
                     { std::cout << i << w << l; return ::CallNextHookEx(sHookKeyboard, i, w, l); },
                                        0, 0);
    sHookMouse = ::SetWindowsHookExA(WH_MOUSE_LL,
                     [] (int i, WPARAM w, LPARAM l)
                     { std::cout << i << w << l; return ::CallNextHookEx(sHookMouse, i, w, l); },
                                        0, 0);
}
#endif

#ifdef __APPLE__ // add in .pro file "mac: LIBS += -framework ApplicationServices"
#include<ApplicationServices/ApplicationServices.h>
// Add binary (& if you are it via running Qt, then that too) into the "privacy" settings of the ...
// ... Mac system; System Preferences > Privacy settings; Without this the code will not work

// https://developer.apple.com/forums/thread/109283
// https://stackoverflow.com/questions/4556278/cgeventtapcreates-watching-keyboard-input-in-cocoa
Activity::Activity ()
{
  CGEventMask mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventScrollWheel) |
                     CGEventMaskBit(kCGEventLeftMouseDown) | CGEventMaskBit(kCGEventRightMouseDown);
  auto eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap,
                                   kCGEventTapOptionDefault, mask,
                       [] (CGEventTapProxy, CGEventType type, CGEventRef event, void*)
                       { std::cout << "captured: " << type; return event; }, this);
  if(eventTap == nullptr)
    return;

  CFRunLoopAddSource(CFRunLoopGetCurrent(),
                     CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0),
                     kCFRunLoopCommonModes);
  // Enable the event tap.
  CGEventTapEnable(eventTap, true);
}
#endif

【讨论】:

    【解决方案2】:

    您需要系统范围的鼠标钩子和 keyb 钩子。它应该在 Windows 中注册的独立 dll 中。然后你应该将它添加到 qt 。使用 installnativeeventfilter 并在可以使用纯 Windows C 代码(来自 msvc)的过滤器中执行 SetWindowsHookEx() 两次。来自 dll 的 Ofc 钩子内容应该在此过滤器模块中可见。总而言之,你做到这一点的机会并不多。我想你同意我的观点哈哈

    忘记平台无关的东西

    一些提示:
    对于钩子的回调函数使用CALLBACK type
    dll应该放在windows\system左右。在 Windows 7 中就足够了
    我敢打赌,您可以在原生过滤器的 .cpp 中执行 #pragma comment(lib, &lt;hook_lib&gt;) 。我的意思是.lib你会在制作.dll之后得到
    使用.def 进行导出。 https://docs.microsoft.com/en-us/cpp/build/exporting-from-a-dll-using-def-files?view=msvc-160

    【讨论】:

    • 你能给出一个代码示例吗?所有 3 个平台都需要此解决方案。
    • 原生过滤器可以看这里github.com/alexeyneu/bitcoin/blob/…
    • 我没有带有钩子的 app / dll 的来源。我在上面写的应用程序 - 我没有在 Windows 7 之后测试它。
    【解决方案3】:

    为以下三个平台编写了最少的代码:

    • Linux:非常简单。它当然会捕获 ctrlaltshift 等特殊键和鼠标点击。其他键盘输入有时会被捕获,有时则不会。这是不一致的。如果使用XGrabKeyboard() API,则捕获所有密钥。但是我不知道如何将这些键原样传递给可见的应用程序。

    • Windows:运行良好且简单明了!

    • Mac OSX:还不行。可能缺少一些东西。如果我发现了什么,我会更新。


    #include<QAbstractEventDispatcher>
    #include<QAbstractNativeEventFilter>
    #include<QGuiApplication>
    #include<QDebug>
    
    struct EventFilter : QAbstractNativeEventFilter
    {
      EventFilter ();
      bool nativeEventFilter (const QByteArray& eventType, void* pMessage, long*) override;
    };
    
    int main (int argc, char *argv[])
    {
      QGuiApplication application(argc, argv);
      EventFilter m_Filter;
      return application.exec();
    }
    
    #ifdef __linux__  // add in .pro file "linux: QT += x11extras" and "linux: LIBS += -lX11"
    #include<QX11Info>
    
    EventFilter::EventFilter () { qApp->eventDispatcher()->installNativeEventFilter(this); }
    
    bool EventFilter::nativeEventFilter (const QByteArray& eventType, void* pMessage, long*)
    {
      auto* const pEvent = static_cast<xcb_generic_event_t*>(pMessage);
      switch(pEvent->response_type)
      {
      case 28: case 37:        qDebug() << eventType << ": Keypress (special)" << pEvent->response_type; break;
      case 2: case 3: case 35: qDebug() << eventType << ": Keypress (general)"  << pEvent->response_type; break;
      case 85:                 qDebug() << eventType << ": Mouse Click"; break;
      default:                 qDebug() << eventType << ": <others> " << pEvent->response_type; break;
      }
      return false;
    }
    #endif
    
    #ifdef WIN32
    #include<Windows.h> // add in .pro file "win32: LIBS += -luser32"
    
    HHOOK sHookKeyboard, sHookMouse;
    EventFilter::EventFilter ()
    {
        sHookKeyboard = ::SetWindowsHookExA(WH_KEYBOARD_LL,
                         [] (int i, WPARAM w, LPARAM l)
                         { qDebug() << i << w << l; return ::CallNextHookEx(sHookKeyboard, i, w, l); },
                                            0, 0);
        sHookMouse = ::SetWindowsHookExA(WH_MOUSE_LL,
                         [] (int i, WPARAM w, LPARAM l)
                         { qDebug() << i << w << l; return ::CallNextHookEx(sHookMouse, i, w, l); },
                                            0, 0);
    }
    bool nativeEventFilter (const QByteArray& eventType, void* pMessage, long*) {}
    #endif
    
    #ifdef __APPLE__ // add in .pro file "mac: LIBS += -framework Carbon"
    #include<Carbon/Carbon.h>  // give required permission to the binary from "Application" settings
    
    OSStatus HandleKeyboard (EventHandlerCallRef nextHandler, EventRef event, void* data)
    { qDebug() << "KeyPress event raised: "; return noErr; }
    OSStatus HandleMouse (EventHandlerCallRef nextHandler, EventRef event, void* data)
    { qDebug() << "MouseClick event raised: "; return noErr; }
    
    EventFilter::EventFilter ()
    {
      EventTypeSpec eventKeyPress;
      eventKeyPress.eventClass = kEventClassKeyboard;
      eventKeyPress.eventKind = kEventRawKeyDown;
      InstallApplicationEventHandler(HandleKeyboard, 1, &eventKeyPress, nullptr, nullptr);
    
      EventTypeSpec eventMouseClick;
      eventMouseClick.eventClass = kEventClassMouse;
      eventMouseClick.eventKind = kEventMouseDown;
      InstallApplicationEventHandler(HandleMouse, 1, &eventMouseClick, nullptr, nullptr);
    }
    bool nativeEventFilter (const QByteArray& eventType, void* pMessage, long*) {}
    #endif
    

    【讨论】:

    • 您安装了一个应用程序过滤器,当应用程序最小化然后失去焦点时该过滤器将不起作用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-13
    相关资源
    最近更新 更多