如何用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
相关产品推荐
相关产品推荐

