egui
egui copied to clipboard
eframe: Native option mouse_passthrough makes window invisible
Describe the bug When using mouse_passthrough native option the window is entirely transparent, it only shows in task bar and task view as a black box. Also reproducible on master.
To Reproduce Steps to reproduce the behavior:
- use mouse_passthrough in some example app. Reproducible example follows.
main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use eframe::egui;
fn main() {
let options = eframe::NativeOptions {
decorated: false,
transparent: true,
mouse_passthrough: true, // Changing this to true makes window fully invisible
min_window_size: Some(egui::vec2(320.0, 100.0)),
initial_window_size: Some(egui::vec2(320.0, 240.0)),
..Default::default()
};
eframe::run_native(
"Basic Window",
options,
Box::new(|_cc| Box::new(MyApp::default())),
);
}
#[derive(Default)]
struct MyApp {}
impl eframe::App for MyApp {
fn clear_color(&self, _visuals: &egui::Visuals) -> egui::Rgba {
egui::Rgba::TRANSPARENT
}
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::Window::new("My Window").show(ctx, |ui| {
ui.label("Hello world");
});
}
}
cargo.toml
[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"
[dependencies]
eframe = { version = "0.20.1" }
Expected behavior Window will be visible, but cannot be interacted with.
Screenshots Nothing to screenshot normally, but here's screenshot of task view
Desktop (please complete the following information):
- OS: Windows 10 21H2
- Native via eframe
- rustc 1.65.0
- eframe - 0.20.1
Additional context The original issue is here There were also a report of this problem
This ... appears to be a winit
bug. Using set_cursor_hittest()
on Windows sets the WS_EX_LAYERED
extended window attribute. Which is documented to have the following caveat:
After the CreateWindowEx call, the layered window will not become visible until the SetLayeredWindowAttributes or UpdateLayeredWindow function has been called for this window.
A call to the SetLayeredWindowAttributes
function was removed in https://github.com/rust-windowing/winit/pull/1815, even though this caveat was mentioned in https://github.com/rust-windowing/winit/pull/1815#issuecomment-753636510. Importantly, this removal predates the introduction of set_cursor_hittest()
, where the documented caveat was not revisited and so not accounted for in https://github.com/rust-windowing/winit/pull/2232.
IMHO, this should be reported upstream to winit
. This analysis is far from comprehensive but may provide some useful context.
edit: I used to have a section claiming it "works for me" on Windows 11. But this is not true. I missed the point about making a change to the minimal repro to actually reproduce the problem. Yes, it's a reproducible problem. But the repro code doesn't reproduce the problem as-is.
I'll report it to winit then, thank you. Also changed the code to avoid the confusion.
Thanks for fixing the minimal repro!
Could you also link back to this issue when you create the upstream one?
~~I have attempted to reproduce it in winit but couldn't: set_cursor_hittest() works fine on winit example window.~~ While trying to repeat the error I noticed that with_visible is somehow involved (it references #2279), because if it's set to true, set_cursor_hittest works as intended.
Current observation is that using set_cursor_hittest(false) while window in set_visible(false) breaks it.
Is there a workaround to this currently? I tried to set visible to true on the frame in my app but it still doesn't render anything. It would seem that I need to access the native window creation itself to do fix this manually (not sure). One way I imagine is to manually get a handle to the window (in windows) and pass the correct options.
Edit:
I developed a work around specific for windows. I use the windows api functions EnumWindows and SetLayeredWindowAttributes to make the window appear again. First I get the window handle through the EnumWindows, searching for the correct window name and then call SetLayeredWindowAttributes(handle, 0, 255, LWA_ALPHA)
to make the window visible.
Here is my EnumWindows callback code for someone who also wants to get around this (using windows crate but winapi is identical or even more trivial):
extern "system" fn enum_win(hwnd: HWND, mut lparam: LPARAM) -> BOOL {
let mut class_name = [0u16; 256];
unsafe { GetWindowTextW(hwnd, &mut class_name) };
let class_name = String::from_utf16_lossy(&class_name[..6 as usize]);
println!("class_name: {}", class_name);
let parameter = unsafe {std::mem::transmute::<LPARAM, &mut LPARAM>(lparam)};
if class_name == "name" {
println!("Found window: {}, {}", hwnd.0, class_name);
(*parameter).0 = hwnd.0;
return BOOL(0)
}
return BOOL(1)
}
Nonetheless, the bug remains to be fixed as it completely removes the mouse_passthrough functionality.
For myself I worked around it in eframe. In file native/run.rs you've got to move this code:
if self.native_options.mouse_passthrough {
gl_window.window().set_cursor_hittest(false).unwrap();
}
from init_run_state()
to GlowWinitApp method paint()
right after integration.post_present(window);
as this function is settings visibility back to window. E.g.
integration.post_present(window);
window.set_cursor_hittest(!self.native_options.mouse_passthrough).unwrap();
mouse_passthrough
works properly after this change.
For myself I worked around it in eframe. In file native/run.rs you've got to move this code:
if self.native_options.mouse_passthrough { gl_window.window().set_cursor_hittest(false).unwrap(); }
from
init_run_state()
to GlowWinitApp methodpaint()
right afterintegration.post_present(window);
as this function is settings visibility back to window. E.g.integration.post_present(window); window.set_cursor_hittest(!self.native_options.mouse_passthrough).unwrap();
mouse_passthrough
works properly after this change.
Is there any update on this issue or rather a cleaner way of approaching patching the package?
if the mentioned "workaround" of @Skrity is the fix, why this isn't merged yet?
For myself I worked around it in eframe. In file native/run.rs you've got to move this code:
if self.native_options.mouse_passthrough { gl_window.window().set_cursor_hittest(false).unwrap(); }
from
init_run_state()
to GlowWinitApp methodpaint()
right afterintegration.post_present(window);
as this function is settings visibility back to window. E.g.integration.post_present(window); window.set_cursor_hittest(!self.native_options.mouse_passthrough).unwrap();
mouse_passthrough
works properly after this change.
I cant quite seem to re-create your workaround, do you have a fork of this change?
Is this still a problem on latest master
? The code has been significantly rewritten over the last week
Is this still a problem on latest
master
? The code has been significantly rewritten over the last week
For some reason, I didn't think to check against master, and instead tried checking only the crates version. That's on me! If there is any work that needs to be done relating to this, I'd love to help, such as an example overlay program maybe? I'm mostly new to this though. 😄
Edit: After using what is current on master branch, the only thing I found that was a little unpleasant was that egui/crates/eframe/src/epi.rs clear_color(&egui::Visuals) only using the hard coded value, is there a reason? Because the function even has a comment.
// _visuals.window_fill() would also be a natural choice
After patching that to instead use the window_fill field, it seems to work perfectly though to my enjoyment!
Hey, is there any progress on this? I know it's only been an issue for around one and a half years and it is a breaking bug, but shouldn't this get some patch here maybe if upstream isn't doing anything about it?
I suggest a temporary feature flag that fixes this locally and can be removed later.
Is there a workaround to this currently? I tried to set visible to true on the frame in my app but it still doesn't render anything. It would seem that I need to access the native window creation itself to do fix this manually (not sure). One way I imagine is to manually get a handle to the window (in windows) and pass the correct options.
Edit:
I developed a work around specific for windows. I use the windows api functions EnumWindows and SetLayeredWindowAttributes to make the window appear again. First I get the window handle through the EnumWindows, searching for the correct window name and then call
SetLayeredWindowAttributes(handle, 0, 255, LWA_ALPHA)
to make the window visible.Here is my EnumWindows callback code for someone who also wants to get around this (using windows crate but winapi is identical or even more trivial):
extern "system" fn enum_win(hwnd: HWND, mut lparam: LPARAM) -> BOOL { let mut class_name = [0u16; 256]; unsafe { GetWindowTextW(hwnd, &mut class_name) }; let class_name = String::from_utf16_lossy(&class_name[..6 as usize]); println!("class_name: {}", class_name); let parameter = unsafe {std::mem::transmute::<LPARAM, &mut LPARAM>(lparam)}; if class_name == "name" { println!("Found window: {}, {}", hwnd.0, class_name); (*parameter).0 = hwnd.0; return BOOL(0) } return BOOL(1) }
Nonetheless, the bug remains to be fixed as it completely removes the mouse_passthrough functionality.
And besides your code being not a general workaround, it's also just bad.
I'd just recommend implementing something similar to this post: https://stackoverflow.com/a/2620522
static INIT_WINDOW_VISIBILITY_FIX: Once = Once::new();
pub fn init_window_visibility_fix() {
INIT_WINDOW_VISIBILITY_FIX.call_once(|| {
if let Err(e) = fix_window_visibility() {
log::error!("Failed to fix window visibility: {:?}", e);
}
});
}
fn fix_window_visibility() -> windows::core::Result<()> {
unsafe { EnumWindows(Some(enum_window_proc), LPARAM(0)) }
}
extern "system" fn enum_window_proc(hwnd: HWND, _: LPARAM) -> BOOL {
let mut class_name_buffer = [0u16; MAX_PATH as usize];
unsafe {
let mut process_id: u32 = 0;
GetWindowThreadProcessId(hwnd, Some(&mut process_id));
if process_id == GetCurrentProcessId() {
if GetWindowTextW(hwnd, &mut class_name_buffer) == 0 {
return TRUE;
}
let _ = SetLayeredWindowAttributes(hwnd, COLORREF(0), 255, LWA_ALPHA);
}
}
TRUE
}
I just hope someone pushes some fixes to winit at some point
I just hope someone pushes some fixes to winit at some point
You could be that someone!
bruh, simple work around is call on startup ctx.send_viewport_cmd(ViewportCommand::MousePassthrough(true))
and window is visible and passthrough