如何在Rust中实现可穿透且能捕获输入的点击覆盖层?
解决透明窗口点击穿透同时捕获输入的问题
你的核心问题在于:set_cursor_hittest(false)会让窗口完全失去输入接收权限,系统不会将任何输入事件(包括DeviceEvent)传递给这个窗口的事件循环,所以才会出现要么都触发要么都不触发的情况。要实现鼠标点击穿透(允许操作后方内容)+ 捕获键盘输入的效果,需要结合Windows原生API调整窗口样式,并改用全局输入监听方案。
步骤1:用Windows API设置点击穿透窗口样式
winit的set_cursor_hittest无法满足需求,需要直接调用Win32 API给窗口添加WS_EX_LAYERED和WS_EX_TRANSPARENT扩展样式:
首先在Cargo.toml添加依赖:
[dependencies] pixels = "0.13" winit = { version = "0.29", features = ["windows", "fullscreen"] } winapi = { version = "0.3", features = ["winuser", "wingdi"] }
然后修改窗口初始化逻辑:
use pixels::{wgpu::Color, Pixels, SurfaceTexture}; use winit::{ event::{ElementState, Event, WindowEvent}, event_loop::EventLoop, platform::windows::WindowExtWindows, window::{WindowBuilder, WindowLevel}, }; use winapi::um::{ wingdi::SetLayeredWindowAttributes, winuser::{GetWindowLongPtrW, SetWindowLongPtrW, GWL_EXSTYLE, WS_EX_LAYERED, WS_EX_TRANSPARENT, LWA_COLORKEY}, }; use std::ptr; fn main() { let event_loop = EventLoop::new(); let window = WindowBuilder::new() .with_fullscreen(Some(winit::window::Fullscreen::Borderless(None))) .with_transparent(true) .build(&event_loop) .unwrap(); window.set_window_level(WindowLevel::AlwaysOnTop); // 获取窗口HWND并设置穿透样式 let hwnd = window.hwnd() as winapi::shared::minwindef::HWND; unsafe { let current_ex_style = GetWindowLongPtrW(hwnd, GWL_EXSTYLE); // 添加分层窗口和透明穿透样式 SetWindowLongPtrW(hwnd, GWL_EXSTYLE, current_ex_style | WS_EX_LAYERED | WS_EX_TRANSPARENT); // 设置全透明色为窗口透明键,确保鼠标穿透 SetLayeredWindowAttributes(hwnd, 0, 0, LWA_COLORKEY); } let window_size = window.inner_size(); let surface = SurfaceTexture::new(window_size.width, window_size.height, &window); let mut pixels = Pixels::new(window_size.width, window_size.height, surface).unwrap(); pixels.set_clear_color(Color::TRANSPARENT); // ... 后续事件循环 }
这段代码会让窗口忽略所有鼠标事件(直接传递给后方窗口),但窗口本身仍保持顶层显示。
步骤2:全局捕获键盘输入
此时窗口无法通过自身的WindowEvent或DeviceEvent获取键盘输入(因为没有焦点),需要用全局热键或全局钩子方案,推荐使用global-hotkey crate简化实现:
在Cargo.toml添加依赖:
global-hotkey = { version = "0.2", features = ["winit"] }
修改事件循环逻辑,注册全局热键并监听:
use global_hotkey::{GlobalHotKeyManager, HotKey, Key, GlobalHotKeyEvent}; // ... 窗口初始化后 let mut hotkey_manager = GlobalHotKeyManager::new().unwrap(); // 注册空格键全局热键 let space_hotkey = HotKey::new(None, Key::Space); hotkey_manager.register(space_hotkey.clone()).unwrap(); event_loop.run(move |event, _, control_flow| { control_flow.set_wait(); match event { // 监听全局热键事件 Event::UserEvent(event) => { if let GlobalHotKeyEvent { hotkey, .. } = event { if hotkey == space_hotkey { println!("Global space key pressed"); } } } Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => control_flow.set_exit(), Event::RedrawRequested(_) => pixels.render().unwrap(), _ => (), } });
关键说明
WS_EX_TRANSPARENT:让窗口忽略鼠标输入,确保点击能穿透到后方窗口。WS_EX_LAYERED:必须配合SetLayeredWindowAttributes使用,确保透明效果和穿透生效。- 全局热键:如果需要监听多个按键或组合键,可扩展
HotKey的配置;若全局热键冲突,可改用Win32的全局钩子(实现更复杂,但灵活性更高)。
内容的提问来源于stack exchange,提问作者Felipe
相关产品推荐
相关产品推荐

