rust-windowing / winit

Window handling library in pure Rust
https://docs.rs/winit/
Apache License 2.0
4.74k stars 891 forks source link

Taskbar icon and system menu information always shown on macOS #3921

Open Jooonnnaass opened 2 days ago

Jooonnnaass commented 2 days ago

Description

Im trying to create an overlay application with winit. I create an event loop and create a tray menu with the tray_icon crate. Based on the MenuItems i create two windows, one for the app config and one for the overlay.

First everything was working fine but for some reason now every time i start the app, the exec icon shows up in the taskbar and the crate name will show up in the system titlebar of macOS even a window wasn't created yet.

I tried different window attributes but nothing works.

None of the opened issues helped me so far.

That's the code for creating the window based on the menu item button click.

                    let overlay_window_attributes = WindowAttributes::default()
                        .with_title("Overlay")
                        .with_inner_size(size)
                        .with_decorations(false)
                        .with_window_level(WindowLevel::AlwaysOnTop)
                        .with_transparent(true);
                    let window = event_loop.create_window(overlay_window_attributes).unwrap();
                    tray_app_state.overlay_window_id = Some(window.id());
                    tray_app_state.windows.insert(window.id(), Box::new(window));

macOS version

macOS
Sonoma 14.5

Winit version

0.30.5

madsmtm commented 2 days ago

First everything was working fine but for some reason now

Could you give more details on this? Did it work with previous versions of tray_icon and/or winit?

Jooonnnaass commented 2 days ago

tray-icon = "0.17.0" winit = "0.30.5"

I never used other versions. Below is the complete code. But it seams quite strange that it suddenly behaves like that because no window is created until tray item action.

#![allow(unused)]

use std::{collections::HashMap, time::Instant};

use tray_icon::{
    menu::{
        accelerator::{Accelerator, Code, Modifiers},
        AboutMetadata, Menu, MenuEvent, MenuItem, PredefinedMenuItem,
    },
    TrayIconBuilder, TrayIconEvent,
};
use winit::{
    dpi::{LogicalPosition, LogicalSize, Position},
    event::{Event, KeyEvent, WindowEvent},
    event_loop::{self, ControlFlow, EventLoopBuilder},
    keyboard::{Key, KeyCode, SmolStr},
    monitor::{MonitorHandle, VideoModeHandle},
    platform::{
        macos::WindowAttributesExtMacOS, modifier_supplement::KeyEventExtModifierSupplement,
    },
    raw_window_handle::HasDisplayHandle,
    window::{Fullscreen, Window, WindowAttributes, WindowId, WindowLevel},
};

struct AppState {
    app_toggle: bool,
    keebplay_toggle: bool,
    capture_sequenz: bool,
    app_window_id: Option<WindowId>,
    overlay_window_id: Option<WindowId>,
    windows: HashMap<WindowId, Box<Window>>,
    key_sequenz: Vec<Key<SmolStr>>,
}

const SEQUENZ_THRESHOLD: i32 = 200;

fn main() {
    let path = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/keyboard.png");

    let tray_menu = Menu::new();

    let mut app_toggle_item = MenuItem::with_id("app_toggle", "Open", true, None);
    let mut keebplay_toggle_item =
        MenuItem::with_id("keebplay_toggle", "Start Keebplay", true, None);
    let quit_item = MenuItem::with_id("quit", "Quit", true, None);

    let _ = tray_menu.append_items(&[
        &app_toggle_item,
        &PredefinedMenuItem::separator(),
        &keebplay_toggle_item,
        &PredefinedMenuItem::separator(),
        &quit_item,
    ]);

    // Since winit doesn't use gtk on Linux, and we need gtk for
    // the tray icon to show up, we need to spawn a thread
    // where we initialize gtk and create the tray_icon
    #[cfg(target_os = "linux")]
    std::thread::spawn(|| {
        use tray_icon::menu::Menu;

        let icon = load_icon(std::path::Path::new(path));

        gtk::init().unwrap();
        let _tray_icon = TrayIconBuilder::new()
            .with_menu(Box::new(tray_menu))
            .with_icon(icon)
            .build()
            .unwrap();

        gtk::main();
    });

    let event_loop = EventLoopBuilder::new().build().unwrap();

    #[cfg(not(target_os = "linux"))]
    let mut tray_icon = None;

    let menu_channel = MenuEvent::receiver();

    let mut app_state = AppState {
        app_toggle: false,
        keebplay_toggle: false,
        capture_sequenz: false,
        app_window_id: None,
        overlay_window_id: None,
        windows: HashMap::new(),
        key_sequenz: Vec::new(),
    };

    let last_keystroke = 0;

    event_loop.run(move |event, event_loop| {
        // We add delay of 16 ms (60fps) to event_loop to reduce cpu load.
        // This can be removed to allow ControlFlow::Poll to poll on each cpu cycle
        // Alternatively, you can set ControlFlow::Wait or use TrayIconEvent::set_event_handler,
        // see https://github.com/tauri-apps/tray-icon/issues/83#issuecomment-1697773065
        event_loop.set_control_flow(ControlFlow::WaitUntil(
            std::time::Instant::now() + std::time::Duration::from_millis(16),
        ));

        #[cfg(not(target_os = "linux"))]
        if let winit::event::Event::NewEvents(winit::event::StartCause::Init) = event {
            let icon = load_icon(std::path::Path::new(path));

            // We create the icon once the event loop is actually running
            // to prevent issues like https://github.com/tauri-apps/tray-icon/issues/90
            tray_icon = Some(
                TrayIconBuilder::new()
                    .with_menu(Box::new(tray_menu.clone()))
                    .with_tooltip("Keebplay")
                    .with_icon(icon)
                    .build()
                    .unwrap(),
            );
            // We have to request a redraw here to have the icon actually show up.
            // Winit only exposes a redraw method on the Window so we use core-foundation directly.
            #[cfg(target_os = "macos")]
            unsafe {
                use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};

                let rl = CFRunLoopGetMain();
                CFRunLoopWakeUp(rl);
            }
        }

        match event {
            Event::WindowEvent { event, .. } => match event {
                WindowEvent::KeyboardInput { event, .. } => {
                    let this_keystroke = Instant::now();

                    if !app_state.capture_sequenz {
                        app_state.key_sequenz.clear();
                        app_state.capture_sequenz = true;
                    }

                    app_state.key_sequenz.push(event.key_without_modifiers());
                }
                _ => (),
            },
            _ => (),
        };

        if let Ok(event) = menu_channel.try_recv() {
            if event.id == "app_toggle" {
                if app_state.app_toggle {
                    app_state.app_toggle = false;
                    app_toggle_item.set_text("Open");
                    app_state.windows.remove(&app_state.app_window_id.unwrap());
                } else {
                    app_state.app_toggle = true;
                    app_toggle_item.set_text("Stop");
                    let app_window_attributes = WindowAttributes::default()
                        .with_title("App")
                        .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0)))
                        .with_min_inner_size(LogicalSize::new(640.0f32, 480.0f32));
                    let window = event_loop.create_window(app_window_attributes).unwrap();
                    app_state.app_window_id = Some(window.id());
                    app_state.windows.insert(window.id(), Box::new(window));
                }
            }
            if event.id == "keebplay_toggle" {
                if app_state.keebplay_toggle {
                    app_state.keebplay_toggle = false;
                    keebplay_toggle_item.set_text("Start Keebplay");
                    app_state
                        .windows
                        .remove(&app_state.overlay_window_id.unwrap());
                } else {
                    app_state.keebplay_toggle = true;
                    keebplay_toggle_item.set_text("Stop Keebplay");

                    let window_handle = &event_loop.primary_monitor().unwrap();

                    let size =
                        LogicalSize::new(window_handle.size().width, window_handle.size().height);

                    let overlay_window_attributes = WindowAttributes::default()
                        .with_title("Overlay")
                        .with_inner_size(size)
                        .with_window_level(WindowLevel::AlwaysOnTop)
                        .with_transparent(true);
                    let window = event_loop.create_window(overlay_window_attributes).unwrap();
                    app_state.overlay_window_id = Some(window.id());
                    app_state.windows.insert(window.id(), Box::new(window));
                }
            }
            if event.id == "quit" {
                event_loop.exit();
            }
        }
    });
}

fn load_icon(path: &std::path::Path) -> tray_icon::Icon {
    let (icon_rgba, icon_width, icon_height) = {
        let image = image::open(path)
            .expect("Failed to open icon path")
            .into_rgba8();
        let (width, height) = image.dimensions();
        let rgba = image.into_raw();
        (rgba, width, height)
    };
    tray_icon::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
}