How to generate a mouse scroll event using XCB on X11 on Linux?

143 Views Asked by At

Trying to send a mouse wheel scroll to a specific window. It can be done with the XTest extension xdotool uses:

xdotool click 5

But this doesn't seem to target the specific window and doesn't work with the Xvfb X11 server.

I'm using this test QML application launched with qmlscene:

import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick.Window 2.2
import QtQuick.Layouts 1.15
import QtQuick.Controls.Styles 1.4

Window {
    width: 800
    height: 600

    ColumnLayout {
        anchors.fill: parent
        spacing: 10

        Button {
            text: "Button"
            Layout.alignment: Qt.AlignHCenter
        }

        SpinBox {
            Layout.fillWidth: true
            value: 50
            width: 200
        }

        ScrollView {
            Layout.fillWidth: true
            Layout.fillHeight: true

            TextArea {
                background: Rectangle {
                }
                text: "TextArea\nTextArea is a multi-line text editor. TextArea extends TextEdit with a placeholder text functionality, and adds decoration.\nTextArea is not scrollable by itself. Especially on screen-size constrained platforms, it is often preferable to make entire application pages scrollable. On such a scrollable page, a non-scrollable TextArea might behave better than nested scrollable controls. Notice, however, that in such a scenario, the background decoration of the TextArea scrolls together with the rest of the scrollable content.\n...\n...\n...\n...\nTextArea\nTextArea is a multi-line text editor. TextArea extends TextEdit with a placeholder text functionality, and adds decoration.\nTextArea is not scrollable by itself. Especially on screen-size constrained platforms, it is often preferable to make entire application pages scrollable. On such a scrollable page, a non-scrollable TextArea might behave better than nested scrollable controls. Notice, however, that in such a scenario, the background decoration of the TextArea scrolls together with the rest of the scrollable content.\n...\n...\n...\n...\nTextArea\nTextArea is a multi-line text editor. TextArea extends TextEdit with a placeholder text functionality, and adds decoration.\nTextArea is not scrollable by itself. Especially on screen-size constrained platforms, it is often preferable to make entire application pages scrollable. On such a scrollable page, a non-scrollable TextArea might behave better than nested scrollable controls. Notice, however, that in such a scenario, the background decoration of the TextArea scrolls together with the rest of the scrollable content."
            }
        }
    }
}

I've tried XCB with xinput:

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <xcb/xcb.h>
#include <xcb/xinput.h>

int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Error: one parameter expected: window id\n");
        return 1;
    }

    char *end;
    const unsigned long window_id = strtoul(argv[1], &end, 0);
    if (end != argv[1] + strlen(argv[1])) {
        fprintf(stderr, "Error: failed to parse window id\n");
        return 1;
    }

    xcb_connection_t *xcb_connection = xcb_connect(NULL, NULL);

    if (xcb_connection_has_error(xcb_connection)) {
        fprintf(stderr, "Error: xcb_connect() failed\n");
        return 1;
    }

    const xcb_setup_t* setup = xcb_get_setup(xcb_connection);
    xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
    xcb_screen_t* xcb_screen = iter.data;
    xcb_window_t root = xcb_screen->root;

    const xcb_query_extension_reply_t *reply = xcb_get_extension_data(xcb_connection, &xcb_input_id);
    if (!reply || !reply->present) {
        fprintf(stderr, "Error: xinput get_extension_data failed\n");
        return 1;
    }

    assert(reply->major_opcode == 131);

    xcb_input_motion_event_t event = {0};
    event.response_type = XCB_INPUT_MOTION;
    event.extension = reply->major_opcode;
    event.length = sizeof(event);
    event.detail = XCB_BUTTON_INDEX_5;
    event.root_x = 300;
    event.root_y = 300;
    event.event_x = 300;
    event.event_y = 300;

    xcb_send_event(xcb_connection, 0, window_id, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
    xcb_flush(xcb_connection);

    usleep(1000000);

    xcb_disconnect(xcb_connection);

    return 0;
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)

project(inject-x11 LANGUAGES C)

# Extra CMake Modules to find XCB.
find_package(ECM REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_FIND_MODULE_DIR})

find_package(XCB REQUIRED XCB XINPUT)

add_executable(${PROJECT_NAME})

target_sources(${PROJECT_NAME} PRIVATE
    main.c
)

target_link_libraries(${PROJECT_NAME} PRIVATE
    XCB::XINPUT
)

I've looked at the X11 protocol data with Wireshark:

socat tcp-listen:6004,reuseaddr,fork unix:/tmp/.X11-unix/X0
DISPLAY=localhost:4 qmlscene test.qml

When scrolling with the mouse there are some xinput GenericEvent messages are transferred (extension code 131 - the code of xcb_input). I can't replicate them with xcb_input. What's the method for simulating the mouse wheel properly?

0

There are 0 best solutions below