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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 23:20:40