1use std::collections::HashMap;
2use std::sync::Arc;
3
4use platform_core::{
5 Event, EventHandler, FullscreenMode, MultiSurfacePlatform, Platform, PlatformError, SurfaceId,
6 Window, WindowConfig, WindowPosition,
7};
8use winit::application::ApplicationHandler;
9use winit::event::{StartCause, WindowEvent};
10use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
11use winit::window::{Fullscreen, WindowAttributes, WindowId, WindowLevel};
12
13use platform_winit::{SurfaceIntent, WinitWindow, map_window_event};
14
15enum UserEvent {
17 Accessibility(accesskit_winit::Event),
21 #[cfg(target_os = "linux")]
24 ColorScheme(bool),
25 Wake,
28}
29
30impl From<accesskit_winit::Event> for UserEvent {
31 fn from(event: accesskit_winit::Event) -> Self {
32 UserEvent::Accessibility(event)
33 }
34}
35
36pub struct WinitPlatform {
37 event_loop: EventLoop<UserEvent>,
38}
39
40impl WinitPlatform {
41 pub fn try_new() -> Result<Self, PlatformError> {
42 Ok(Self {
43 event_loop: EventLoop::<UserEvent>::with_user_event()
44 .build()
45 .map_err(|e| PlatformError(e.to_string()))?,
46 })
47 }
48}
49
50struct WinitRunner<H: EventHandler<WinitWindow>> {
51 handler: H,
52 window: Option<WinitWindow>,
53 config: WindowConfig,
54 cursor_position: (f64, f64),
55 scale_factor: f64,
56 modifiers: platform_core::ModifiersState,
57 timer_has_fired: bool,
59 a11y: Option<accesskit_winit::Adapter>,
62 a11y_proxy: winit::event_loop::EventLoopProxy<UserEvent>,
63 a11y_nodes: Vec<platform_core::AccessNode>,
65}
66
67impl<H: EventHandler<WinitWindow>> WinitRunner<H> {
68 fn on_accessibility(&mut self, event: accesskit_winit::WindowEvent) {
70 use accesskit_winit::WindowEvent as AkEvent;
71 match event {
72 AkEvent::InitialTreeRequested => self.publish_accessibility(),
73 AkEvent::ActionRequested(request) => {
74 let Some((id, activate)) =
75 crate::accessibility::requested_focus_id(&request, &self.a11y_nodes)
76 else {
77 return;
78 };
79 self.handler.on_accessibility_action(id, activate);
82 self.publish_accessibility();
83 }
84 AkEvent::AccessibilityDeactivated => self.a11y_nodes.clear(),
87 }
88 }
89
90 fn publish_accessibility(&mut self) {
93 let Some(adapter) = &mut self.a11y else {
94 return;
95 };
96 let nodes = self.handler.accessibility();
97 let title = self.config.title.clone();
98 adapter.update_if_active(|| crate::accessibility::tree_update(&nodes, &title));
99 self.a11y_nodes = nodes;
100 }
101}
102
103impl<H: EventHandler<WinitWindow>> ApplicationHandler<UserEvent> for WinitRunner<H> {
104 fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
105 match event {
106 UserEvent::Accessibility(event) => self.on_accessibility(event.window_event),
107 #[cfg(target_os = "linux")]
108 UserEvent::ColorScheme(dark) => {
109 if let Some(window) = &self.window {
110 self.handler
111 .on_event(Event::ColorSchemeChanged { dark }, window);
112 }
113 }
114 UserEvent::Wake => {
115 if let Some(window) = &self.window {
116 window.request_redraw();
117 }
118 }
119 }
120 }
121
122 fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
123 self.timer_has_fired = matches!(cause, StartCause::ResumeTimeReached { .. });
124 self.handler.new_events();
125 }
126
127 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
128 if let Some(d) = self.handler.about_to_wait() {
129 if self.timer_has_fired {
131 if let Some(window) = &self.window {
132 window.request_redraw();
133 }
134 }
135 event_loop.set_control_flow(ControlFlow::WaitUntil(std::time::Instant::now() + d));
136 } else {
137 event_loop.set_control_flow(ControlFlow::Wait);
138 }
139 }
140
141 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
142 let Some(window) = create_window_from_config(event_loop, &self.config) else {
143 return;
144 };
145 if let Some(dark) = initial_prefers_dark(&window) {
148 self.handler
149 .on_event(Event::ColorSchemeChanged { dark }, &window);
150 }
151 if !self.handler.on_resume(&window) {
152 event_loop.exit();
153 return;
154 }
155 self.a11y = Some(accesskit_winit::Adapter::with_event_loop_proxy(
158 event_loop,
159 &window.0,
160 self.a11y_proxy.clone(),
161 ));
162 window.0.set_visible(true);
163 window.request_redraw();
164 self.window = Some(window);
165 }
166
167 fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
168 let Some(window) = self.window.clone() else {
171 return;
172 };
173 if let Some(adapter) = &mut self.a11y {
176 adapter.process_event(&window.0, &event);
177 }
178 let redrawn = matches!(event, WindowEvent::RedrawRequested);
179 let outcome = dispatch_window_event(
180 &mut self.handler,
181 &window,
182 &mut self.cursor_position,
183 &mut self.scale_factor,
184 &mut self.modifiers,
185 event,
186 );
187 if redrawn {
189 self.publish_accessibility();
190 }
191 if matches!(outcome, WindowEventOutcome::CloseRequested) || self.handler.take_exit_request()
194 {
195 event_loop.exit();
196 }
197 }
198}
199
200enum WindowEventOutcome {
201 Continue,
202 CloseRequested,
203}
204
205fn create_window_from_config(
207 event_loop: &ActiveEventLoop,
208 config: &WindowConfig,
209) -> Option<WinitWindow> {
210 let mut attributes = WindowAttributes::default()
211 .with_visible(false)
214 .with_title(config.title.as_str())
215 .with_inner_size(winit::dpi::LogicalSize::new(config.width, config.height))
216 .with_resizable(config.is_resizable)
217 .with_decorations(config.has_decorations)
218 .with_transparent(config.is_transparent);
219
220 if let Some((w, h)) = config.min_size {
221 attributes = attributes.with_min_inner_size(winit::dpi::LogicalSize::new(w, h));
222 }
223 if let Some((w, h)) = config.max_size {
224 attributes = attributes.with_max_inner_size(winit::dpi::LogicalSize::new(w, h));
225 }
226 match config.fullscreen {
227 FullscreenMode::Disabled => {}
228 FullscreenMode::Borderless | FullscreenMode::Exclusive => {
230 attributes = attributes.with_fullscreen(Some(Fullscreen::Borderless(None)));
231 }
232 }
233 if let WindowPosition::At(x, y) = config.position {
234 attributes = attributes.with_position(winit::dpi::PhysicalPosition::new(x, y));
235 }
236 if config.is_always_on_top {
237 attributes = attributes.with_window_level(WindowLevel::AlwaysOnTop);
238 }
239
240 match event_loop.create_window(attributes) {
241 Ok(w) => Some(WinitWindow(std::sync::Arc::new(w))),
242 Err(e) => {
243 tracing::error!(error = %e, "failed to create window");
244 None
245 }
246 }
247}
248
249fn dispatch_window_event<H: EventHandler<WinitWindow>>(
252 handler: &mut H,
253 window: &WinitWindow,
254 cursor_position: &mut (f64, f64),
255 scale_factor: &mut f64,
256 modifiers: &mut platform_core::ModifiersState,
257 event: WindowEvent,
258) -> WindowEventOutcome {
259 match map_window_event(event, cursor_position, scale_factor, modifiers) {
260 SurfaceIntent::Event(e) => handler.on_event(e, window),
261 SurfaceIntent::Resized(e) => {
262 handler.on_event(e, window);
263 window.request_redraw();
264 }
265 SurfaceIntent::Redraw => handler.on_redraw(window),
266 SurfaceIntent::Close(e) => {
267 handler.on_event(e, window);
268 return WindowEventOutcome::CloseRequested;
269 }
270 SurfaceIntent::Ignore => {}
271 }
272 WindowEventOutcome::Continue
273}
274
275impl Platform for WinitPlatform {
276 type Window = WinitWindow;
277
278 fn run<H: EventHandler<Self::Window>>(
279 self,
280 config: WindowConfig,
281 handler: H,
282 ) -> Result<(), PlatformError> {
283 let mut runner = WinitRunner {
284 handler,
285 window: None,
286 config,
287 cursor_position: (0.0, 0.0),
288 scale_factor: 1.0,
289 modifiers: platform_core::ModifiersState::default(),
290 timer_has_fired: false,
291 a11y: None,
292 a11y_proxy: self.event_loop.create_proxy(),
293 a11y_nodes: Vec::new(),
294 };
295 let wake_proxy = self.event_loop.create_proxy();
298 platform_core::set_loop_waker(std::sync::Arc::new(move || {
299 let _ = wake_proxy.send_event(UserEvent::Wake);
300 }));
301 #[cfg(target_os = "linux")]
304 {
305 let proxy = self.event_loop.create_proxy();
306 crate::color_scheme::spawn_watch(move |dark| {
307 let _ = proxy.send_event(UserEvent::ColorScheme(dark));
308 });
309 }
310 self.event_loop
311 .run_app(&mut runner)
312 .map_err(|e| PlatformError(e.to_string()))
313 }
314}
315
316fn initial_prefers_dark(window: &WinitWindow) -> Option<bool> {
319 let winit = window.prefers_dark();
320 #[cfg(target_os = "linux")]
321 {
322 winit.or_else(crate::color_scheme::portal_prefers_dark)
323 }
324 #[cfg(not(target_os = "linux"))]
325 {
326 winit
327 }
328}
329
330struct DynamicRequest {
347 config: WindowConfig,
348 handler: Box<dyn EventHandler<WinitWindow>>,
349 close: Arc<std::sync::atomic::AtomicBool>,
350}
351
352thread_local! {
353 static DYNAMIC_QUEUE: std::cell::RefCell<Vec<DynamicRequest>> =
354 const { std::cell::RefCell::new(Vec::new()) };
355}
356
357pub fn request_dynamic_surface(
362 config: WindowConfig,
363 handler: Box<dyn EventHandler<WinitWindow>>,
364) -> Arc<std::sync::atomic::AtomicBool> {
365 let close = Arc::new(std::sync::atomic::AtomicBool::new(false));
366 DYNAMIC_QUEUE.with(|q| {
367 q.borrow_mut().push(DynamicRequest {
368 config,
369 handler,
370 close: Arc::clone(&close),
371 })
372 });
373 close
374}
375
376fn drain_dynamic_requests() -> Vec<DynamicRequest> {
377 DYNAMIC_QUEUE.with(|q| std::mem::take(&mut *q.borrow_mut()))
378}
379
380struct SurfaceRunner {
384 handler: Box<dyn EventHandler<WinitWindow>>,
385 window: WinitWindow,
386 cursor_position: (f64, f64),
387 scale_factor: f64,
388 modifiers: platform_core::ModifiersState,
389 pace: Option<std::time::Duration>,
390 close_flag: Option<Arc<std::sync::atomic::AtomicBool>>,
392 resumed: bool,
396 a11y: Option<accesskit_winit::Adapter>,
398 a11y_nodes: Vec<platform_core::AccessNode>,
399 title: String,
400}
401
402impl SurfaceRunner {
403 fn publish_accessibility(&mut self) {
407 let Some(adapter) = &mut self.a11y else {
408 return;
409 };
410 let nodes = self.handler.accessibility();
411 let title = self.title.clone();
412 adapter.update_if_active(|| crate::accessibility::tree_update(&nodes, &title));
413 self.a11y_nodes = nodes;
414 }
415}
416
417fn resume_surface(surface: &mut SurfaceRunner) -> bool {
421 let window = surface.window.clone();
422 surface.handler.new_events();
423 let built = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
424 if let Some(dark) = initial_prefers_dark(&window) {
425 surface
426 .handler
427 .on_event(Event::ColorSchemeChanged { dark }, &window);
428 }
429 surface.handler.on_resume(&window)
430 }));
431 surface.pace = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
432 surface.handler.about_to_wait()
433 }))
434 .unwrap_or(None);
435 matches!(built, Ok(true))
436}
437
438type BoxedFactory = Box<dyn Fn(SurfaceId) -> Box<dyn EventHandler<WinitWindow>>>;
441
442struct WinitMultiRunner {
443 factory: BoxedFactory,
444 pending: Vec<(SurfaceId, WindowConfig)>,
445 surfaces: HashMap<WindowId, SurfaceRunner>,
446 created: bool,
447 a11y_proxy: winit::event_loop::EventLoopProxy<UserEvent>,
448 timer_has_fired: bool,
450}
451
452impl WinitMultiRunner {
453 fn spawn_surface(
458 &mut self,
459 event_loop: &ActiveEventLoop,
460 config: WindowConfig,
461 handler: Box<dyn EventHandler<WinitWindow>>,
462 close_flag: Option<Arc<std::sync::atomic::AtomicBool>>,
463 resume_now: bool,
464 ) {
465 let Some(window) = ({
469 let _gpu = renderer_core::gpu_sync::lifecycle_guard();
470 create_window_from_config(event_loop, &config)
471 }) else {
472 return;
473 };
474 let window_id = window.0.id();
475 let a11y = accesskit_winit::Adapter::with_event_loop_proxy(
477 event_loop,
478 &window.0,
479 self.a11y_proxy.clone(),
480 );
481 let mut surface = SurfaceRunner {
482 handler,
483 window,
484 cursor_position: (0.0, 0.0),
485 scale_factor: 1.0,
486 modifiers: platform_core::ModifiersState::default(),
487 pace: None,
488 close_flag,
489 resumed: false,
490 a11y: Some(a11y),
491 a11y_nodes: Vec::new(),
492 title: config.title.clone(),
493 };
494 if resume_now {
495 if !resume_surface(&mut surface) {
496 tracing::error!("surface on_resume failed or panicked; skipping it");
497 return;
498 }
499 surface.window.request_redraw();
500 surface.resumed = true;
501 }
502 self.surfaces.insert(window_id, surface);
503 }
504}
505
506impl ApplicationHandler<UserEvent> for WinitMultiRunner {
507 fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
508 match event {
509 UserEvent::Accessibility(event) => {
511 use accesskit_winit::WindowEvent as AkEvent;
512 let Some(surface) = self.surfaces.get_mut(&event.window_id) else {
513 return;
514 };
515 match event.window_event {
516 AkEvent::InitialTreeRequested => surface.publish_accessibility(),
517 AkEvent::ActionRequested(request) => {
518 let Some((id, activate)) =
519 crate::accessibility::requested_focus_id(&request, &surface.a11y_nodes)
520 else {
521 return;
522 };
523 surface.handler.on_accessibility_action(id, activate);
524 surface.publish_accessibility();
525 }
526 AkEvent::AccessibilityDeactivated => surface.a11y_nodes.clear(),
527 }
528 }
529 #[cfg(target_os = "linux")]
530 UserEvent::ColorScheme(dark) => {
531 for surface in self.surfaces.values_mut() {
533 surface.handler.new_events();
534 surface
535 .handler
536 .on_event(Event::ColorSchemeChanged { dark }, &surface.window);
537 surface.pace = surface.handler.about_to_wait();
538 }
539 }
540 UserEvent::Wake => {
541 for surface in self.surfaces.values() {
544 surface.window.request_redraw();
545 }
546 }
547 }
548 }
549
550 fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
551 self.timer_has_fired = matches!(cause, StartCause::ResumeTimeReached { .. });
553 }
554
555 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
556 for req in drain_dynamic_requests() {
559 self.spawn_surface(event_loop, req.config, req.handler, Some(req.close), false);
560 }
561 let to_close: Vec<WindowId> = self
562 .surfaces
563 .iter()
564 .filter(|(_, s)| {
565 s.close_flag
566 .as_ref()
567 .is_some_and(|f| f.load(std::sync::atomic::Ordering::Relaxed))
568 })
569 .map(|(&id, _)| id)
570 .collect();
571 for id in to_close {
572 if let Some(mut removed) = self.surfaces.remove(&id) {
573 removed.handler.on_suspend();
574 let _gpu = renderer_core::gpu_sync::lifecycle_guard();
577 drop(removed);
578 }
579 }
580 if self.created && self.surfaces.is_empty() {
581 event_loop.exit();
582 return;
583 }
584
585 let mut next_wake: Option<std::time::Duration> = None;
588 for surface in self.surfaces.values() {
589 if let Some(d) = surface.pace {
590 if self.timer_has_fired {
591 surface.window.request_redraw();
592 }
593 next_wake = Some(next_wake.map_or(d, |cur| cur.min(d)));
594 }
595 }
596 match next_wake {
597 Some(d) => {
598 event_loop.set_control_flow(ControlFlow::WaitUntil(std::time::Instant::now() + d))
599 }
600 None => event_loop.set_control_flow(ControlFlow::Wait),
601 }
602 }
603
604 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
605 if self.created {
608 return;
609 }
610 self.created = true;
611 for (id, config) in std::mem::take(&mut self.pending) {
612 let handler = (self.factory)(id);
614 self.spawn_surface(event_loop, config, handler, None, true);
615 }
616 if self.surfaces.is_empty() {
617 event_loop.exit();
618 }
619 }
620
621 fn window_event(&mut self, _event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
622 let Some(surface) = self.surfaces.get_mut(&id) else {
623 return;
624 };
625 if !surface.resumed {
626 let configured =
631 matches!(&event, WindowEvent::Resized(s) if s.width > 0 && s.height > 0);
632 if !configured {
633 return;
634 }
635 if resume_surface(surface) {
636 surface.resumed = true;
637 } else {
640 if let Some(removed) = self.surfaces.remove(&id) {
643 let _gpu = renderer_core::gpu_sync::lifecycle_guard();
644 drop(removed);
645 }
646 return;
647 }
648 }
649 let window = surface.window.clone();
651 let redrawn = matches!(event, WindowEvent::RedrawRequested);
652 if let Some(adapter) = &mut surface.a11y {
654 adapter.process_event(&window.0, &event);
655 }
656 surface.handler.new_events();
657 let dispatched = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
662 dispatch_window_event(
663 &mut surface.handler,
664 &window,
665 &mut surface.cursor_position,
666 &mut surface.scale_factor,
667 &mut surface.modifiers,
668 event,
669 )
670 }));
671 let paced = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
672 surface.handler.about_to_wait()
673 }));
674 surface.pace = paced.as_ref().copied().unwrap_or(None);
675 let panicked = dispatched.is_err() || paced.is_err();
676 if redrawn && !panicked {
678 surface.publish_accessibility();
679 }
680 let close = matches!(dispatched, Ok(WindowEventOutcome::CloseRequested));
681 let exit_requested = !panicked && surface.handler.take_exit_request();
683 if panicked {
684 tracing::error!(?id, "surface panicked; unmounting it");
685 }
686 if panicked || close || exit_requested {
688 if let Some(mut removed) = self.surfaces.remove(&id) {
689 if !panicked {
692 removed.handler.on_suspend();
693 }
694 let _gpu = renderer_core::gpu_sync::lifecycle_guard();
699 drop(removed);
700 }
701 tracing::debug!(
702 ?id,
703 close,
704 exit_requested,
705 panicked,
706 remaining = self.surfaces.len(),
707 "surface closed"
708 );
709 }
713 }
714}
715
716impl MultiSurfacePlatform for WinitPlatform {
717 type Window = WinitWindow;
718
719 fn run_surfaces<H, F>(
720 self,
721 surfaces: Vec<(SurfaceId, WindowConfig)>,
722 factory: F,
723 ) -> Result<(), PlatformError>
724 where
725 H: EventHandler<WinitWindow> + 'static,
726 F: Fn(SurfaceId) -> H + 'static,
727 {
728 let factory: BoxedFactory =
730 Box::new(move |id| Box::new(factory(id)) as Box<dyn EventHandler<WinitWindow>>);
731 let mut runner = WinitMultiRunner {
732 factory,
733 pending: surfaces,
734 surfaces: HashMap::new(),
735 created: false,
736 a11y_proxy: self.event_loop.create_proxy(),
737 timer_has_fired: false,
738 };
739 let wake_proxy = self.event_loop.create_proxy();
743 platform_core::set_loop_waker(std::sync::Arc::new(move || {
744 let _ = wake_proxy.send_event(UserEvent::Wake);
745 }));
746 #[cfg(target_os = "linux")]
748 {
749 let proxy = self.event_loop.create_proxy();
750 crate::color_scheme::spawn_watch(move |dark| {
751 let _ = proxy.send_event(UserEvent::ColorScheme(dark));
752 });
753 }
754 self.event_loop
755 .run_app(&mut runner)
756 .map_err(|e| PlatformError(e.to_string()))
757 }
758}