iced_winit/
lib.rs

1//! A windowing shell for Iced, on top of [`winit`].
2//!
3//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
4//!
5//! `iced_winit` offers some convenient abstractions on top of [`iced_runtime`]
6//! to quickstart development when using [`winit`].
7//!
8//! It exposes a renderer-agnostic [`Program`] trait that can be implemented
9//! and then run with a simple call. The use of this trait is optional.
10//!
11//! Additionally, a [`conversion`] module is available for users that decide to
12//! implement a custom event loop.
13//!
14//! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.13/runtime
15//! [`winit`]: https://github.com/rust-windowing/winit
16//! [`conversion`]: crate::conversion
17#![doc(
18    html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
19)]
20#![cfg_attr(docsrs, feature(doc_auto_cfg))]
21pub use iced_program as program;
22pub use program::core;
23pub use program::graphics;
24pub use program::runtime;
25pub use runtime::debug;
26pub use runtime::futures;
27pub use winit;
28
29pub mod clipboard;
30pub mod conversion;
31
32mod error;
33mod proxy;
34mod window;
35
36pub use clipboard::Clipboard;
37pub use error::Error;
38pub use proxy::Proxy;
39
40use crate::core::mouse;
41use crate::core::renderer;
42use crate::core::theme;
43use crate::core::time::Instant;
44use crate::core::widget::operation;
45use crate::core::{Point, Size};
46use crate::futures::futures::channel::mpsc;
47use crate::futures::futures::channel::oneshot;
48use crate::futures::futures::task;
49use crate::futures::futures::{Future, StreamExt};
50use crate::futures::subscription;
51use crate::futures::{Executor, Runtime};
52use crate::graphics::{Compositor, compositor};
53use crate::runtime::system;
54use crate::runtime::user_interface::{self, UserInterface};
55use crate::runtime::{Action, Task};
56
57use program::Program;
58use window::WindowManager;
59
60use rustc_hash::FxHashMap;
61use std::borrow::Cow;
62use std::mem::ManuallyDrop;
63use std::slice;
64use std::sync::Arc;
65
66/// Runs a [`Program`] with the provided settings.
67pub fn run<P>(program: P) -> Result<(), Error>
68where
69    P: Program + 'static,
70    P::Theme: theme::Base,
71{
72    use winit::event_loop::EventLoop;
73
74    let boot_span = debug::boot();
75    let settings = program.settings();
76    let window_settings = program.window();
77
78    let graphics_settings = settings.clone().into();
79    let event_loop = EventLoop::with_user_event()
80        .build()
81        .expect("Create event loop");
82
83    let (proxy, worker) = Proxy::new(event_loop.create_proxy());
84
85    #[cfg(feature = "debug")]
86    {
87        let proxy = proxy.clone();
88
89        debug::on_hotpatch(move || {
90            proxy.send_action(Action::Reload);
91        });
92    }
93
94    let mut runtime = {
95        let executor =
96            P::Executor::new().map_err(Error::ExecutorCreationFailed)?;
97        executor.spawn(worker);
98
99        Runtime::new(executor, proxy.clone())
100    };
101
102    let (program, task) = runtime.enter(|| program::Instance::new(program));
103    let is_daemon = window_settings.is_none();
104
105    let task = if let Some(window_settings) = window_settings {
106        let mut task = Some(task);
107
108        let (_id, open) = runtime::window::open(window_settings);
109
110        open.then(move |_| task.take().unwrap_or_else(Task::none))
111    } else {
112        task
113    };
114
115    if let Some(stream) = runtime::task::into_stream(task) {
116        runtime.run(stream);
117    }
118
119    runtime.track(subscription::into_recipes(
120        runtime.enter(|| program.subscription().map(Action::Output)),
121    ));
122
123    let (event_sender, event_receiver) = mpsc::unbounded();
124    let (control_sender, control_receiver) = mpsc::unbounded();
125    let (system_theme_sender, system_theme_receiver) = oneshot::channel();
126
127    let instance = Box::pin(run_instance::<P>(
128        program,
129        runtime,
130        proxy.clone(),
131        event_receiver,
132        control_sender,
133        is_daemon,
134        graphics_settings,
135        settings.fonts,
136        system_theme_receiver,
137    ));
138
139    let context = task::Context::from_waker(task::noop_waker_ref());
140
141    struct Runner<Message: 'static, F> {
142        instance: std::pin::Pin<Box<F>>,
143        context: task::Context<'static>,
144        id: Option<String>,
145        sender: mpsc::UnboundedSender<Event<Action<Message>>>,
146        receiver: mpsc::UnboundedReceiver<Control>,
147        error: Option<Error>,
148        system_theme: Option<oneshot::Sender<theme::Mode>>,
149
150        #[cfg(target_arch = "wasm32")]
151        canvas: Option<web_sys::HtmlCanvasElement>,
152    }
153
154    let runner = Runner {
155        instance,
156        context,
157        id: settings.id,
158        sender: event_sender,
159        receiver: control_receiver,
160        error: None,
161        system_theme: Some(system_theme_sender),
162
163        #[cfg(target_arch = "wasm32")]
164        canvas: None,
165    };
166
167    boot_span.finish();
168
169    impl<Message, F> winit::application::ApplicationHandler<Action<Message>>
170        for Runner<Message, F>
171    where
172        F: Future<Output = ()>,
173    {
174        fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
175            if let Some(sender) = self.system_theme.take() {
176                let _ = sender.send(
177                    event_loop
178                        .system_theme()
179                        .map(conversion::theme_mode)
180                        .unwrap_or_default(),
181                );
182            }
183        }
184
185        fn new_events(
186            &mut self,
187            event_loop: &winit::event_loop::ActiveEventLoop,
188            cause: winit::event::StartCause,
189        ) {
190            self.process_event(
191                event_loop,
192                Event::EventLoopAwakened(winit::event::Event::NewEvents(cause)),
193            );
194        }
195
196        fn window_event(
197            &mut self,
198            event_loop: &winit::event_loop::ActiveEventLoop,
199            window_id: winit::window::WindowId,
200            event: winit::event::WindowEvent,
201        ) {
202            #[cfg(target_os = "windows")]
203            let is_move_or_resize = matches!(
204                event,
205                winit::event::WindowEvent::Resized(_)
206                    | winit::event::WindowEvent::Moved(_)
207            );
208
209            self.process_event(
210                event_loop,
211                Event::EventLoopAwakened(winit::event::Event::WindowEvent {
212                    window_id,
213                    event,
214                }),
215            );
216
217            // TODO: Remove when unnecessary
218            // On Windows, we emulate an `AboutToWait` event after every `Resized` event
219            // since the event loop does not resume during resize interaction.
220            // More details: https://github.com/rust-windowing/winit/issues/3272
221            #[cfg(target_os = "windows")]
222            {
223                if is_move_or_resize {
224                    self.process_event(
225                        event_loop,
226                        Event::EventLoopAwakened(
227                            winit::event::Event::AboutToWait,
228                        ),
229                    );
230                }
231            }
232        }
233
234        fn user_event(
235            &mut self,
236            event_loop: &winit::event_loop::ActiveEventLoop,
237            action: Action<Message>,
238        ) {
239            self.process_event(
240                event_loop,
241                Event::EventLoopAwakened(winit::event::Event::UserEvent(
242                    action,
243                )),
244            );
245        }
246
247        fn received_url(
248            &mut self,
249            event_loop: &winit::event_loop::ActiveEventLoop,
250            url: String,
251        ) {
252            self.process_event(
253                event_loop,
254                Event::EventLoopAwakened(
255                    winit::event::Event::PlatformSpecific(
256                        winit::event::PlatformSpecific::MacOS(
257                            winit::event::MacOS::ReceivedUrl(url),
258                        ),
259                    ),
260                ),
261            );
262        }
263
264        fn about_to_wait(
265            &mut self,
266            event_loop: &winit::event_loop::ActiveEventLoop,
267        ) {
268            self.process_event(
269                event_loop,
270                Event::EventLoopAwakened(winit::event::Event::AboutToWait),
271            );
272        }
273    }
274
275    impl<Message, F> Runner<Message, F>
276    where
277        F: Future<Output = ()>,
278    {
279        fn process_event(
280            &mut self,
281            event_loop: &winit::event_loop::ActiveEventLoop,
282            event: Event<Action<Message>>,
283        ) {
284            if event_loop.exiting() {
285                return;
286            }
287
288            self.sender.start_send(event).expect("Send event");
289
290            loop {
291                let poll = self.instance.as_mut().poll(&mut self.context);
292
293                match poll {
294                    task::Poll::Pending => match self.receiver.try_next() {
295                        Ok(Some(control)) => match control {
296                            Control::ChangeFlow(flow) => {
297                                use winit::event_loop::ControlFlow;
298
299                                match (event_loop.control_flow(), flow) {
300                                    (
301                                        ControlFlow::WaitUntil(current),
302                                        ControlFlow::WaitUntil(new),
303                                    ) if current < new => {}
304                                    (
305                                        ControlFlow::WaitUntil(target),
306                                        ControlFlow::Wait,
307                                    ) if target > Instant::now() => {}
308                                    _ => {
309                                        event_loop.set_control_flow(flow);
310                                    }
311                                }
312                            }
313                            Control::CreateWindow {
314                                id,
315                                settings,
316                                title,
317                                scale_factor,
318                                monitor,
319                                on_open,
320                            } => {
321                                let exit_on_close_request =
322                                    settings.exit_on_close_request;
323
324                                let visible = settings.visible;
325
326                                #[cfg(target_arch = "wasm32")]
327                                let target =
328                                    settings.platform_specific.target.clone();
329
330                                let window_attributes =
331                                    conversion::window_attributes(
332                                        settings,
333                                        &title,
334                                        scale_factor,
335                                        monitor
336                                            .or(event_loop.primary_monitor()),
337                                        self.id.clone(),
338                                    )
339                                    .with_visible(false);
340
341                                #[cfg(target_arch = "wasm32")]
342                                let window_attributes = {
343                                    use winit::platform::web::WindowAttributesExtWebSys;
344                                    window_attributes
345                                        .with_canvas(self.canvas.take())
346                                };
347
348                                log::info!(
349                                    "Window attributes for id `{id:#?}`: {window_attributes:#?}"
350                                );
351
352                                // On macOS, the `position` in `WindowAttributes` represents the "inner"
353                                // position of the window; while on other platforms it's the "outer" position.
354                                // We fix the inconsistency on macOS by positioning the window after creation.
355                                #[cfg(target_os = "macos")]
356                                let mut window_attributes = window_attributes;
357
358                                #[cfg(target_os = "macos")]
359                                let position =
360                                    window_attributes.position.take();
361
362                                let window = event_loop
363                                    .create_window(window_attributes)
364                                    .expect("Create window");
365
366                                #[cfg(target_os = "macos")]
367                                if let Some(position) = position {
368                                    window.set_outer_position(position);
369                                }
370
371                                #[cfg(target_arch = "wasm32")]
372                                {
373                                    use winit::platform::web::WindowExtWebSys;
374
375                                    let canvas = window
376                                        .canvas()
377                                        .expect("Get window canvas");
378
379                                    let _ = canvas.set_attribute(
380                                        "style",
381                                        "display: block; width: 100%; height: 100%",
382                                    );
383
384                                    let window = web_sys::window().unwrap();
385                                    let document = window.document().unwrap();
386                                    let body = document.body().unwrap();
387
388                                    let target = target.and_then(|target| {
389                                        body.query_selector(&format!(
390                                            "#{target}"
391                                        ))
392                                        .ok()
393                                        .unwrap_or(None)
394                                    });
395
396                                    match target {
397                                        Some(node) => {
398                                            let _ = node
399                                                .replace_with_with_node_1(
400                                                    &canvas,
401                                                )
402                                                .expect(&format!(
403                                                    "Could not replace #{}",
404                                                    node.id()
405                                                ));
406                                        }
407                                        None => {
408                                            let _ = body
409                                                .append_child(&canvas)
410                                                .expect(
411                                                "Append canvas to HTML body",
412                                            );
413                                        }
414                                    };
415                                }
416
417                                self.process_event(
418                                    event_loop,
419                                    Event::WindowCreated {
420                                        id,
421                                        window: Arc::new(window),
422                                        exit_on_close_request,
423                                        make_visible: visible,
424                                        on_open,
425                                    },
426                                );
427                            }
428                            Control::Exit => {
429                                self.process_event(event_loop, Event::Exit);
430                                event_loop.exit();
431                                break;
432                            }
433                            Control::Crash(error) => {
434                                self.error = Some(error);
435                                event_loop.exit();
436                            }
437                        },
438                        _ => {
439                            break;
440                        }
441                    },
442                    task::Poll::Ready(_) => {
443                        event_loop.exit();
444                        break;
445                    }
446                };
447            }
448        }
449    }
450
451    #[cfg(not(target_arch = "wasm32"))]
452    {
453        let mut runner = runner;
454        let _ = event_loop.run_app(&mut runner);
455
456        runner.error.map(Err).unwrap_or(Ok(()))
457    }
458
459    #[cfg(target_arch = "wasm32")]
460    {
461        use winit::platform::web::EventLoopExtWebSys;
462        let _ = event_loop.spawn_app(runner);
463
464        Ok(())
465    }
466}
467
468#[derive(Debug)]
469enum Event<Message: 'static> {
470    WindowCreated {
471        id: window::Id,
472        window: Arc<winit::window::Window>,
473        exit_on_close_request: bool,
474        make_visible: bool,
475        on_open: oneshot::Sender<window::Id>,
476    },
477    EventLoopAwakened(winit::event::Event<Message>),
478    Exit,
479}
480
481#[derive(Debug)]
482enum Control {
483    ChangeFlow(winit::event_loop::ControlFlow),
484    Exit,
485    Crash(Error),
486    CreateWindow {
487        id: window::Id,
488        settings: window::Settings,
489        title: String,
490        monitor: Option<winit::monitor::MonitorHandle>,
491        on_open: oneshot::Sender<window::Id>,
492        scale_factor: f32,
493    },
494}
495
496async fn run_instance<P>(
497    mut program: program::Instance<P>,
498    mut runtime: Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>,
499    mut proxy: Proxy<P::Message>,
500    mut event_receiver: mpsc::UnboundedReceiver<Event<Action<P::Message>>>,
501    mut control_sender: mpsc::UnboundedSender<Control>,
502    is_daemon: bool,
503    graphics_settings: graphics::Settings,
504    default_fonts: Vec<Cow<'static, [u8]>>,
505    mut _system_theme: oneshot::Receiver<theme::Mode>,
506) where
507    P: Program + 'static,
508    P::Theme: theme::Base,
509{
510    use winit::event;
511    use winit::event_loop::ControlFlow;
512
513    let mut window_manager = WindowManager::new();
514    let mut is_window_opening = !is_daemon;
515
516    let mut compositor = None;
517    let mut events = Vec::new();
518    let mut messages = Vec::new();
519    let mut actions = 0;
520
521    let mut ui_caches = FxHashMap::default();
522    let mut user_interfaces = ManuallyDrop::new(FxHashMap::default());
523    let mut clipboard = Clipboard::unconnected();
524
525    #[cfg(all(feature = "linux-theme-detection", target_os = "linux"))]
526    let mut system_theme = {
527        let to_mode = |color_scheme| match color_scheme {
528            mundy::ColorScheme::NoPreference => theme::Mode::None,
529            mundy::ColorScheme::Light => theme::Mode::Light,
530            mundy::ColorScheme::Dark => theme::Mode::Dark,
531        };
532
533        runtime.run(
534            mundy::Preferences::stream(mundy::Interest::ColorScheme)
535                .map(move |preferences| {
536                    Action::System(system::Action::NotifyTheme(to_mode(
537                        preferences.color_scheme,
538                    )))
539                })
540                .boxed(),
541        );
542
543        mundy::Preferences::once_blocking(
544            mundy::Interest::ColorScheme,
545            core::time::Duration::from_millis(200),
546        )
547        .map(|preferences| to_mode(preferences.color_scheme))
548        .unwrap_or_default()
549    };
550
551    #[cfg(not(all(feature = "linux-theme-detection", target_os = "linux")))]
552    let mut system_theme =
553        _system_theme.try_recv().ok().flatten().unwrap_or_default();
554
555    log::info!("System theme: {system_theme:?}");
556
557    loop {
558        // Empty the queue if possible
559        let event = if let Ok(event) = event_receiver.try_next() {
560            event
561        } else {
562            event_receiver.next().await
563        };
564
565        let Some(event) = event else {
566            break;
567        };
568
569        match event {
570            Event::WindowCreated {
571                id,
572                window,
573                exit_on_close_request,
574                make_visible,
575                on_open,
576            } => {
577                if compositor.is_none() {
578                    let (compositor_sender, compositor_receiver) =
579                        oneshot::channel();
580
581                    let create_compositor = {
582                        let window = window.clone();
583                        let proxy = proxy.clone();
584                        let default_fonts = default_fonts.clone();
585
586                        async move {
587                            let mut compositor =
588                                <P::Renderer as compositor::Default>::Compositor::new(graphics_settings, window).await;
589
590                            if let Ok(compositor) = &mut compositor {
591                                for font in default_fonts {
592                                    compositor.load_font(font.clone());
593                                }
594                            }
595
596                            compositor_sender
597                                .send(compositor)
598                                .ok()
599                                .expect("Send compositor");
600
601                            // HACK! Send a proxy event on completion to trigger
602                            // a runtime re-poll
603                            // TODO: Send compositor through proxy (?)
604                            {
605                                let (sender, _receiver) = oneshot::channel();
606
607                                proxy.send_action(Action::Window(
608                                    runtime::window::Action::GetLatest(sender),
609                                ));
610                            }
611                        }
612                    };
613
614                    #[cfg(target_arch = "wasm32")]
615                    wasm_bindgen_futures::spawn_local(create_compositor);
616
617                    #[cfg(not(target_arch = "wasm32"))]
618                    runtime.block_on(create_compositor);
619
620                    match compositor_receiver
621                        .await
622                        .expect("Wait for compositor")
623                    {
624                        Ok(new_compositor) => {
625                            compositor = Some(new_compositor);
626                        }
627                        Err(error) => {
628                            let _ = control_sender
629                                .start_send(Control::Crash(error.into()));
630                            continue;
631                        }
632                    }
633                }
634
635                let window_theme = window
636                    .theme()
637                    .map(conversion::theme_mode)
638                    .unwrap_or_default();
639
640                if system_theme != window_theme {
641                    system_theme = window_theme;
642
643                    runtime.broadcast(subscription::Event::SystemThemeChanged(
644                        window_theme,
645                    ));
646                }
647
648                let is_first = window_manager.is_empty();
649                let window = window_manager.insert(
650                    id,
651                    window,
652                    &program,
653                    compositor
654                        .as_mut()
655                        .expect("Compositor must be initialized"),
656                    exit_on_close_request,
657                    system_theme,
658                );
659
660                window.raw.set_theme(conversion::window_theme(
661                    window.state.theme_mode(),
662                ));
663
664                debug::theme_changed(|| {
665                    if is_first {
666                        theme::Base::palette(window.state.theme())
667                    } else {
668                        None
669                    }
670                });
671
672                let logical_size = window.state.logical_size();
673
674                let _ = user_interfaces.insert(
675                    id,
676                    build_user_interface(
677                        &program,
678                        user_interface::Cache::default(),
679                        &mut window.renderer,
680                        logical_size,
681                        id,
682                    ),
683                );
684                let _ = ui_caches.insert(id, user_interface::Cache::default());
685
686                if make_visible {
687                    window.raw.set_visible(true);
688                }
689
690                events.push((
691                    id,
692                    core::Event::Window(window::Event::Opened {
693                        position: window.position(),
694                        size: window.size(),
695                    }),
696                ));
697
698                if clipboard.window_id().is_none() {
699                    clipboard = Clipboard::connect(window.raw.clone());
700                }
701
702                let _ = on_open.send(id);
703                is_window_opening = false;
704            }
705            Event::EventLoopAwakened(event) => {
706                match event {
707                    event::Event::NewEvents(event::StartCause::Init) => {
708                        for (_id, window) in window_manager.iter_mut() {
709                            window.raw.request_redraw();
710                        }
711                    }
712                    event::Event::NewEvents(
713                        event::StartCause::ResumeTimeReached { .. },
714                    ) => {
715                        let now = Instant::now();
716
717                        for (_id, window) in window_manager.iter_mut() {
718                            if let Some(redraw_at) = window.redraw_at
719                                && redraw_at <= now
720                            {
721                                window.raw.request_redraw();
722                                window.redraw_at = None;
723                            }
724                        }
725
726                        if let Some(redraw_at) = window_manager.redraw_at() {
727                            let _ =
728                                control_sender.start_send(Control::ChangeFlow(
729                                    ControlFlow::WaitUntil(redraw_at),
730                                ));
731                        } else {
732                            let _ = control_sender.start_send(
733                                Control::ChangeFlow(ControlFlow::Wait),
734                            );
735                        }
736                    }
737                    event::Event::PlatformSpecific(
738                        event::PlatformSpecific::MacOS(
739                            event::MacOS::ReceivedUrl(url),
740                        ),
741                    ) => {
742                        runtime.broadcast(
743                            subscription::Event::PlatformSpecific(
744                                subscription::PlatformSpecific::MacOS(
745                                    subscription::MacOS::ReceivedUrl(url),
746                                ),
747                            ),
748                        );
749                    }
750                    event::Event::UserEvent(action) => {
751                        run_action(
752                            action,
753                            &program,
754                            &mut runtime,
755                            &mut compositor,
756                            &mut events,
757                            &mut messages,
758                            &mut clipboard,
759                            &mut control_sender,
760                            &mut user_interfaces,
761                            &mut window_manager,
762                            &mut ui_caches,
763                            &mut is_window_opening,
764                            &mut system_theme,
765                        );
766                        actions += 1;
767                    }
768                    event::Event::WindowEvent {
769                        window_id: id,
770                        event: event::WindowEvent::RedrawRequested,
771                        ..
772                    } => {
773                        let Some(compositor) = &mut compositor else {
774                            continue;
775                        };
776
777                        let Some((id, window)) =
778                            window_manager.get_mut_alias(id)
779                        else {
780                            continue;
781                        };
782
783                        let physical_size = window.state.physical_size();
784
785                        if physical_size.width == 0 || physical_size.height == 0
786                        {
787                            continue;
788                        }
789
790                        if window.viewport_version
791                            != window.state.viewport_version()
792                        {
793                            let logical_size = window.state.logical_size();
794
795                            let layout_span = debug::layout(id);
796                            let ui = user_interfaces
797                                .remove(&id)
798                                .expect("Remove user interface");
799
800                            let _ = user_interfaces.insert(
801                                id,
802                                ui.relayout(logical_size, &mut window.renderer),
803                            );
804                            layout_span.finish();
805
806                            compositor.configure_surface(
807                                &mut window.surface,
808                                physical_size.width,
809                                physical_size.height,
810                            );
811
812                            window.viewport_version =
813                                window.state.viewport_version();
814                        }
815
816                        let redraw_event = core::Event::Window(
817                            window::Event::RedrawRequested(Instant::now()),
818                        );
819
820                        let cursor = window.state.cursor();
821
822                        let ui = user_interfaces
823                            .get_mut(&id)
824                            .expect("Get user interface");
825
826                        let draw_span = debug::draw(id);
827                        let (ui_state, _) = ui.update(
828                            slice::from_ref(&redraw_event),
829                            cursor,
830                            &mut window.renderer,
831                            &mut clipboard,
832                            &mut messages,
833                        );
834
835                        ui.draw(
836                            &mut window.renderer,
837                            window.state.theme(),
838                            &renderer::Style {
839                                text_color: window.state.text_color(),
840                            },
841                            cursor,
842                        );
843                        draw_span.finish();
844
845                        runtime.broadcast(subscription::Event::Interaction {
846                            window: id,
847                            event: redraw_event,
848                            status: core::event::Status::Ignored,
849                        });
850
851                        if let user_interface::State::Updated {
852                            redraw_request,
853                            input_method,
854                            mouse_interaction,
855                        } = ui_state
856                        {
857                            window.request_redraw(redraw_request);
858                            window.request_input_method(input_method);
859                            window.update_mouse(mouse_interaction);
860                        }
861
862                        window.draw_preedit();
863
864                        let present_span = debug::present(id);
865                        match compositor.present(
866                            &mut window.renderer,
867                            &mut window.surface,
868                            window.state.viewport(),
869                            window.state.background_color(),
870                            || window.raw.pre_present_notify(),
871                        ) {
872                            Ok(()) => {
873                                present_span.finish();
874                            }
875                            Err(error) => match error {
876                                // This is an unrecoverable error.
877                                compositor::SurfaceError::OutOfMemory => {
878                                    panic!("{error:?}");
879                                }
880                                _ => {
881                                    present_span.finish();
882
883                                    log::error!(
884                                        "Error {error:?} when \
885                                        presenting surface."
886                                    );
887
888                                    // Try rendering all windows again next frame.
889                                    for (_id, window) in
890                                        window_manager.iter_mut()
891                                    {
892                                        window.raw.request_redraw();
893                                    }
894                                }
895                            },
896                        }
897                    }
898                    event::Event::WindowEvent {
899                        event: window_event,
900                        window_id,
901                    } => {
902                        if !is_daemon
903                            && matches!(
904                                window_event,
905                                winit::event::WindowEvent::Destroyed
906                            )
907                            && !is_window_opening
908                            && window_manager.is_empty()
909                        {
910                            control_sender
911                                .start_send(Control::Exit)
912                                .expect("Send control action");
913
914                            continue;
915                        }
916
917                        let Some((id, window)) =
918                            window_manager.get_mut_alias(window_id)
919                        else {
920                            continue;
921                        };
922
923                        match window_event {
924                            winit::event::WindowEvent::Resized(_) => {
925                                window.raw.request_redraw();
926                            }
927                            winit::event::WindowEvent::ThemeChanged(theme) => {
928                                let mode = conversion::theme_mode(theme);
929
930                                if mode != system_theme {
931                                    system_theme = mode;
932
933                                    runtime.broadcast(
934                                        subscription::Event::SystemThemeChanged(
935                                            mode,
936                                        ),
937                                    );
938                                }
939                            }
940                            _ => {}
941                        }
942
943                        if matches!(
944                            window_event,
945                            winit::event::WindowEvent::CloseRequested
946                        ) && window.exit_on_close_request
947                        {
948                            run_action(
949                                Action::Window(runtime::window::Action::Close(
950                                    id,
951                                )),
952                                &program,
953                                &mut runtime,
954                                &mut compositor,
955                                &mut events,
956                                &mut messages,
957                                &mut clipboard,
958                                &mut control_sender,
959                                &mut user_interfaces,
960                                &mut window_manager,
961                                &mut ui_caches,
962                                &mut is_window_opening,
963                                &mut system_theme,
964                            );
965                        } else {
966                            window.state.update(
967                                &program,
968                                &window.raw,
969                                &window_event,
970                            );
971
972                            if let Some(event) = conversion::window_event(
973                                window_event,
974                                window.state.scale_factor(),
975                                window.state.modifiers(),
976                            ) {
977                                events.push((id, event));
978                            }
979                        }
980                    }
981                    event::Event::AboutToWait => {
982                        if actions > 0 {
983                            proxy.free_slots(actions);
984                            actions = 0;
985                        }
986
987                        if events.is_empty()
988                            && messages.is_empty()
989                            && window_manager.is_idle()
990                        {
991                            continue;
992                        }
993
994                        let mut uis_stale = false;
995
996                        for (id, window) in window_manager.iter_mut() {
997                            let interact_span = debug::interact(id);
998                            let mut window_events = vec![];
999
1000                            events.retain(|(window_id, event)| {
1001                                if *window_id == id {
1002                                    window_events.push(event.clone());
1003                                    false
1004                                } else {
1005                                    true
1006                                }
1007                            });
1008
1009                            if window_events.is_empty() && messages.is_empty() {
1010                                continue;
1011                            }
1012
1013                            let (ui_state, statuses) = user_interfaces
1014                                .get_mut(&id)
1015                                .expect("Get user interface")
1016                                .update(
1017                                    &window_events,
1018                                    window.state.cursor(),
1019                                    &mut window.renderer,
1020                                    &mut clipboard,
1021                                    &mut messages,
1022                                );
1023
1024                            #[cfg(feature = "unconditional-rendering")]
1025                            window.request_redraw(
1026                                window::RedrawRequest::NextFrame,
1027                            );
1028
1029                            match ui_state {
1030                                user_interface::State::Updated {
1031                                    redraw_request: _redraw_request,
1032                                    mouse_interaction,
1033                                    ..
1034                                } => {
1035                                    window.update_mouse(mouse_interaction);
1036
1037                                    #[cfg(not(
1038                                        feature = "unconditional-rendering"
1039                                    ))]
1040                                    window.request_redraw(_redraw_request);
1041                                }
1042                                user_interface::State::Outdated => {
1043                                    uis_stale = true;
1044                                }
1045                            }
1046
1047                            for (event, status) in window_events
1048                                .into_iter()
1049                                .zip(statuses.into_iter())
1050                            {
1051                                runtime.broadcast(
1052                                    subscription::Event::Interaction {
1053                                        window: id,
1054                                        event,
1055                                        status,
1056                                    },
1057                                );
1058                            }
1059
1060                            interact_span.finish();
1061                        }
1062
1063                        for (id, event) in events.drain(..) {
1064                            runtime.broadcast(
1065                                subscription::Event::Interaction {
1066                                    window: id,
1067                                    event,
1068                                    status: core::event::Status::Ignored,
1069                                },
1070                            );
1071                        }
1072
1073                        if !messages.is_empty() || uis_stale {
1074                            let cached_interfaces: FxHashMap<
1075                                window::Id,
1076                                user_interface::Cache,
1077                            > = ManuallyDrop::into_inner(user_interfaces)
1078                                .drain()
1079                                .map(|(id, ui)| (id, ui.into_cache()))
1080                                .collect();
1081
1082                            update(&mut program, &mut runtime, &mut messages);
1083
1084                            for (id, window) in window_manager.iter_mut() {
1085                                window.state.synchronize(
1086                                    &program,
1087                                    id,
1088                                    &window.raw,
1089                                );
1090
1091                                window.raw.request_redraw();
1092                            }
1093
1094                            debug::theme_changed(|| {
1095                                window_manager.first().and_then(|window| {
1096                                    theme::Base::palette(window.state.theme())
1097                                })
1098                            });
1099
1100                            user_interfaces =
1101                                ManuallyDrop::new(build_user_interfaces(
1102                                    &program,
1103                                    &mut window_manager,
1104                                    cached_interfaces,
1105                                ));
1106                        }
1107
1108                        if let Some(redraw_at) = window_manager.redraw_at() {
1109                            let _ =
1110                                control_sender.start_send(Control::ChangeFlow(
1111                                    ControlFlow::WaitUntil(redraw_at),
1112                                ));
1113                        } else {
1114                            let _ = control_sender.start_send(
1115                                Control::ChangeFlow(ControlFlow::Wait),
1116                            );
1117                        }
1118                    }
1119                    _ => {}
1120                }
1121            }
1122            Event::Exit => break,
1123        }
1124    }
1125
1126    let _ = ManuallyDrop::into_inner(user_interfaces);
1127}
1128
1129/// Builds a window's [`UserInterface`] for the [`Program`].
1130fn build_user_interface<'a, P: Program>(
1131    program: &'a program::Instance<P>,
1132    cache: user_interface::Cache,
1133    renderer: &mut P::Renderer,
1134    size: Size,
1135    id: window::Id,
1136) -> UserInterface<'a, P::Message, P::Theme, P::Renderer>
1137where
1138    P::Theme: theme::Base,
1139{
1140    let view_span = debug::view(id);
1141    let view = program.view(id);
1142    view_span.finish();
1143
1144    let layout_span = debug::layout(id);
1145    let user_interface = UserInterface::build(view, size, cache, renderer);
1146    layout_span.finish();
1147
1148    user_interface
1149}
1150
1151fn update<P: Program, E: Executor>(
1152    program: &mut program::Instance<P>,
1153    runtime: &mut Runtime<E, Proxy<P::Message>, Action<P::Message>>,
1154    messages: &mut Vec<P::Message>,
1155) where
1156    P::Theme: theme::Base,
1157{
1158    for message in messages.drain(..) {
1159        let task = runtime.enter(|| program.update(message));
1160
1161        if let Some(stream) = runtime::task::into_stream(task) {
1162            runtime.run(stream);
1163        }
1164    }
1165
1166    let subscription = runtime.enter(|| program.subscription());
1167    let recipes = subscription::into_recipes(subscription.map(Action::Output));
1168
1169    runtime.track(recipes);
1170}
1171
1172fn run_action<'a, P, C>(
1173    action: Action<P::Message>,
1174    program: &'a program::Instance<P>,
1175    runtime: &mut Runtime<P::Executor, Proxy<P::Message>, Action<P::Message>>,
1176    compositor: &mut Option<C>,
1177    events: &mut Vec<(window::Id, core::Event)>,
1178    messages: &mut Vec<P::Message>,
1179    clipboard: &mut Clipboard,
1180    control_sender: &mut mpsc::UnboundedSender<Control>,
1181    interfaces: &mut FxHashMap<
1182        window::Id,
1183        UserInterface<'a, P::Message, P::Theme, P::Renderer>,
1184    >,
1185    window_manager: &mut WindowManager<P, C>,
1186    ui_caches: &mut FxHashMap<window::Id, user_interface::Cache>,
1187    is_window_opening: &mut bool,
1188    system_theme: &mut theme::Mode,
1189) where
1190    P: Program,
1191    C: Compositor<Renderer = P::Renderer> + 'static,
1192    P::Theme: theme::Base,
1193{
1194    use crate::runtime::clipboard;
1195    use crate::runtime::window;
1196
1197    match action {
1198        Action::Output(message) => {
1199            messages.push(message);
1200        }
1201        Action::Clipboard(action) => match action {
1202            clipboard::Action::Read { target, channel } => {
1203                let _ = channel.send(clipboard.read(target));
1204            }
1205            clipboard::Action::Write { target, contents } => {
1206                clipboard.write(target, contents);
1207            }
1208        },
1209        Action::Window(action) => match action {
1210            window::Action::Open(id, settings, channel) => {
1211                let monitor = window_manager.last_monitor();
1212
1213                control_sender
1214                    .start_send(Control::CreateWindow {
1215                        id,
1216                        settings,
1217                        title: program.title(id),
1218                        scale_factor: program.scale_factor(id),
1219                        monitor,
1220                        on_open: channel,
1221                    })
1222                    .expect("Send control action");
1223
1224                *is_window_opening = true;
1225            }
1226            window::Action::Close(id) => {
1227                let _ = ui_caches.remove(&id);
1228                let _ = interfaces.remove(&id);
1229
1230                if let Some(window) = window_manager.remove(id) {
1231                    if clipboard.window_id() == Some(window.raw.id()) {
1232                        *clipboard = window_manager
1233                            .first()
1234                            .map(|window| window.raw.clone())
1235                            .map(Clipboard::connect)
1236                            .unwrap_or_else(Clipboard::unconnected);
1237                    }
1238
1239                    events.push((
1240                        id,
1241                        core::Event::Window(core::window::Event::Closed),
1242                    ));
1243                }
1244
1245                if window_manager.is_empty() {
1246                    *compositor = None;
1247                }
1248            }
1249            window::Action::GetOldest(channel) => {
1250                let id =
1251                    window_manager.iter_mut().next().map(|(id, _window)| id);
1252
1253                let _ = channel.send(id);
1254            }
1255            window::Action::GetLatest(channel) => {
1256                let id =
1257                    window_manager.iter_mut().last().map(|(id, _window)| id);
1258
1259                let _ = channel.send(id);
1260            }
1261            window::Action::Drag(id) => {
1262                if let Some(window) = window_manager.get_mut(id) {
1263                    let _ = window.raw.drag_window();
1264                }
1265            }
1266            window::Action::DragResize(id, direction) => {
1267                if let Some(window) = window_manager.get_mut(id) {
1268                    let _ = window.raw.drag_resize_window(
1269                        conversion::resize_direction(direction),
1270                    );
1271                }
1272            }
1273            window::Action::Resize(id, size) => {
1274                if let Some(window) = window_manager.get_mut(id) {
1275                    let _ = window.raw.request_inner_size(
1276                        winit::dpi::LogicalSize {
1277                            width: size.width,
1278                            height: size.height,
1279                        }
1280                        .to_physical::<f32>(f64::from(
1281                            window.state.scale_factor(),
1282                        )),
1283                    );
1284                }
1285            }
1286            window::Action::SetMinSize(id, size) => {
1287                if let Some(window) = window_manager.get_mut(id) {
1288                    window.raw.set_min_inner_size(size.map(|size| {
1289                        winit::dpi::LogicalSize {
1290                            width: size.width,
1291                            height: size.height,
1292                        }
1293                        .to_physical::<f32>(f64::from(
1294                            window.state.scale_factor(),
1295                        ))
1296                    }));
1297                }
1298            }
1299            window::Action::SetMaxSize(id, size) => {
1300                if let Some(window) = window_manager.get_mut(id) {
1301                    window.raw.set_max_inner_size(size.map(|size| {
1302                        winit::dpi::LogicalSize {
1303                            width: size.width,
1304                            height: size.height,
1305                        }
1306                        .to_physical::<f32>(f64::from(
1307                            window.state.scale_factor(),
1308                        ))
1309                    }));
1310                }
1311            }
1312            window::Action::SetResizeIncrements(id, increments) => {
1313                if let Some(window) = window_manager.get_mut(id) {
1314                    window.raw.set_resize_increments(increments.map(|size| {
1315                        winit::dpi::LogicalSize {
1316                            width: size.width,
1317                            height: size.height,
1318                        }
1319                        .to_physical::<f32>(f64::from(
1320                            window.state.scale_factor(),
1321                        ))
1322                    }));
1323                }
1324            }
1325            window::Action::SetResizable(id, resizable) => {
1326                if let Some(window) = window_manager.get_mut(id) {
1327                    window.raw.set_resizable(resizable);
1328                }
1329            }
1330            window::Action::GetSize(id, channel) => {
1331                if let Some(window) = window_manager.get_mut(id) {
1332                    let size = window
1333                        .raw
1334                        .inner_size()
1335                        .to_logical(f64::from(window.state.scale_factor()));
1336
1337                    let _ = channel.send(Size::new(size.width, size.height));
1338                }
1339            }
1340            window::Action::GetMaximized(id, channel) => {
1341                if let Some(window) = window_manager.get_mut(id) {
1342                    let _ = channel.send(window.raw.is_maximized());
1343                }
1344            }
1345            window::Action::Maximize(id, maximized) => {
1346                if let Some(window) = window_manager.get_mut(id) {
1347                    window.raw.set_maximized(maximized);
1348                }
1349            }
1350            window::Action::GetMinimized(id, channel) => {
1351                if let Some(window) = window_manager.get_mut(id) {
1352                    let _ = channel.send(window.raw.is_minimized());
1353                }
1354            }
1355            window::Action::Minimize(id, minimized) => {
1356                if let Some(window) = window_manager.get_mut(id) {
1357                    window.raw.set_minimized(minimized);
1358                }
1359            }
1360            window::Action::GetPosition(id, channel) => {
1361                if let Some(window) = window_manager.get(id) {
1362                    let position = window
1363                        .raw
1364                        .outer_position()
1365                        .map(|position| {
1366                            let position = position
1367                                .to_logical::<f32>(window.raw.scale_factor());
1368
1369                            Point::new(position.x, position.y)
1370                        })
1371                        .ok();
1372
1373                    let _ = channel.send(position);
1374                }
1375            }
1376            window::Action::GetScaleFactor(id, channel) => {
1377                if let Some(window) = window_manager.get_mut(id) {
1378                    let scale_factor = window.raw.scale_factor();
1379
1380                    let _ = channel.send(scale_factor as f32);
1381                }
1382            }
1383            window::Action::Move(id, position) => {
1384                if let Some(window) = window_manager.get_mut(id) {
1385                    window.raw.set_outer_position(
1386                        winit::dpi::LogicalPosition {
1387                            x: position.x,
1388                            y: position.y,
1389                        },
1390                    );
1391                }
1392            }
1393            window::Action::SetMode(id, mode) => {
1394                if let Some(window) = window_manager.get_mut(id) {
1395                    window.raw.set_visible(conversion::visible(mode));
1396                    window.raw.set_fullscreen(conversion::fullscreen(
1397                        window.raw.current_monitor(),
1398                        mode,
1399                    ));
1400                }
1401            }
1402            window::Action::SetIcon(id, icon) => {
1403                if let Some(window) = window_manager.get_mut(id) {
1404                    window.raw.set_window_icon(conversion::icon(icon));
1405                }
1406            }
1407            window::Action::GetMode(id, channel) => {
1408                if let Some(window) = window_manager.get_mut(id) {
1409                    let mode = if window.raw.is_visible().unwrap_or(true) {
1410                        conversion::mode(window.raw.fullscreen())
1411                    } else {
1412                        core::window::Mode::Hidden
1413                    };
1414
1415                    let _ = channel.send(mode);
1416                }
1417            }
1418            window::Action::ToggleMaximize(id) => {
1419                if let Some(window) = window_manager.get_mut(id) {
1420                    window.raw.set_maximized(!window.raw.is_maximized());
1421                }
1422            }
1423            window::Action::ToggleDecorations(id) => {
1424                if let Some(window) = window_manager.get_mut(id) {
1425                    window.raw.set_decorations(!window.raw.is_decorated());
1426                }
1427            }
1428            window::Action::RequestUserAttention(id, attention_type) => {
1429                if let Some(window) = window_manager.get_mut(id) {
1430                    window.raw.request_user_attention(
1431                        attention_type.map(conversion::user_attention),
1432                    );
1433                }
1434            }
1435            window::Action::GainFocus(id) => {
1436                if let Some(window) = window_manager.get_mut(id) {
1437                    window.raw.focus_window();
1438                }
1439            }
1440            window::Action::SetLevel(id, level) => {
1441                if let Some(window) = window_manager.get_mut(id) {
1442                    window
1443                        .raw
1444                        .set_window_level(conversion::window_level(level));
1445                }
1446            }
1447            window::Action::ShowSystemMenu(id) => {
1448                if let Some(window) = window_manager.get_mut(id)
1449                    && let mouse::Cursor::Available(point) =
1450                        window.state.cursor()
1451                {
1452                    window.raw.show_window_menu(winit::dpi::LogicalPosition {
1453                        x: point.x,
1454                        y: point.y,
1455                    });
1456                }
1457            }
1458            window::Action::GetRawId(id, channel) => {
1459                if let Some(window) = window_manager.get_mut(id) {
1460                    let _ = channel.send(window.raw.id().into());
1461                }
1462            }
1463            window::Action::RunWithHandle(id, f) => {
1464                use window::raw_window_handle::HasWindowHandle;
1465
1466                if let Some(handle) = window_manager
1467                    .get_mut(id)
1468                    .and_then(|window| window.raw.window_handle().ok())
1469                {
1470                    f(handle);
1471                }
1472            }
1473            window::Action::Screenshot(id, channel) => {
1474                if let Some(window) = window_manager.get_mut(id)
1475                    && let Some(compositor) = compositor
1476                {
1477                    let bytes = compositor.screenshot(
1478                        &mut window.renderer,
1479                        window.state.viewport(),
1480                        window.state.background_color(),
1481                    );
1482
1483                    let _ = channel.send(core::window::Screenshot::new(
1484                        bytes,
1485                        window.state.physical_size(),
1486                        window.state.viewport().scale_factor(),
1487                    ));
1488                }
1489            }
1490            window::Action::EnableMousePassthrough(id) => {
1491                if let Some(window) = window_manager.get_mut(id) {
1492                    let _ = window.raw.set_cursor_hittest(false);
1493                }
1494            }
1495            window::Action::DisableMousePassthrough(id) => {
1496                if let Some(window) = window_manager.get_mut(id) {
1497                    let _ = window.raw.set_cursor_hittest(true);
1498                }
1499            }
1500        },
1501        Action::System(action) => match action {
1502            system::Action::GetInformation(_channel) => {
1503                #[cfg(feature = "sysinfo")]
1504                {
1505                    if let Some(compositor) = compositor {
1506                        let graphics_info = compositor.information();
1507
1508                        let _ = std::thread::spawn(move || {
1509                            let information = system_information(graphics_info);
1510
1511                            let _ = _channel.send(information);
1512                        });
1513                    }
1514                }
1515            }
1516            system::Action::GetTheme(channel) => {
1517                let _ = channel.send(*system_theme);
1518            }
1519            system::Action::NotifyTheme(mode) => {
1520                if mode != *system_theme {
1521                    *system_theme = mode;
1522
1523                    runtime.broadcast(subscription::Event::SystemThemeChanged(
1524                        mode,
1525                    ));
1526                }
1527
1528                let Some(theme) = conversion::window_theme(mode) else {
1529                    return;
1530                };
1531
1532                for (_id, window) in window_manager.iter_mut() {
1533                    window.state.update(
1534                        program,
1535                        &window.raw,
1536                        &winit::event::WindowEvent::ThemeChanged(theme),
1537                    );
1538                }
1539            }
1540        },
1541        Action::Widget(operation) => {
1542            let mut current_operation = Some(operation);
1543
1544            while let Some(mut operation) = current_operation.take() {
1545                for (id, ui) in interfaces.iter_mut() {
1546                    if let Some(window) = window_manager.get_mut(*id) {
1547                        ui.operate(&window.renderer, operation.as_mut());
1548                    }
1549                }
1550
1551                match operation.finish() {
1552                    operation::Outcome::None => {}
1553                    operation::Outcome::Some(()) => {}
1554                    operation::Outcome::Chain(next) => {
1555                        current_operation = Some(next);
1556                    }
1557                }
1558            }
1559        }
1560        Action::LoadFont { bytes, channel } => {
1561            if let Some(compositor) = compositor {
1562                // TODO: Error handling (?)
1563                compositor.load_font(bytes.clone());
1564
1565                let _ = channel.send(Ok(()));
1566            }
1567        }
1568        Action::Reload => {
1569            for (id, window) in window_manager.iter_mut() {
1570                let Some(ui) = interfaces.remove(&id) else {
1571                    continue;
1572                };
1573
1574                let cache = ui.into_cache();
1575                let size = window.size();
1576
1577                let _ = interfaces.insert(
1578                    id,
1579                    build_user_interface(
1580                        program,
1581                        cache,
1582                        &mut window.renderer,
1583                        size,
1584                        id,
1585                    ),
1586                );
1587
1588                window.raw.request_redraw();
1589            }
1590        }
1591        Action::Exit => {
1592            control_sender
1593                .start_send(Control::Exit)
1594                .expect("Send control action");
1595        }
1596    }
1597}
1598
1599/// Build the user interface for every window.
1600pub fn build_user_interfaces<'a, P: Program, C>(
1601    program: &'a program::Instance<P>,
1602    window_manager: &mut WindowManager<P, C>,
1603    mut cached_user_interfaces: FxHashMap<window::Id, user_interface::Cache>,
1604) -> FxHashMap<window::Id, UserInterface<'a, P::Message, P::Theme, P::Renderer>>
1605where
1606    C: Compositor<Renderer = P::Renderer>,
1607    P::Theme: theme::Base,
1608{
1609    cached_user_interfaces
1610        .drain()
1611        .filter_map(|(id, cache)| {
1612            let window = window_manager.get_mut(id)?;
1613
1614            Some((
1615                id,
1616                build_user_interface(
1617                    program,
1618                    cache,
1619                    &mut window.renderer,
1620                    window.state.logical_size(),
1621                    id,
1622                ),
1623            ))
1624        })
1625        .collect()
1626}
1627
1628/// Returns true if the provided event should cause a [`Program`] to
1629/// exit.
1630pub fn user_force_quit(
1631    event: &winit::event::WindowEvent,
1632    _modifiers: winit::keyboard::ModifiersState,
1633) -> bool {
1634    match event {
1635        #[cfg(target_os = "macos")]
1636        winit::event::WindowEvent::KeyboardInput {
1637            event:
1638                winit::event::KeyEvent {
1639                    logical_key: winit::keyboard::Key::Character(c),
1640                    state: winit::event::ElementState::Pressed,
1641                    ..
1642                },
1643            ..
1644        } if c == "q" && _modifiers.super_key() => true,
1645        _ => false,
1646    }
1647}
1648
1649#[cfg(feature = "sysinfo")]
1650fn system_information(
1651    graphics: compositor::Information,
1652) -> system::Information {
1653    use sysinfo::{Process, System};
1654
1655    let mut system = System::new_all();
1656    system.refresh_all();
1657
1658    let cpu_brand = system
1659        .cpus()
1660        .first()
1661        .map(|cpu| cpu.brand().to_string())
1662        .unwrap_or_default();
1663
1664    let memory_used = sysinfo::get_current_pid()
1665        .and_then(|pid| system.process(pid).ok_or("Process not found"))
1666        .map(Process::memory)
1667        .ok();
1668
1669    system::Information {
1670        system_name: System::name(),
1671        system_kernel: System::kernel_version(),
1672        system_version: System::long_os_version(),
1673        system_short_version: System::os_version(),
1674        cpu_brand,
1675        cpu_cores: system.physical_core_count(),
1676        memory_total: system.total_memory(),
1677        memory_used,
1678        graphics_adapter: graphics.adapter,
1679        graphics_backend: graphics.backend,
1680    }
1681}