linux 使用Xcb监听键盘鼠标输入

前言

在现代Linux桌面开发中,高效、低延迟的输入事件处理至关重要。XCB作为X11协议的现代化接口,不仅提供更优的性能,还通过其无状态、异步的设计为输入监听带来了革命性的改进。

本文将深入探讨如何使用XCB实现高性能的全局键盘鼠标输入监听,构建响应灵敏的桌面应用。

X11输入事件系统架构

基础键鼠事件监听

XCB原生事件同样不支持键盘鼠标的全局监听,因此,同样选择通过额外的扩展来实现监听。

2.1 键码解析

由于XCB库没有提供完整的键盘映射函数,而事件KeyCode和修饰符状态,与X11完全一样,因此直接使用X11的转换函数即可。

高级键鼠事件监听

3.1 XInput扩展
cpp 复制代码
int main()
{
    xcb_connection_t* conn = xcb_connect(NULL, NULL);
    if (xcb_connection_has_error(conn)) {
        fprintf(stderr, "XCB连接失败\n");
        return -1;
    }

    xcb_prefetch_extension_data(conn, &xcb_input_id);
    auto record_ext = xcb_get_extension_data(conn, &xcb_input_id);
    if (!record_ext || !record_ext->present) {
        fprintf(stderr, "XInput 扩展不可用\n");
        return -1;
    }

    auto root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;

    struct xcb_input_event_mask_extend {
        xcb_input_event_mask_t header;
        unsigned mask;
    } event_mask = {XCB_INPUT_DEVICE_ALL,
                    1,
                    XCB_INPUT_XI_EVENT_MASK_KEY_PRESS | XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS};

    xcb_input_xi_select_events(conn, root, 1, (const xcb_input_event_mask_t*)&event_mask);
    xcb_flush(conn);

    while (1) {
        std::unique_ptr<xcb_generic_event_t> event(
            xcb_wait_for_event(conn));
        if (event->response_type != XCB_GE_GENERIC) {
            continue;
        }

        switch (((xcb_ge_generic_event_t*)event.get())->event_type) {
        case XCB_KEY_PRESS: {
            auto key_event = (xcb_input_key_press_event_t*)event.get();
            printf("=== Mouse Button Press ===\n");
            printf("  Button: %d\n", key_event->detail);
            printf("  Root Coordinates: (%f, %f)\n", key_event->root_x >> 16,
                   key_event->root_y >> 16);

        } break;
        case XCB_BUTTON_PRESS: {
            auto btn_event = (xcb_input_button_press_event_t*)event.get();
            printf("=== Key Press ===\n");
            printf("  Key: %d\n", btn_event->detail);
            printf("  Mask: %d\n", btn_event->mods.effective);

        } break;
        }
    }

    xcb_disconnect(conn);
    return 0;
}
3.2 Record扩展
cpp 复制代码
int main() {
    xcb_connection_t* connection = xcb_connect(NULL, NULL);
    if (xcb_connection_has_error(connection)) {
        fprintf(stderr, "XCB连接失败\n");
        return -1;
    }

    xcb_prefetch_extension_data(connection, &xcb_record_id);
    const xcb_query_extension_reply_t* record_ext =
        xcb_get_extension_data(connection, &xcb_record_id);
    if (!record_ext || !record_ext->present) {
        fprintf(stderr, "RECORD 扩展不可用\n");
        return -1;
    }

    xcb_record_client_spec_t clients = XCB_RECORD_CS_ALL_CLIENTS;
    xcb_record_context_t context = xcb_generate_id(connection);
    xcb_record_range_t range;
    memset(&range, 0, sizeof(range));
    range.device_events.first = XCB_KEY_PRESS;
    range.device_events.last = XCB_BUTTON_PRESS;
    xcb_void_cookie_t create_cookie = xcb_record_create_context(
        connection, context, 0, 1, 1, &clients, &range);
    xcb_flush(connection);

    xcb_record_enable_context_cookie_t enable_cookie =
        xcb_record_enable_context(connection, context);
    std::unique_ptr<xcb_record_enable_context_reply_t> reply;
    while (1) {
        reply.reset(xcb_record_enable_context_reply(connection,
                                                    enable_cookie, NULL));

        uint8_t* data = xcb_record_enable_context_data(reply.get());
        int len = xcb_record_enable_context_data_length(reply.get());

        xcb_generic_event_t* event = (xcb_generic_event_t*)data;
        while ((uint8_t*)event < data + len) {
            switch (event->response_type & ~0x80) {
            case XCB_BUTTON_PRESS: {
                auto btn_event = (xcb_button_press_event_t*)event;
                printf("=== Mouse Button Press ===\n");
                printf("  Button: %d\n", btn_event->detail);
                printf("  Root Coordinates: (%f, %f)\n", btn_event->root_x, btn_event->root_y);
            } break;
            case XCB_KEY_PRESS: {
                auto key_event = (xcb_key_press_event_t*)event;
                printf("=== Key Press ===\n");
                printf("  Key: %d\n", key_event->detail);
                printf("  Mask: %d\n", key_event->state);
            } break;
            }
            event += 1;
        }
    }
    xcb_disconnect(connection);
    return 0;
}

结语

虽然XInput和Record都是同步阻塞的,但是XInput直接调用xcb_wait_for_event等待事件到来,而Record则依赖底层的xcb_wait_for_reply响应请求。

由于XInput可以通过xcb_send_event模拟事件来唤醒xcb_wait_for_event,而xcb_wait_for_reply目前暂未找到合适的方法,因此更推荐使用XInput。

相关推荐
ch3nyuyu11 分钟前
网络编程拟面试题
linux·网络
无限进步_19 分钟前
【Linux】Makefile:让编译自动化
linux·运维·自动化
猫头虎21 分钟前
【Trea】Trea国内版|国际版|海外版下载|Mac版|Windows版|Linux下载配置教程
linux·人工智能·windows·macos·aigc·ai编程·agi
流浪00130 分钟前
告别静态打印:Linux C 实现实时刷新进度条
linux·运维·c语言
小此方32 分钟前
Re:Linux系统篇(二十)进程篇·五:深入理解 Linux 进程优先级:从底层逻辑到实战修改
linux·运维·服务器
路溪非溪33 分钟前
Linux下物理总线驱动模型之SDIO驱动框架
linux·驱动开发
深圳市九鼎创展科技34 分钟前
九鼎创展 X7110 开发板(JH7110):国产 RISC-V 多媒体平台全解析
大数据·linux·人工智能·嵌入式硬件·ubuntu·risc-v
流浪00137 分钟前
Linux篇(八) Make 与 Makefile 超详细入门教程|从零基础到手写自动化编译
linux·运维·自动化
爱莉希雅&&&41 分钟前
Redis哨兵模式和主从复制和集群模式搭建与扩容缩容
linux·redis·缓存·集群·哨兵·数据库同步
j_xxx404_42 分钟前
Linux线程:从内存分页机制(Page Table/TLB/Page Fault)彻底读懂 Linux 线程本质
linux·运维·服务器·开发语言·c++·人工智能·ai