egui icon indicating copy to clipboard operation
egui copied to clipboard

[eFrame] Can't set window Visible(true) after setting the window Visible(false) on Windows

Open jonatino opened this issue 1 year ago • 7 comments

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

jonatino avatar Oct 07 '24 21:10 jonatino

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.

Wcubed avatar Nov 01 '24 12:11 Wcubed

If there is any solution to this, please let me know, thanks!

mcthesw avatar Mar 17 '25 02:03 mcthesw

I've just encountered this on Windows 10 as well and would be interested in a fix.

izolyomi avatar Mar 20 '25 11:03 izolyomi

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!

Infraviored avatar Jun 08 '25 11:06 Infraviored

Same issue, making a tray utility, Windows 11.

maksiksq avatar Aug 18 '25 10:08 maksiksq

Also, the application is taking 100% of a CPU core when the window is hidden because the event loop never blocks anymore.

Nahor avatar Aug 18 '25 15:08 Nahor

@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?

nekename avatar Nov 30 '25 13:11 nekename