You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用x11rb监听屏幕任意位置的按键输入?

问题描述

我正在使用x11rb(X11 Rust绑定),修改了x11rb官方教程代码,将按键监听对象从自定义窗口win改为屏幕根窗口(screen.root),代码如下:

use std::error::Error;
use x11rb::connection::Connection;
use x11rb::protocol::xproto::*;
use x11rb::protocol::Event;
use x11rb::COPY_DEPTH_FROM_PARENT;

fn print_modifiers(mask: x11rb::protocol::xproto::KeyButMask) {
    println!("Modifier mask: {:#?}", mask);
}

fn main() -> Result<(), Box<dyn Error>> {
    // Open the connection to the X server. Use the DISPLAY environment variable.
    let (conn, screen_num) = x11rb::connect(None)?;

    // Get the screen #screen_num
    let screen = &conn.setup().roots[screen_num];

    // Ask for our window's Id
    let win = conn.generate_id()?;
    let parent_win = screen.root;

    println!("win: {}", win);
    println!("parentWin: {}", parent_win);

    // NOTE: This window is not being used
    let values = CreateWindowAux::default().background_pixel(screen.white_pixel);
    conn.create_window(
        COPY_DEPTH_FROM_PARENT,    // depth
        win,                       // window Id
        screen.root,               // parent window
        0,                         // x
        0,                         // y
        150,                       // width
        150,                       // height
        10,                        // border_width
        WindowClass::INPUT_OUTPUT, // class
        screen.root_visual,        // visual
        &values,
    )?;

    let values = ChangeWindowAttributesAux::default().event_mask(EventMask::KEY_PRESS);

    conn.change_window_attributes(screen.root, &values)?;

    // Map the window on the screen
    conn.map_window(win)?;
    conn.flush()?;

    loop {
        let event = conn.wait_for_event()?;
        match event {
            Event::KeyPress(event) => {
                print_modifiers(event.state);
                println!("Key pressed in window {}", event.event);
            }
            _ => {
                // Unknown event type, ignore it
                println!("Unknown event: {:?}", event);
            }
        }
    }
}

原本预期在屏幕任意位置按键时,终端会输出按键日志,但实际没有任何输出。请问正确的实现方式是什么?

问题原因

X11的事件模型中,KEY_PRESS事件默认会发送到拥有输入焦点的窗口,而非根窗口。仅给根窗口设置KEY_PRESS事件掩码,只有当系统中没有任何窗口获取输入焦点时,根窗口才会收到按键事件,这几乎不会发生,所以你看不到任何输出。

正确实现方式

要全局监听所有按键事件,需要使用XInput2扩展(X11的输入扩展)来捕获原始按键事件,或者使用XGrabKeyboard抓取键盘(但会导致其他窗口无法接收按键,不推荐日常使用)。下面是基于XInput2的实现方案:

实现代码

use std::error::Error;
use x11rb::connection::Connection;
use x11rb::protocol::{xproto::*, xi2::*, Event};
use x11rb::COPY_DEPTH_FROM_PARENT;

fn print_modifiers(mask: KeyButMask) {
    println!("Modifier mask: {:#?}", mask);
}

fn main() -> Result<(), Box<dyn Error>> {
    let (conn, screen_num) = x11rb::connect(None)?;
    let screen = &conn.setup().roots[screen_num];

    // 初始化XInput2扩展
    let xi2 = conn.extension_information::<Xi2>()?
        .ok_or("XInput2 extension not available")?;
    let major_version = 2;
    let minor_version = 3;
    conn.xi_query_version(xi2.major_opcode, major_version, minor_version)?;

    // 选择监听根窗口的原始按键事件
    let event_mask = [XIEventMask {
        deviceid: XIAllMasterDevices,
        mask: &[XI_RawKeyPress],
    }];
    conn.xi_select_events(screen.root, &event_mask)?;

    conn.flush()?;

    loop {
        let event = conn.wait_for_event()?;
        match event {
            Event::XI_RawKeyPress(event) => {
                print_modifiers(event.mods.effective);
                println!("Key pressed (raw keycode: {})", event.detail);
            }
            _ => {
                // 忽略其他事件
            }
        }
    }
}

关键步骤说明

  • 启用XInput2扩展:先查询XInput2扩展是否可用,确保系统支持该功能。
  • 选择原始按键事件:通过xi_select_events给根窗口设置监听XI_RawKeyPress事件,该事件会捕获所有设备的原始按键动作,不受输入焦点影响。
  • 处理原始按键事件:在事件循环中匹配XI_RawKeyPress事件,获取按键信息并输出。

注意事项

  • 需要确保系统安装了XInput2相关库,编译时x11rb会自动处理依赖,但运行环境需要支持该扩展(主流X11桌面环境都支持)。
  • 原始按键事件的detail字段是键盘的原始键码,如需转换为字符,还需要结合X11的键盘映射进行处理。

内容的提问来源于stack exchange,提问作者wyc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 01:07:41