Linux平台获取用户在图形界面上(QT)的输入的方法

目前主要调研到了四个方法

代码1解析

#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

​ 核心代码位于Activity中,下面来逐语句分析

  • if(auto* const pDisplay = XOpenDisplay(nullptr)):

    nullptr是C++11标准语言用来表示空指针的常量值(https://zh.wikipedia.org/zh-hans/Nullptr);xopenDisplay方法用来与X server连接,函数原型为:Display *XOpenDisplay(char *display_name); display_name指定硬件的dispaly_name,当为null的时候默认指向DISPLAY环境变量。xopenDisplay返回一个与X serverl连接的结构体,当函数失败的时候返回null(if语句中 the null pointer is implicitly converted into boolean false)。

  • XRecordClientSpec clients = XRecordAllClients;

    在X11服务器编程中,XRecordAllClients是一个常量,用于指定记录所有客户端的X记录客户端规范(XRecordClientSpec)。这意味着在记录会话期间,X记录客户端将记录所有与X11服务器建立连接的客户端的请求和事件。

    XRecordClientSpec是一个结构体,用于指定要记录的客户端以及要记录的请求和事件类型。通过指定XRecordAllClients常量,可以记录所有客户端的请求和事件,而无需指定每个客户端的规范。但是,如果需要仅记录特定客户端的请求和事件,则可以指定单个客户端的规范,以及要记录的请求和事件类型。

  • auto* pRange = ::XRecordAllocRange();

    Rust对该函数的函数签名pub unsafe extern "C" fn XRecordAllocRange() -> *mut XRecordRange;该函数的返回值类型是一个指向XRecordRange结构体的可变指针(即C语言中的XRecordRange *类型),用于指向分配的XID值范围所在的内存地址。这意味着该函数分配了一块内存地址来存储XID值范围(在 X Window System 中,XID 是一种表示 X 服务器资源的唯一标识符,例如窗口、图形上下文、GC 等等。X Record 扩展允许记录并重放 X Window System 中的事件和请求。XRecordAllocRange() 函数是 X Record 扩展中的一个函数,用于在 X Window System 中分配一个 XID 值范围,以用于记录事件。)

  • pRange->device_events = XRecordRange8{KeyPress, ButtonRelease};

    device_events 是 XRecordRange 结构体中的一个字段,用于指定要记录的设备事件(device events)的 X11 按键值(keycode)范围。

    在 X Window System 中,每个设备(如键盘、鼠标、触摸板等)都会产生一些事件,这些事件被称为设备事件。例如,键盘事件包括按键按下和松开事件,鼠标事件包括鼠标按钮按下和松开事件、鼠标移动事件等等。XRecord 扩展可以记录这些设备事件以及其他 X11 协议的请求和事件。

    XRecordRange 结构体定义了一个要记录的 XID 值范围,其中 device_events 字段是一个 XRecordRange8 类型的值,用于指定要记录的设备事件的 X11 按键值范围。例如,如果您想要记录键盘按键按下事件和鼠标左键按下事件,您可以将 device_events 字段设置为一个 XRecordRange8 类型的值,该值包含 KeyPressButton1 按键值范围。

    在这个例子中,我们将 device_events 字段设置为一个包含两个输入事件的 X11 按键值范围,以指示 XRecord 扩展要记录键盘按键按下事件和鼠标按钮释放事件的 X11 按键值范围。

  • auto context = ::XRecordCreateContext(pDisplay, 0, &clients, 1, &pRange, 1);

    这段代码涉及到一个名为 XRecordCreateContext 的函数,它用于创建一个记录上下文(record context)并返回一个指向该上下文的指针。

    具体来说,该函数的第一个参数 pDisplay 是一个指向 Display 结构体的指针,它表示一个连接到 X 服务器的客户端程序的显示。第二个参数 major_opcode 是一个整数,它表示要记录的 X11 请求的主操作码(major opcode),通常设置为 0 表示记录所有请求。第三个参数 clients 是一个 XRecordClientSpec 类型的值,表示要记录的客户端(client)的 X11 识别号。第四个参数 num_clients 是一个整数,表示要记录的客户端数量。第五个参数 ranges 是一个指向 XRecordRange 结构体的指针数组,表示要记录的设备事件的范围。第六个参数 num_ranges 是一个整数,表示要记录的设备事件范围的数量。

    在这个例子中,我们传递了以下参数:pDisplay 表示连接到 X 服务器的客户端程序的显示;0 表示要记录所有请求;&clients 表示要记录的客户端的 X11 识别号;1 表示要记录一个客户端;&pRange 表示要记录的设备事件的范围;1 表示要记录一个设备事件范围。函数返回一个指向记录上下文的指针,该上下文用于记录 X11 请求和设备事件。

    总之,这段代码的作用是创建一个记录上下文,以便记录来自指定客户端和设备事件范围的 X11 请求和设备事件,并返回一个指向该上下文的指针。

  • ::XRecordEnableContextAsync(pDisplay, context, Handle, nullptr)

    XRecordEnableContext函数用于启用记录指定上下文的 X11 事件,它的函数原型为:Status **XRecordEnableContext**(Display *display, XRecordContext context, XRecordInterceptProc callback, XPointer closure);其中callback为“Specifies the function to be called for each protocol element received.”即截获到用户输入消息时候的回调函数。需要注意的是,这个函数会一直阻塞,所以需要在线程中调用。

​ 接下来我们来分析Handle函数

  • 首先来看一看Handle应该具有的函数原型:typedef void (*XRecordInterceptProc)(XPointer closure, XRecordInterceptData *recorded_data);

    XRecordInterceptData是一个结构体,具体为

  • using XRecordDatum = char;

    这里涉及到一个using关键字的知识点:using关键字可以取代typedef,用于类型重定义using alias = typename

  • std::cout << pRecord->category << "---" << pRecord->data;

    这一句比较简答就是一个打印语句,但是从上一条可以看出,data的类型是unsigned char * ,而cout对于unsigned char的输出有些奇怪,我们可以利用强制类型转换来(int *)输出,如果遇到程序无法正常进行的时候,考虑删除该行或者利用前面所说的强制类型转换

  • if(auto* const pDatum = reinterpret_cast<XRecordDatum*>(pRecord->data))

    这是一个类型转换,pDatum指向pRecord->data,并且将它视为XRecordDatum类型

  • CHECK(KeyPress); else CHECK(KeyRelease); else CHECK(ButtonPress); else CHECK(ButtonRelease);

    这个语句比较简单,就是利用宏来输出,不做过多解释

  • XRecordFreeData(pRecord)

    释放内存。XRecordFreeData用于释放记录数据所占用的内存。在 X Record 中,当记录数据不再需要时,应该使用XRecordFreeData 函数来释放内存,以避免内存泄漏。

    在这个上下文中,pRecord是一个指向 XRecord 的指针,它可能包含了记录数据。调用 XRecordFreeData(pRecord) 函数可以释放与pRecord相关联的记录数据所占用的内存。该函数将释放记录数据所占用的所有内存,并将记录数据指针设置为NULL。

    需要注意的是,在调用 XRecordFreeData 函数之前,应该确保不再需要记录数据,并且不应该再使用记录数据的任何部分。否则,可能会导致使用已释放的内存,从而引发未定义的行为或程序崩溃。

代码2解析

#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

​ 这段代码主要是一个基于 Qt 库的程序,它创建了一个 QGuiApplication 对象并安装了一个本地事件过滤器(native event filter)。

​ 本地事件过滤器是一种机制,它允许程序在处理 Qt 框架外的原生窗口系统事件(如 X11 的事件)时,进行事件拦截和处理。这个过滤器可以截获并处理底层的窗口 系统事件,以便应用程序可以监视和处理这些事件。

​ 这段代码的核心部分在EventFilter结构

  • EventFilter::EventFilter () { qApp->eventDispatcher()->installNativeEventFilter(this); }

    qApp:包含头文件QApplication和QCoreapplication的时候就可以使用qApp指向当前实例。(qApp是一个宏)

    这行代码大概意思就是注册一个过滤器?(对QT不太熟)

  • bool EventFilter::nativeEventFilter

    重写本地事件过滤器

    Event Filters

    Sometimes an object needs to look at, and possibly intercept, the events that are delivered to another object. For example, dialogs commonly want to filter key presses for some widgets; for example, to modify Return-key handling.

    The QObject::installEventFilter() function enables this by setting up an event filter, causing a nominated filter object to receive the events for a target object in its QObject::eventFilter() function. An event filter gets to process events before the target object does, allowing it to inspect and discard the events as required. An existing event filter can be removed using the QObject::removeEventFilter() function.

    When the filter object’s eventFilter() implementation is called, it can accept or reject the event, and allow or deny further processing of the event. If all the event filters allow further processing of an event (by each returning false), the event is sent to the target object itself. If one of them stops processing (by returning true), the target and any later event filters do not get to see the event at all.

    bool FilterObject::eventFilter(QObject *object, QEvent *event)
    {
    if (object == target && event->type() == QEvent::KeyPress) {
      QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
      if (keyEvent->key() == Qt::Key_Tab) {
          // Special tab handling
          return true;
      } else
          return false;
    }
    return false;
    }
    

    The above code shows another way to intercept Tab key press events sent to a particular target widget. In this case, the filter handles the relevant events and returns true to stop them from being processed any further. All other events are ignored, and the filter returns false to allow them to be sent on to the target widget, via any other event filters that are installed on it.

    It is also possible to filter all events for the entire application, by installing an event filter on the QApplication or QCoreApplication object. Such global event filters are called before the object-specific filters. This is very powerful, but it also slows down event delivery of every single event in the entire application; the other techniques discussed should generally be used instead.

​ Xrecord 和Eventfilter对比(前者为非侵入式,后者为侵入式)

在这里插入图片描述

xev 和 xinput

  • 权限问题?
  • 一次只监视一个窗口(Actually, xev should show you all events… it’s just that it only monitors one window at a time. Perhaps one could hack a script to launch multiple copies of xev each monitoring one window so that you end up monitoring all of them?)
  • xinput --test-xi2[device] Register for a number of XI2 events and display them. If a device is given, only events on this device are displayed.

​ 这个方法确实也能检测到用户输入,但是权限问题会不会发生呢?(按照评论里的解释好像root选项是只监视root窗口的意思*“However, with the -root option, you might be able to get xev to monitor the whole X session.”* <= This is wrong. This way, xev will only capture events on the “root window”, i.e. normally your desktop background. xinput is the correct solution for capturing all events regardless of the currently active window)

Qt_Monkey(这个挺有用的,需要可以进一步了解)

Qt_Monkey

json11格式bug? :fatal error :json11.hpp: No such file or dictionary --路径问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值