XTestFakeButtonEvent& XSendEvent

时间:2017-02-02 21:56:05

标签: c++ linux google-chrome x11

我试图通过x11为ubuntu编写简单的鼠标点击器。

首先我写了点击程序的第一个变体(基于XSendEvent):

#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

void mouseClick(int button)
{
    Display *display = XOpenDisplay(NULL);

    XEvent event;

    if(display == NULL)
    {
        std::cout << "clicking error 0" << std::endl;
        exit(EXIT_FAILURE);
    }

    memset(&event, 0x00, sizeof(event));

    event.type = ButtonPress;
    event.xbutton.button = button;
    event.xbutton.same_screen = True;

    XQueryPointer(display, RootWindow(display, DefaultScreen(display)), &event.xbutton.root, &event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y, &event.xbutton.state);

    event.xbutton.subwindow = event.xbutton.window;

    while(event.xbutton.subwindow)
    {
        event.xbutton.window = event.xbutton.subwindow;
        XQueryPointer(display, event.xbutton.window, &event.xbutton.root, &event.xbutton.subwindow, &event.xbutton.x_root, &event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y, &event.xbutton.state);
    }

    if(XSendEvent(display, PointerWindow, True, 0xfff, &event) == 0)
        std::cout << "clicking error 1" << std::endl;

    XFlush(display);

    event.type = ButtonRelease;
    event.xbutton.state = 0x100;

    if(XSendEvent(display, PointerWindow, True, 0xfff, &event) == 0)
        std::cout << "clicking error 2" << std::endl;

    XFlush(display);

    XCloseDisplay(display);
}

此代码适用于除chrome之外的所有应用程序(Mozilla也可以正常工作)。

所以我写了第二个变体(基于XTestFakeButtonEvent):

#include <X11/extensions/XTest.h>

void SendClick(int button, Bool down) 
{
    Display *display = XOpenDisplay(NULL);
    XTestFakeButtonEvent(display, button, down, CurrentTime);
    XFlush(display);
    XCloseDisplay(display);
}

这段代码很好用,包括chrome。

调用这些函数非常简单

// XSendEvent variant
mouseClick(1);

// XTestFakeButtonEvent variant
SendClick(1, true);   // press lmb
SendClick(1, false);  // release lmb

1:帮助我理解我在第一个版本中做错了什么(或者铬有什么不对)。

1.1:我认为当我用XOpenDisplay(NULL)打开显示时,我试图发送不是所需窗口的事件; chrome与x11服务器有不同的连接系统吗?

2:在应用程序中使用第二个变体是个好主意吗?它很短,适用于我拥有的每个应用程序

P.S。要编译此代码,您需要添加-lX11 -lXtst libs

3 个答案:

答案 0 :(得分:2)

XSendEvent会生成标记为已发送的事件。服务器发送的事件未标记。

   typedef struct {
           int type;
           unsigned long serial; 
           Bool send_event;        // <----- here
           Display *display;
           Window window;
   } XAnyEvent;

出于安全原因,某些应用程序会忽略设置了此标志的事件。想想以某种方式访问​​您的X11服务器的恶意软件 - 它可以欺骗任何应用程序通过发送这些事件来做任何想做的事情。

在您自己的计算机上使用第二个变体是完全可以的,但它依赖于可以禁用的扩展(再次出于安全原因),因此不一定适用于其他人的X11服务器。

答案 1 :(得分:1)

在XCB上,您可以使用以下函数来验证事件是否是通过XSendEvent()/ xcb_send_event()API发送的:

static bool fromSendEvent(const void *event)
{
    // From X11 protocol: Every event contains an 8-bit type code. The most
    // significant bit in this code is set if the event was generated from
    // a SendEvent request.
    const xcb_generic_event_t *e = reinterpret_cast<const xcb_generic_event_t *>(event);
    return (e->response_type & 0x80) != 0;
}

AFACT,无法判断是否通过XTest扩展程序发送了事件。

你应该使用XTest,因为它会更好地工作,XSendEvents对内部X服务器状态一无所知。 Fom XSendEvent手册:

“除了强制send_event为True之外,X服务器不会更改事件的内容并取消选中。”

因此,使用XSendEvent,在某些情况下可能会出现意外问题。

答案 2 :(得分:0)

尽管不是直接使用Xlib,而是通过python Xlib包装器库作为Xlib的代理,但第一种方法确实适用于我当前在桌面上打开的所有窗口,而不是IntelliJ。

在这种第一种方法中,您将事件直接发送到目标窗口,并且as others have noted还用属性值标记(污染)了事件并将其标记为模拟事件。接收窗口可能会像许多应用程序窗口一样对它起作用。

但是,使用第二种方法,您正在模拟实际发生的事情-根据我的理解,这与用户请求的事件几乎没有区别:该事件通过用户输入事件的完整X11处理流程(而不是直接盲目调度)到目标窗口),这意味着它将像实际用户请求事件的自然事件流一样,下移到指针下方的窗口(或Gnome桌面小部件)。

因此,第二种方法似乎比第一种方法更广泛地适用-对于选择不对通过第一种方法发送给他们的事件采取行动的窗口以及例如对第一种方法不起作用的窗口也将具有预期的效果。 Gnome桌面元素本身不是普通的Windows(例如语言和功能小部件)。您无需提及任何窗口即可提供坐标,然后单击即可完成。

如果我不得不对这种路由的二重性做出某种解释,我可能会认为XSendEvent更像是一种通用事件发送工具,而XTEST提供了专门模拟 user的手段输入事件。