[eFrame] Can't set window Visible(true) after setting the window Visible(false) on Windows
Describe the bug
Due to the way eframe handles querying for viewport commands, it relies on the window event RepaintNow or RedrawRequested to process the queued viewport commands. The problem is, once you set a window Visible(false), those events are no longer sent to the window (at least on Windows 11) therefore making it impossible to set the window Visible(true) using a viewport command.
https://github.com/emilk/egui/blob/master/crates/eframe/src/native/glow_integration.rs#L719 https://github.com/emilk/egui/blob/master/crates/eframe/src/native/glow_integration.rs#L1297 https://github.com/emilk/egui/blob/master/crates/egui-winit/src/lib.rs#L1389
The example below just opens a window then uses a new thread to toggle its visibility,
To Reproduce Steps to reproduce the behavior:
eframe = "0.29.1"
egui = "0.29.1"
use eframe::{egui, App};
use std::time::{Duration, Instant};
use egui::{Id, ViewportBuilder, ViewportCommand};
use crossbeam::channel;
use std::sync::{Arc, Mutex};
use std::thread;
struct VolumeApp {
}
impl App for VolumeApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
let volume: u8= ctx.data(|map| *map.get_temp(Id::new(1337)).unwrap_or(&0));
let start_time: Option<Instant>= ctx.data(|map| *map.get_temp(Id::new(1338)).unwrap_or(&None));
println!("Start time {:?}", start_time);
// Only show the UI when the start_time is set (window is visible)
if start_time.is_some() {
egui::CentralPanel::default().show(ctx, |ui| {
ui.label(format!("Volume: {:.0}%", volume)); // Display the volume percentage
});
}
}
}
fn main() {
// std::env::set_var("RUST_LOG", "trace");
env_logger::builder().init();
// Create a crossbeam channel for sending volume levels
let (volume_sender, volume_receiver) = channel::unbounded();
let volume_receiver = Arc::new(Mutex::new(volume_receiver));
// Spawn a thread to simulate sending volume levels to the app
let volume_sender_clone = volume_sender.clone();
thread::spawn(move || {
let test_volumes = vec![10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
for volume in test_volumes {
volume_sender_clone.send(volume).unwrap();
thread::sleep(Duration::from_secs_f32(2.0)); // Simulate delay between volume changes
}
});
let viewport = ViewportBuilder::default()
.with_resizable(false)
.with_always_on_top()
.with_decorations(true)
.with_inner_size(egui::vec2(200.0, 20.0));
let options = eframe::NativeOptions {
viewport,
..Default::default()
};
eframe::run_native(
"MyApp",
options,
Box::new(|cc| {
let volume_receiver = volume_receiver.clone();
let context =cc.egui_ctx.clone(); // Store the frame handle for controlling visibility
// Background thread to manage volume updates and window visibility
{
thread::spawn(move || {
loop {
// Wait for a new volume to arrive
if let Ok(new_volume) = volume_receiver.lock().unwrap().recv() {
println!("Set Visible");
context.data_mut(|map| {
map.insert_temp(Id::new(1337), new_volume);
map.insert_temp(Id::new(1338), Some(Instant::now()));
});
// Set the window visible
context.send_viewport_cmd(ViewportCommand::Visible(true));
context.request_repaint();
context.send_viewport_cmd(ViewportCommand::MousePassthrough(true));
// Wait for 1 second before hiding the window
thread::sleep(Duration::from_secs(1));
// Hide the window after 1 second of inactivity
println!("Set Invisible");
context.send_viewport_cmd(ViewportCommand::Visible(false));
}
}
});
}
// Initialize the app and pass control to eframe
Ok(Box::new(VolumeApp {
}))
}),
)
.expect("Failed to run the app");
}
Expected behavior The window should open/close every 2 seconds but it cannot open after being set invisible since viewport commands are no longer polled.
Desktop (please complete the following information):
- OS: Windows 11
Ran into this one as well. I was building a launcher that listents to a global shortcut, and then appears. But it wouldn't re-appear once it was hidden. As a workaround I made the window 0x0 pixels, no decoration, and put it behind all the other windows.
If there is any solution to this, please let me know, thanks!
I've just encountered this on Windows 10 as well and would be interested in a fix.
Just wanted to add that this is a critical issue for me as well.
Environment:
-
eframe:
0.31.1 -
egui:
0.31.1 - OS: Windows 11
Use Case:
I am building a standard tray-based utility. The app lives in the tray, and the eframe window is only shown when the user clicks "Settings".
The Problem:
As described, once the window is hidden with ViewportCommand::Visible(false), it's impossible to get it back. The event loop seems to go dormant, and any later command to set it Visible(true) is ignored.
This completely blocks the standard single-process architecture for tray apps on Windows. The only options are complex multi-process workarounds (spawning a separate settings process with IPC) or hacky solutions like a 0x0 ghost window.
Being able to reliably show and hide the viewport is a must-have for this kind of app. A fix would be hugely appreciated. Thanks for the great work on the framework!
Same issue, making a tray utility, Windows 11.
Also, the application is taking 100% of a CPU core when the window is hidden because the event loop never blocks anymore.
@emilk Sorry for the ping. This is quite a critical issue for tray-based apps. Do you know if there are any plans for this to be fixed?