I already have code that creates a transparent window and draws a square on it using winit and pixels, but I can't make it click-through, that is, let the user interact with what is behind the overlay window, while still letting the app capture input. Here's a minimal example of my code:
use pixels::{wgpu::Color, Pixels, SurfaceTexture};
use winit::{
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::EventLoop,
platform::windows::{WindowExtWindows, HWND},
window::{WindowBuilder, WindowLevel},
};
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);
window.set_cursor_hittest(false).unwrap();
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);
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent {
event: window_event,
..
} => match window_event {
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Pressed,
..
},
..
} => {
println!("Input from window event");
}
WindowEvent::CloseRequested => control_flow.set_exit(),
_ => (),
},
Event::DeviceEvent {
event:
DeviceEvent::Key(KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::Space),
state: ElementState::Pressed,
..
}),
..
} => {
println!("Input from device event");
}
Event::RedrawRequested(_) => {
pixels.render().unwrap();
}
_ => (),
}
});
}
I thought that Event::DeviceEvent
would work because it seemed like it wasn't restricted to a specific window, but it is. In every scenario I've tried, both or none of the println!()
s were called. Do I need another crate for that?
The crate device_query can solve the problem. It doesn't even need a window to function, as the input is queried on demand by calling DeviceState::get_keys()
and DeviceState::mouse()
on a DeviceState instance for keyboard and mouse respectively.
use device_query::{DeviceState, DeviceQuery};
// Cheaply creates an empty DeviceState
let device_state = DeviceState::new();
// Those methods query the input. They are individualy lazily queried.
let keys = device_state.get_keys();
let mouse = device_state.get_mouse();
let is_alt_pressed = keys.contains(&Keycode::LAlt);
let is_m1_pressed = mouse.button_pressed[1]; // It starts at [1] for M1. [0] has no meaning.
The above code can capture input without window focus and even without a window at all. In the code provided in the question, it should be put inside the MainEventsCleared
event. It is also required to replace control_flow.set_wait()
in the first line inside the event_loop.run()
closure with control_flow.set_poll()
, so that MainEventsCleared
will always run, even without new events.