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