Skip to content

Cannot fetch system information if no initial window opened #3275

@cw-software

Description

@cw-software

Is your issue REALLY a bug?

  • My issue is indeed a bug!
  • I am not crazy! I will not fill out this form just to ask a question or request a feature. Pinky promise.

Is there an existing issue for this?

  • I have searched the existing issues.

Is this issue related to iced?

  • My hardware is compatible and my graphics drivers are up-to-date.

What happened?

I use this crate to create a tray icon for my app, and I implemented a functionality to start on system boot with this crate. In case my app is started with system boot, I do not create a window, rather let the user decide when to open one from the tray icon (so that my app runs in the background). An argument (--background) signals whether to open a window or not. This works fine, just for context.

However, I am fetching some limited system information to display it in my "about" page, namely: OS name, graphics backend and adapter. I do so with the help of iced's built-in system feature, that returns a task. This task runs perfectly and fetches the necessary information when a window is opened on boot, however it does not work when the app is started in the background (without an initial window). The task does not return when a window is opened later either. Somehow it seems like system info is tied to opening a window on startup, and if not, the task system::information() returns fails silently in the background, or hangs and never yields.

Here is a MRE, without --background, the widgets are populated with correct system info, with --background, when the window is opened from the tray icon, "no data" is displayed, and it stays that way indefinitely:

use iced::{
    Alignment,
    Length::Fill,
    futures::SinkExt,
    widget::{button, column, text},
};
use trayicon::{MenuBuilder, TrayIcon, TrayIconBuilder};

fn main() -> iced::Result {
    let args: Vec<String> = std::env::args().collect();
    println!("{:?}", args);
    let nowindow = args.contains(&"--background".to_string());
    iced::daemon(move || App::new(nowindow), App::update, App::view).run()
}

#[derive(Debug, Clone)]
enum Message {
    Start,
    Sysinfo(iced::system::Information),
    OpenWindow,
    CloseWindow,
    Exit,
}

#[derive(Clone, Eq, PartialEq, Debug)]
enum TrayIconEvent {
    Open,
    Exit,
}

struct App {
    window_id: Option<iced::window::Id>,
    _tray_icon: TrayIcon<TrayIconEvent>,
    tray_icon_receiver: tokio::sync::broadcast::Receiver<TrayIconEvent>,
    info: Option<iced::system::Information>,
}

impl App {
    fn new(nowindow: bool) -> (Self, iced::Task<Message>) {
        let (window_id, open_task): (Option<iced::window::Id>, iced::Task<_>) = if nowindow {
            (None, iced::Task::done(Message::Start))
        } else {
            let (id, t) = iced::window::open(iced::window::Settings::default());
            (Some(id), t.map(|_| Message::Start))
        };
        let (tray_sender, tray_receiver) = tokio::sync::broadcast::channel(1);
        (
            Self {
                window_id,
                _tray_icon: create_tray_icon(tray_sender),
                tray_icon_receiver: tray_receiver,
                info: None,
            },
            open_task,
        )
    }
    fn update(&mut self, message: Message) -> iced::Task<Message> {
        match message {
            Message::Start => {
                return iced::Task::batch([
                    iced::Task::stream(tray_icon_events(self.tray_icon_receiver.resubscribe()))
                        .map(|v| v),
                    iced::system::information().map(Message::Sysinfo),
                ]);
            }
            Message::Sysinfo(inf) => self.info = Some(inf),
            Message::OpenWindow => {
                if self.window_id.is_none() {
                    let (id, open_task) = iced::window::open(iced::window::Settings::default());
                    self.window_id = Some(id);
                    return open_task.discard();
                }
            }
            Message::CloseWindow => {
                if let Some(id) = self.window_id {
                    self.window_id = None;
                    return iced::window::close(id);
                }
            }
            Message::Exit => return iced::exit(),
        }
        iced::Task::none()
    }
    fn view(&self, _id: iced::window::Id) -> iced::Element<'_, Message> {
        let info = if let Some(inf) = self.info.as_ref() {
            (
                inf.system_name.clone().unwrap_or_default(),
                inf.graphics_backend.clone(),
                inf.graphics_adapter.clone(),
            )
        } else {
            (
                "no data".to_string(),
                "no data".to_string(),
                "no data".to_string(),
            )
        };
        column![
            button("Close window").on_press(Message::CloseWindow),
            text("Environment information")
                .align_x(Alignment::Center)
                .width(Fill)
                .size(20),
            text(format!("Operating system: {}", info.0)),
            text(format!("Graphics backend: {}", info.1)),
            text(format!("Graphics adapter: {}", info.2)),
        ]
        .padding(12)
        .spacing(12)
        .into()
    }
}

fn create_tray_icon(
    tray_sender: tokio::sync::broadcast::Sender<TrayIconEvent>,
) -> TrayIcon<TrayIconEvent> {
    TrayIconBuilder::new()
        .sender(move |e: &TrayIconEvent| {
            let _ = tray_sender.send(e.clone());
        })
        .title("NowindowTest")
        .icon_from_buffer(include_bytes!("../icon.ico"))
        .on_click(TrayIconEvent::Open)
        .menu(
            MenuBuilder::new()
                .item("Open", TrayIconEvent::Open)
                .item("Exit", TrayIconEvent::Exit),
        )
        .build()
        .unwrap()
}

fn tray_icon_events(
    mut rec_clone: tokio::sync::broadcast::Receiver<TrayIconEvent>,
) -> impl iced::futures::Stream<Item = Message> {
    iced::stream::channel(1, async move |mut out| {
        loop {
            match rec_clone.recv().await {
                Ok(v) => match v {
                    TrayIconEvent::Open => {
                        let _ = out.send(Message::OpenWindow).await;
                    }
                    TrayIconEvent::Exit => {
                        let _ = out.send(Message::Exit).await;
                    }
                },
                Err(_) => {
                    break;
                }
            }
        }
    })
}

What is the expected behavior?

Task yields system info even without initial window.

Version

crates.io release

Operating System

Windows

Do you have any log output?

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions