Skip to main content

windows_reactor/
app.rs

1use std::cell::RefCell;
2use std::collections::{HashMap, HashSet};
3use std::rc::Rc;
4
5use super::*;
6use crate::core::*;
7use crate::native::*;
8
9const E_INVALIDARG: windows_core::HRESULT = windows_core::HRESULT(0x80070057_u32 as _);
10const MAX_PENDING_WINDOW_OPENS: usize = 64;
11
12#[cfg(feature = "test")]
13pub(crate) mod test;
14
15thread_local! {
16    static HOST: RefCell<Option<LiveHost>> = const { RefCell::new(None) };
17    static SCHEDULER_FAULT: RefCell<Option<windows_core::Error>> = const { RefCell::new(None) };
18}
19
20#[cfg(feature = "test")]
21thread_local! {
22    static LIVE_DISPATCH_TIMES_US: RefCell<Vec<f64>> = const { RefCell::new(Vec::new()) };
23}
24
25struct LiveHost {
26    _application: Application,
27    closed_in_flight: HashSet<WindowToken>,
28    fault: Option<windows_core::Error>,
29    in_flight: HashSet<WindowToken>,
30    pending_opens: usize,
31    #[cfg(feature = "test")]
32    primary: WindowToken,
33    windows: HashMap<WindowToken, Box<dyn LivePump>>,
34}
35
36impl LiveHost {
37    fn is_empty(&self) -> bool {
38        self.windows.is_empty() && self.in_flight.is_empty() && self.pending_opens == 0
39    }
40
41    #[cfg(feature = "test")]
42    fn primary(&self) -> Option<&dyn LivePump> {
43        self.windows.get(&self.primary).map(Box::as_ref)
44    }
45
46    #[cfg(feature = "test")]
47    fn primary_mut(&mut self) -> Option<&mut (dyn LivePump + '_)> {
48        match self.windows.get_mut(&self.primary) {
49            Some(pump) => Some(pump.as_mut()),
50            None => None,
51        }
52    }
53
54    #[cfg(feature = "test")]
55    fn secondary(&self) -> Option<&dyn LivePump> {
56        self.windows
57            .iter()
58            .find(|(token, _)| **token != self.primary)
59            .map(|(_, pump)| pump.as_ref())
60    }
61
62    #[cfg(feature = "test")]
63    fn secondary_mut(&mut self) -> Option<&mut (dyn LivePump + '_)> {
64        for (token, pump) in &mut self.windows {
65            if *token != self.primary {
66                return Some(pump.as_mut());
67            }
68        }
69        None
70    }
71}
72
73trait LivePump {
74    fn mount(&mut self) -> Result<(), PumpError>;
75    fn dispatch_events(&mut self) -> Result<(), PumpError>;
76    fn drain_diagnostics(&mut self) -> Vec<PumpDiagnostic>;
77    fn native_work_pending(&self) -> bool;
78    fn schedule_dispatch(&self) -> Result<(), RuntimeError>;
79    fn close_scheduler(&self);
80    fn native_window_closed(&mut self);
81    fn shutdown(&mut self);
82    fn window_token(&self) -> WindowToken;
83    #[cfg(feature = "test")]
84    fn live_window(&self) -> Result<Window, RuntimeError> {
85        Err(RuntimeError::UnsupportedKind)
86    }
87    #[cfg(feature = "test")]
88    fn live_bring_virtual_index(&self, _index: usize) -> Result<(), RuntimeError> {
89        Err(RuntimeError::UnsupportedKind)
90    }
91    #[cfg(feature = "test")]
92    fn live_virtual_shell_counts(&self) -> Result<(usize, usize), RuntimeError> {
93        Err(RuntimeError::UnsupportedKind)
94    }
95    #[cfg(feature = "test")]
96    fn live_event_subscription_count(&self) -> Result<usize, RuntimeError> {
97        Err(RuntimeError::UnsupportedKind)
98    }
99    #[cfg(feature = "test")]
100    fn take_live_native_apply_times(&mut self) -> Vec<f64> {
101        Vec::new()
102    }
103    #[cfg(feature = "test")]
104    fn clear_live_native_apply_times(&mut self) {}
105    #[cfg(feature = "test")]
106    fn live_event_revokers(&mut self) -> bool {
107        false
108    }
109    #[cfg(feature = "test")]
110    fn live_event_delivery_step(&mut self) -> Result<bool, String> {
111        Err("live event delivery is unsupported".to_string())
112    }
113    #[cfg(feature = "test")]
114    fn live_content_dialog_lifecycle_step(&mut self) -> Result<bool, String> {
115        Err("live ContentDialog lifecycle is unsupported".to_string())
116    }
117    #[cfg(feature = "test")]
118    fn live_controlled_feedback_start(&mut self) -> bool {
119        false
120    }
121    #[cfg(feature = "test")]
122    fn live_controlled_feedback_input(&mut self) -> bool {
123        false
124    }
125    #[cfg(feature = "test")]
126    fn live_controlled_feedback_finish(&mut self) -> bool {
127        false
128    }
129}
130
131struct ComponentLoop {
132    pump: Pump<WinUiRuntime>,
133    root: Option<View>,
134    #[cfg(feature = "test")]
135    test: test::LiveTestState,
136}
137
138#[cfg(feature = "test")]
139impl ComponentLoop {
140    fn begin_live_event_stage(
141        &mut self,
142        view: impl Into<View>,
143        name: &str,
144        observed: Rc<std::cell::Cell<bool>>,
145        apply: impl FnOnce(&WinUiRuntime, NodeId) -> Result<(), RuntimeError>,
146    ) -> Result<(), String> {
147        self.pump
148            .update_view(view.into())
149            .map_err(|error| format!("{name} event target update failed: {error:?}"))?;
150        let node = self
151            .pump
152            .root_native()
153            .ok_or_else(|| format!("{name} event target is unavailable"))?;
154        apply(self.pump.runtime(), node)
155            .map_err(|error| format!("{name} native input failed: {error:?}"))?;
156        self.test.event_delivery_observed = Some(observed);
157        self.test.event_delivery_waits = 0;
158        Ok(())
159    }
160
161    fn live_event_delivery_step_impl(&mut self) -> Result<bool, String> {
162        if let Some(observed) = self.test.event_delivery_observed.take() {
163            self.pump
164                .dispatch_events()
165                .map_err(|error| format!("event dispatch failed: {error:?}"))?;
166            if !observed.get() {
167                self.test.event_delivery_waits += 1;
168                if self.test.event_delivery_waits == 100 {
169                    return Err(format!(
170                        "event delivery stage {} produced no matching payload",
171                        self.test.event_delivery_stage
172                    ));
173                }
174                self.test.event_delivery_observed = Some(observed);
175                return Ok(false);
176            }
177            self.test.event_delivery_stage += 1;
178        }
179
180        let observed = Rc::new(std::cell::Cell::new(false));
181        let callback = Rc::clone(&observed);
182        match self.test.event_delivery_stage {
183            0 => self.begin_live_event_stage(
184                ToggleSwitch::new()
185                    .is_on(false)
186                    .on_toggled(move |value| callback.set(value)),
187                "bool",
188                observed,
189                |runtime, node| {
190                    runtime.live_write_test_property(
191                        node,
192                        PropertyId::ToggleSwitchIsOn,
193                        &PropertyValue::Bool(true),
194                    )
195                },
196            )?,
197            1 => self.begin_live_event_stage(
198                PasswordBox::new()
199                    .password("initial")
200                    .on_password_changed(move |value| callback.set(value == "native")),
201                "string",
202                observed,
203                |runtime, node| {
204                    runtime.live_write_test_property(
205                        node,
206                        PropertyId::PasswordBoxPassword,
207                        &PropertyValue::Str("native".to_string()),
208                    )
209                },
210            )?,
211            2 => self.begin_live_event_stage(
212                Slider::new()
213                    .minimum(0.0)
214                    .maximum(10.0)
215                    .value(1.0)
216                    .on_value_changed(move |value| callback.set(value == 4.5)),
217                "f64",
218                observed,
219                |runtime, node| {
220                    runtime.live_write_test_property(
221                        node,
222                        PropertyId::SliderValue,
223                        &PropertyValue::F64(4.5),
224                    )
225                },
226            )?,
227            3 => self.begin_live_event_stage(
228                NumberBox::new()
229                    .minimum(0.0)
230                    .maximum(10.0)
231                    .value(1.0)
232                    .on_value_changed(move |value| callback.set(value == Some(4.5))),
233                "NumberBox optional f64",
234                observed,
235                |runtime, node| {
236                    runtime.live_write_test_property(
237                        node,
238                        PropertyId::NumberBoxValue,
239                        &PropertyValue::OptionalF64(Some(4.5)),
240                    )
241                },
242            )?,
243            4 => {
244                let color = Color {
245                    a: 255,
246                    r: 12,
247                    g: 34,
248                    b: 56,
249                };
250                self.begin_live_event_stage(
251                    ColorPicker::new()
252                        .color(Color::default())
253                        .on_color_changed(move |value| callback.set(value == color)),
254                    "color",
255                    observed,
256                    move |runtime, node| {
257                        runtime.live_write_test_property(
258                            node,
259                            PropertyId::ColorPickerColor,
260                            &PropertyValue::Color(color),
261                        )
262                    },
263                )?;
264            }
265            5 => self.begin_live_event_stage(
266                ListView::new()
267                    .selected_index(None)
268                    .on_selection_changed(move |value| callback.set(value == Some(1)))
269                    .collection_slot(
270                        ListViewSlot::Items,
271                        [
272                            KeyedView::new(
273                                "first",
274                                ListViewItem::new()
275                                    .tag("first")
276                                    .content(TextBlock::new().text("First")),
277                            ),
278                            KeyedView::new(
279                                "second",
280                                ListViewItem::new()
281                                    .tag("second")
282                                    .content(TextBlock::new().text("Second")),
283                            ),
284                        ],
285                    ),
286                "selection index",
287                observed,
288                |runtime, node| {
289                    runtime.live_write_test_property(
290                        node,
291                        PropertyId::ListViewSelectedIndex,
292                        &PropertyValue::SelectionIndex(Some(1)),
293                    )
294                },
295            )?,
296            6 => {
297                let date = DateTime::from_unix_secs(1_700_000_000);
298                self.begin_live_event_stage(
299                    CalendarDatePicker::new()
300                        .on_date_changed(move |value| callback.set(value == Some(date))),
301                    "optional date",
302                    observed,
303                    move |runtime, node| runtime.live_set_test_date(node, date),
304                )?;
305            }
306            7 => {
307                let time = TimeSpan::from_hours(14) + TimeSpan::from_minutes(30);
308                self.begin_live_event_stage(
309                    TimePicker::new()
310                        .on_selected_time_changed(move |value| callback.set(value == Some(time))),
311                    "optional time",
312                    observed,
313                    move |runtime, node| runtime.live_set_test_time(node, time),
314                )?;
315            }
316            8 => return Ok(true),
317            _ => return Err("event delivery stage is invalid".to_string()),
318        }
319        Ok(false)
320    }
321
322    fn live_content_dialog_lifecycle_step_impl(&mut self) -> Result<bool, String> {
323        let view = |first_open, second_open| {
324            StackPanel::new().keyed_children([
325                KeyedView::new(
326                    "first",
327                    ContentDialog::new().title("First").is_open(first_open),
328                ),
329                KeyedView::new(
330                    "second",
331                    ContentDialog::new().title("Second").is_open(second_open),
332                ),
333            ])
334        };
335        let mut wait = |states: &[LiveContentDialogState]| {
336            self.test.content_dialog_waits += 1;
337            if self.test.content_dialog_waits == 250 {
338                Err(format!(
339                    "ContentDialog probe stalled at stage {}: {states:?}",
340                    self.test.content_dialog_stage
341                ))
342            } else {
343                Ok(false)
344            }
345        };
346        match self.test.content_dialog_stage {
347            0 => {
348                self.pump
349                    .update_view(view(true, false))
350                    .map_err(|error| format!("ContentDialog mount failed: {error:?}"))?;
351                self.test.content_dialog_stage = 1;
352                self.test.content_dialog_waits = 0;
353            }
354            1 => {
355                let states = self.pump.runtime().live_content_dialog_states();
356                let [first, second] = states.as_slice() else {
357                    return Err("ContentDialog probe expected two dialogs".to_string());
358                };
359                if !first.pending || second.pending {
360                    return wait(&states);
361                }
362                self.pump
363                    .update_view(view(true, true))
364                    .and_then(|_| self.pump.update_view(view(false, true)))
365                    .and_then(|_| self.pump.update_view(view(true, true)))
366                    .map_err(|error| format!("ContentDialog queue setup failed: {error:?}"))?;
367                self.test.content_dialog_stage = 2;
368                self.test.content_dialog_waits = 0;
369            }
370            2 => {
371                let states = self.pump.runtime().live_content_dialog_states();
372                let [first, second] = states.as_slice() else {
373                    return Err("ContentDialog probe lost a dialog".to_string());
374                };
375                if first.pending || !first.queued || !second.pending {
376                    return wait(&states);
377                }
378                self.pump
379                    .runtime()
380                    .live_hide_content_dialog(second.node)
381                    .map_err(|error| format!("second ContentDialog hide failed: {error:?}"))?;
382                self.test.content_dialog_stage = 3;
383                self.test.content_dialog_waits = 0;
384            }
385            3 => {
386                let states = self.pump.runtime().live_content_dialog_states();
387                let [first, second] = states.as_slice() else {
388                    return Err("ContentDialog probe lost a dialog".to_string());
389                };
390                if first.pending && !first.queued && !second.pending {
391                    self.pump
392                        .update_view(view(false, false))
393                        .map_err(|error| format!("ContentDialog cleanup failed: {error:?}"))?;
394                    self.test.content_dialog_stage = 4;
395                    self.test.content_dialog_waits = 0;
396                    return Ok(false);
397                }
398                return wait(&states);
399            }
400            4 => {
401                let states = self.pump.runtime().live_content_dialog_states();
402                let [first, second] = states.as_slice() else {
403                    return Err("ContentDialog probe lost a dialog".to_string());
404                };
405                if !first.desired_open
406                    && !first.pending
407                    && !first.queued
408                    && !second.desired_open
409                    && !second.pending
410                    && !second.queued
411                {
412                    return Ok(true);
413                }
414                return wait(&states);
415            }
416            _ => return Err("ContentDialog probe stage is invalid".to_string()),
417        }
418        Ok(false)
419    }
420}
421
422impl LivePump for ComponentLoop {
423    fn mount(&mut self) -> Result<(), PumpError> {
424        self.pump
425            .mount_view(self.root.take().ok_or(PumpError::AlreadyMounted)?)
426            .map(|_| ())
427    }
428
429    fn dispatch_events(&mut self) -> Result<(), PumpError> {
430        self.pump.dispatch_events()?;
431        self.pump.dispatch_components(64)?;
432        self.pump.process_imperatives().map(|_| ())
433    }
434
435    fn drain_diagnostics(&mut self) -> Vec<PumpDiagnostic> {
436        self.pump.drain_diagnostics()
437    }
438
439    fn native_work_pending(&self) -> bool {
440        self.pump.native_work_pending()
441    }
442
443    fn schedule_dispatch(&self) -> Result<(), RuntimeError> {
444        self.pump.runtime().schedule_dispatch()
445    }
446
447    fn close_scheduler(&self) {
448        self.pump.runtime().close_scheduler();
449    }
450
451    fn native_window_closed(&mut self) {
452        self.pump.native_window_closed();
453    }
454
455    fn shutdown(&mut self) {
456        self.pump.shutdown();
457        self.pump.runtime().close_scheduler();
458    }
459
460    fn window_token(&self) -> WindowToken {
461        self.pump.window_token()
462    }
463
464    #[cfg(feature = "test")]
465    fn live_window(&self) -> Result<Window, RuntimeError> {
466        self.pump.runtime().live_window()
467    }
468
469    #[cfg(feature = "test")]
470    fn live_bring_virtual_index(&self, index: usize) -> Result<(), RuntimeError> {
471        self.pump.runtime().live_bring_virtual_index(index)
472    }
473
474    #[cfg(feature = "test")]
475    fn live_virtual_shell_counts(&self) -> Result<(usize, usize), RuntimeError> {
476        self.pump.runtime().live_virtual_shell_counts()
477    }
478
479    #[cfg(feature = "test")]
480    fn live_event_subscription_count(&self) -> Result<usize, RuntimeError> {
481        Ok(self.pump.runtime().live_event_subscription_count())
482    }
483
484    #[cfg(feature = "test")]
485    fn take_live_native_apply_times(&mut self) -> Vec<f64> {
486        self.pump.runtime_mut().take_live_native_apply_times()
487    }
488
489    #[cfg(feature = "test")]
490    fn clear_live_native_apply_times(&mut self) {
491        self.pump.runtime_mut().clear_live_native_apply_times();
492    }
493
494    #[cfg(feature = "test")]
495    fn live_event_revokers(&mut self) -> bool {
496        let first = Rc::new(std::cell::Cell::new(0_u8));
497        let second = Rc::new(std::cell::Cell::new(0_u8));
498        let first_callback = Rc::clone(&first);
499        if self
500            .pump
501            .update_view(
502                CheckBox::new()
503                    .is_checked(false)
504                    .on_is_checked_changed(move |_| first_callback.set(first_callback.get() + 1))
505                    .content(TextBlock::new().text("event target")),
506            )
507            .is_err()
508        {
509            return false;
510        }
511        let Some(node) = self.pump.root_native() else {
512            return false;
513        };
514        if !matches!(self.pump.live_native_children(node), [_])
515            || self.pump.runtime().live_set_checked(node, true).is_err()
516            || self.pump.dispatch_events() != Ok(1)
517            || first.get() != 1
518        {
519            return false;
520        }
521
522        let second_callback = Rc::clone(&second);
523        if self
524            .pump
525            .update_view(
526                CheckBox::new()
527                    .is_checked(true)
528                    .on_is_checked_changed(move |_| second_callback.set(second_callback.get() + 1))
529                    .content(TextBlock::new().text("event target")),
530            )
531            .is_err()
532            || self.pump.runtime().live_set_checked(node, false).is_err()
533            || self.pump.dispatch_events() != Ok(1)
534            || first.get() != 1
535            || second.get() != 1
536        {
537            return false;
538        }
539
540        self.pump
541            .update_view(
542                CheckBox::new()
543                    .is_checked(false)
544                    .content(TextBlock::new().text("event target")),
545            )
546            .is_ok()
547            && self.pump.runtime().live_set_checked(node, true).is_ok()
548            && self.pump.dispatch_events() == Ok(0)
549            && first.get() == 1
550            && second.get() == 1
551    }
552
553    #[cfg(feature = "test")]
554    fn live_event_delivery_step(&mut self) -> Result<bool, String> {
555        self.live_event_delivery_step_impl()
556    }
557
558    #[cfg(feature = "test")]
559    fn live_content_dialog_lifecycle_step(&mut self) -> Result<bool, String> {
560        self.live_content_dialog_lifecycle_step_impl()
561    }
562
563    #[cfg(feature = "test")]
564    fn live_controlled_feedback_start(&mut self) -> bool {
565        true
566    }
567
568    #[cfg(feature = "test")]
569    fn live_controlled_feedback_input(&mut self) -> bool {
570        true
571    }
572
573    #[cfg(feature = "test")]
574    fn live_controlled_feedback_finish(&mut self) -> bool {
575        let text_events = Rc::new(std::cell::Cell::new(0_u8));
576        let callback = Rc::clone(&text_events);
577        let text_view = |value| {
578            let callback = Rc::clone(&callback);
579            TextBox::new()
580                .text(value)
581                .on_text_changed(move |_| callback.set(callback.get() + 1))
582        };
583        if self.pump.update_view(text_view("first").into()).is_err()
584            || self.pump.update_view(text_view("second").into()).is_err()
585            || text_events.get() != 0
586        {
587            eprintln!("controlled TextBox setter echoed to the application");
588            return false;
589        }
590
591        let number_events = Rc::new(std::cell::Cell::new(0_u8));
592        let number_view = |maximum| {
593            let events = Rc::clone(&number_events);
594            NumberBox::new()
595                .minimum(0.0)
596                .maximum(maximum)
597                .value(7.0)
598                .on_value_changed(move |_| events.set(events.get() + 1))
599        };
600        if self.pump.update_view(number_view(10.0).into()).is_err()
601            || self.pump.update_view(number_view(5.0).into()).is_err()
602            || number_events.get() != 0
603            || self
604                .pump
605                .root_native()
606                .is_none_or(|node| self.pump.runtime().live_range_value(node) != Ok(5.0))
607        {
608            eprintln!("controlled NumberBox feedback failed");
609            return false;
610        }
611
612        let slider_events = Rc::new(std::cell::Cell::new(0_u8));
613        let slider_view = |maximum| {
614            let events = Rc::clone(&slider_events);
615            Slider::new()
616                .minimum(0.0)
617                .maximum(maximum)
618                .value(7.0)
619                .on_value_changed(move |_| events.set(events.get() + 1))
620        };
621        if self.pump.update_view(slider_view(10.0).into()).is_err()
622            || self.pump.update_view(slider_view(5.0).into()).is_err()
623            || slider_events.get() != 0
624            || self
625                .pump
626                .root_native()
627                .is_none_or(|node| self.pump.runtime().live_range_value(node) != Ok(5.0))
628        {
629            eprintln!("controlled Slider feedback failed");
630            return false;
631        }
632
633        let toggle_events = Rc::new(std::cell::Cell::new(0_u8));
634        let check_box = |checked| {
635            let events = Rc::clone(&toggle_events);
636            CheckBox::new()
637                .is_checked(checked)
638                .on_is_checked_changed(move |_| events.set(events.get() + 1))
639                .content(TextBlock::new().text("Check"))
640        };
641        if self.pump.update_view(check_box(false)).is_err()
642            || self.pump.update_view(check_box(true)).is_err()
643            || toggle_events.get() != 0
644        {
645            eprintln!("controlled CheckBox setter echoed to the application");
646            return false;
647        }
648        let Some(check_box) = self.pump.root_native() else {
649            return false;
650        };
651        if self
652            .pump
653            .runtime()
654            .live_set_checked(check_box, false)
655            .is_err()
656            || self.pump.dispatch_events() != Ok(1)
657            || toggle_events.get() != 1
658            || self.pump.runtime().live_checked_value(check_box) != Ok(false)
659        {
660            eprintln!("CheckBox native feedback failed");
661            return false;
662        }
663
664        let selected = Rc::new(RefCell::new(None));
665        let selected_callback = Rc::clone(&selected);
666        let list = ListBox::new()
667            .on_selected_tag_changed(move |tag| *selected_callback.borrow_mut() = tag)
668            .slots([SlotView::collection(
669                ListBoxSlot::Items,
670                [
671                    KeyedView::new(
672                        "one",
673                        ListBoxItem::new()
674                            .tag("one")
675                            .is_selected(true)
676                            .content(TextBlock::new().text("One")),
677                    ),
678                    KeyedView::new(
679                        "two",
680                        ListBoxItem::new()
681                            .tag("two")
682                            .is_selected(false)
683                            .content(TextBlock::new().text("Two")),
684                    ),
685                ],
686            )]);
687        if self.pump.update_view(list).is_err() || self.pump.dispatch_events() != Ok(0) {
688            eprintln!("controlled selection feedback failed");
689            return false;
690        }
691        let Some(list_box) = self.pump.root_native() else {
692            return false;
693        };
694        if self
695            .pump
696            .runtime()
697            .live_select_list_box_item(list_box, 1)
698            .is_err()
699            || self.pump.dispatch_events() != Ok(1)
700            || selected.borrow().as_deref() != Some("two")
701        {
702            eprintln!("ListBox native feedback failed");
703            return false;
704        }
705
706        let progress = |value| {
707            ProgressBar::new()
708                .minimum(0.0)
709                .maximum(100.0)
710                .value(value)
711                .is_indeterminate(false)
712        };
713        if self.pump.update_view(progress(25.0).into()).is_err()
714            || self.pump.update_view(progress(75.0).into()).is_err()
715        {
716            eprintln!("controlled range update failed");
717            return false;
718        }
719        true
720    }
721}
722
723/// Starts and owns a Reactor application's WinUI message loop.
724///
725/// Each run method blocks until all application-owned windows have closed.
726pub struct App;
727
728impl App {
729    /// Runs one window whose root content is `root`.
730    ///
731    /// This call blocks while the WinUI message loop is running.
732    pub fn run(root: View) -> windows_core::Result<()> {
733        Self::run_with(move |application| {
734            vec![Box::new(ComponentLoop {
735                pump: Pump::new(WinUiRuntime::with_application(application)),
736                root: Some(root),
737                #[cfg(feature = "test")]
738                test: Default::default(),
739            })]
740        })
741    }
742
743    /// Runs one independent window and component pump for each root view.
744    ///
745    /// Unlike placing several views in a fragment, each item creates a separate native window.
746    /// This call blocks until every application-owned window has closed.
747    ///
748    /// Returns an invalid-argument error if `roots` is empty.
749    pub fn run_windows<I>(roots: I) -> windows_core::Result<()>
750    where
751        I: IntoIterator<Item = View>,
752    {
753        let roots = roots.into_iter().collect::<Vec<_>>();
754        if roots.is_empty() {
755            return Err(windows_core::Error::new(
756                E_INVALIDARG,
757                "at least one window is required",
758            ));
759        }
760        Self::run_with(move |application| {
761            roots
762                .into_iter()
763                .map(|root| {
764                    Box::new(ComponentLoop {
765                        pump: Pump::new(WinUiRuntime::with_application(application.clone())),
766                        root: Some(root),
767                        #[cfg(feature = "test")]
768                        test: Default::default(),
769                    }) as Box<dyn LivePump>
770                })
771                .collect()
772        })
773    }
774
775    /// Runs one window rooted at component `C`.
776    ///
777    /// This is equivalent to passing [`View::component`] to [`run`](Self::run), and blocks while
778    /// the WinUI message loop is running.
779    pub fn run_component<C: Component>(input: C::Input) -> windows_core::Result<()> {
780        Self::run(View::component::<C>(input))
781    }
782
783    fn run_with(
784        create_pumps: impl FnOnce(Application) -> Vec<Box<dyn LivePump>> + 'static,
785    ) -> windows_core::Result<()> {
786        if !is_packaged_process()? {
787            bootstrap_runtime()?;
788        }
789
790        initialize_ui_thread()?;
791        let create_pumps = Rc::new(RefCell::new(Some(create_pumps)));
792        let result = Rc::new(RefCell::new(Ok(())));
793        let callback_result = Rc::clone(&result);
794
795        let start = Application::Start(&ApplicationInitializationCallback::new(move |_| {
796            let application = Rc::new(RefCell::new(None));
797            let launch_application = Rc::clone(&application);
798            let launch_result = Rc::clone(&callback_result);
799            let launch_create_pumps = Rc::clone(&create_pumps);
800            let on_launched = Box::new(move || {
801                let launched: windows_core::Result<()> = (|| {
802                    let application = launch_application
803                        .borrow_mut()
804                        .take()
805                        .ok_or_else(|| windows_core::Error::new(E_FAIL, "missing application"))?;
806                    install_xaml_controls_resources(&application)?;
807                    let create_pumps = launch_create_pumps.borrow_mut().take().unwrap();
808                    let mut pumps = create_pumps(application.clone()).into_iter();
809                    let mut primary_pump = pumps.next().ok_or_else(|| {
810                        windows_core::Error::new(E_INVALIDARG, "at least one window is required")
811                    })?;
812                    let primary = primary_pump.window_token();
813                    let pumps = pumps.collect::<Vec<_>>();
814                    let mut in_flight = pumps
815                        .iter()
816                        .map(|pump| pump.window_token())
817                        .collect::<HashSet<_>>();
818                    assert!(in_flight.insert(primary));
819                    HOST.with(|host| {
820                        *host.borrow_mut() = Some(LiveHost {
821                            _application: application,
822                            closed_in_flight: HashSet::new(),
823                            fault: None,
824                            in_flight,
825                            pending_opens: pumps.len() + 1,
826                            #[cfg(feature = "test")]
827                            primary,
828                            windows: HashMap::with_capacity(pumps.len() + 1),
829                        });
830                    });
831                    primary_pump.mount().map_err(pump_error)?;
832                    publish_mounted_window(primary_pump);
833                    if !pumps.is_empty() {
834                        let dispatcher = DispatcherQueue::GetForCurrentThread()?;
835                        let pumps = Rc::new(RefCell::new(Some(pumps)));
836                        let mount = DispatcherQueueHandler::new(move || {
837                            let Some(pumps) = pumps.borrow_mut().take() else {
838                                return;
839                            };
840                            for mut pump in pumps {
841                                if let Err(error) = pump.mount() {
842                                    let error = pump_error(error);
843                                    eprintln!("windows-reactor additional window fault: {error}");
844                                    HOST.with(|host| {
845                                        if let Some(host) = host.borrow_mut().as_mut() {
846                                            host.fault = Some(error);
847                                        }
848                                    });
849                                    exit_ui_thread();
850                                    return;
851                                }
852                                publish_mounted_window(pump);
853                            }
854                        });
855                        if !dispatcher
856                            .TryEnqueueWithPriority(DispatcherQueuePriority::Normal, &mount)?
857                        {
858                            return Err(windows_core::Error::new(
859                                E_FAIL,
860                                "dispatcher rejected additional window mounting",
861                            ));
862                        }
863                    }
864                    Ok(())
865                })();
866                if let Err(error) = &launched {
867                    *launch_result.borrow_mut() = Err(error.clone());
868                    exit_ui_thread();
869                }
870                launched
871            });
872            match create_application(on_launched) {
873                Ok(created) => *application.borrow_mut() = Some(created),
874                Err(error) => {
875                    *callback_result.borrow_mut() = Err(error);
876                    exit_ui_thread();
877                }
878            }
879        }));
880
881        let callback_result = std::mem::replace(&mut *result.borrow_mut(), Ok(()));
882        let host = HOST.with(|host| host.borrow_mut().take());
883        let host_result = host
884            .and_then(|mut host| {
885                for pump in host.windows.values_mut() {
886                    pump.shutdown();
887                }
888                host.fault
889            })
890            .map_or(Ok(()), Err);
891        let scheduler_result = SCHEDULER_FAULT
892            .with(|fault| fault.borrow_mut().take())
893            .map_or(Ok(()), Err);
894        start
895            .and(callback_result)
896            .and(host_result)
897            .and(scheduler_result)
898    }
899}
900
901fn publish_mounted_window(pump: Box<dyn LivePump>) {
902    let token = pump.window_token();
903    let mut pump = Some(pump);
904    let finalize = HOST.with(|host| {
905        let mut host = host.borrow_mut();
906        let host = host
907            .as_mut()
908            .expect("missing live host during window mount");
909        assert!(host.in_flight.remove(&token));
910        host.pending_opens = host.pending_opens.checked_sub(1).unwrap();
911        if host.closed_in_flight.remove(&token) {
912            Some((pump.take().unwrap(), host.is_empty()))
913        } else {
914            assert!(host.windows.insert(token, pump.take().unwrap()).is_none());
915            None
916        }
917    });
918    if let Some((pump, empty)) = finalize {
919        finalize_closed_window(pump, empty);
920    }
921}
922
923pub(crate) fn open_live_windows(roots: Vec<View>) -> Result<(), RuntimeError> {
924    if roots.is_empty() {
925        return Ok(());
926    }
927    let application =
928        HOST.with(|host| host.borrow().as_ref().map(|host| host._application.clone()));
929    let application = application.ok_or(RuntimeError::MissingApplication)?;
930    let pumps = roots
931        .into_iter()
932        .map(|root| {
933            Box::new(ComponentLoop {
934                pump: Pump::new(WinUiRuntime::with_application(application.clone())),
935                root: Some(root),
936                #[cfg(feature = "test")]
937                test: Default::default(),
938            }) as Box<dyn LivePump>
939        })
940        .collect::<Vec<_>>();
941    let tokens = pumps
942        .iter()
943        .map(|pump| pump.window_token())
944        .collect::<Vec<_>>();
945    let registered = HOST.with(|host| {
946        let mut host = host.borrow_mut();
947        let Some(host) = host.as_mut() else {
948            return false;
949        };
950        if host.pending_opens.saturating_add(pumps.len()) > MAX_PENDING_WINDOW_OPENS {
951            return false;
952        }
953        host.pending_opens += pumps.len();
954        for token in &tokens {
955            assert!(host.in_flight.insert(*token));
956        }
957        true
958    });
959    if !registered {
960        return Err(RuntimeError::WindowOpenCapacity);
961    }
962
963    let pending = Rc::new(RefCell::new(Some(pumps)));
964    let pending_mount = Rc::clone(&pending);
965    let mount = DispatcherQueueHandler::new(move || {
966        let Some(pumps) = pending_mount.borrow_mut().take() else {
967            return;
968        };
969        for mut pump in pumps {
970            match pump.mount() {
971                Ok(()) => publish_mounted_window(pump),
972                Err(error) => reject_pending_window(pump, error),
973            }
974        }
975    });
976    let queued = DispatcherQueue::GetForCurrentThread()
977        .map_err(winui_runtime_error)
978        .and_then(|dispatcher| {
979            dispatcher
980                .TryEnqueueWithPriority(DispatcherQueuePriority::Normal, &mount)
981                .map_err(winui_runtime_error)
982        });
983    match queued {
984        Ok(true) => Ok(()),
985        Ok(false) => {
986            rollback_pending_windows(&tokens);
987            Err(RuntimeError::DispatcherRejected)
988        }
989        Err(error) => {
990            rollback_pending_windows(&tokens);
991            Err(error)
992        }
993    }
994}
995
996fn rollback_pending_windows(tokens: &[WindowToken]) {
997    HOST.with(|host| {
998        let mut host = host.borrow_mut();
999        let Some(host) = host.as_mut() else {
1000            return;
1001        };
1002        for token in tokens {
1003            assert!(host.in_flight.remove(token));
1004            host.closed_in_flight.remove(token);
1005        }
1006        host.pending_opens = host.pending_opens.checked_sub(tokens.len()).unwrap();
1007    });
1008}
1009
1010fn reject_pending_window(mut pump: Box<dyn LivePump>, error: PumpError) {
1011    let token = pump.window_token();
1012    let rejected = error.is_declaration_rejection();
1013    pump.shutdown();
1014    let empty = HOST.with(|host| {
1015        let mut host = host.borrow_mut();
1016        let host = host
1017            .as_mut()
1018            .expect("missing live host during window rejection");
1019        assert!(host.in_flight.remove(&token));
1020        host.closed_in_flight.remove(&token);
1021        host.pending_opens = host.pending_opens.checked_sub(1).unwrap();
1022        if !rejected {
1023            host.fault = Some(pump_error(error.clone()));
1024        }
1025        host.is_empty()
1026    });
1027    if rejected {
1028        eprintln!("windows-reactor rejected a runtime window: {error:?}");
1029    } else {
1030        eprintln!("windows-reactor runtime window fault: {error:?}");
1031        exit_ui_thread();
1032    }
1033    if empty {
1034        exit_ui_thread();
1035    }
1036}
1037
1038pub(crate) fn dispatch_native_events(token: WindowToken) {
1039    HOST.with(|host| {
1040        let Some(mut live) = ({
1041            let mut host = host.borrow_mut();
1042            let Some(host) = host.as_mut() else {
1043                return;
1044            };
1045            let live = host.windows.remove(&token);
1046            if live.is_some() {
1047                host.in_flight.insert(token);
1048            }
1049            live
1050        }) else {
1051            return;
1052        };
1053        let mut rearm = false;
1054        let mut fault = None;
1055        #[cfg(feature = "test")]
1056        let dispatch_started = std::time::Instant::now();
1057        match live.dispatch_events() {
1058            Ok(()) => rearm = live.native_work_pending(),
1059            Err(error) => {
1060                let error = pump_error(error);
1061                eprintln!("windows-reactor fault: {error}");
1062                fault = Some(error);
1063                live.shutdown();
1064                exit_ui_thread();
1065            }
1066        }
1067        #[cfg(feature = "test")]
1068        LIVE_DISPATCH_TIMES_US.with(|times| {
1069            times
1070                .borrow_mut()
1071                .push(dispatch_started.elapsed().as_secs_f64() * 1_000_000.0);
1072        });
1073        for diagnostic in live.drain_diagnostics() {
1074            match diagnostic {
1075                PumpDiagnostic::WindowOpenRejected { error } => {
1076                    let message = format!("runtime window open was rejected: {error:?}");
1077                    #[cfg(feature = "test")]
1078                    test::record_live_diagnostic(message.clone());
1079                    eprintln!("windows-reactor warning: {message}");
1080                }
1081                PumpDiagnostic::VirtualRowRootCount {
1082                    collection,
1083                    key,
1084                    actual,
1085                } => {
1086                    let message = format!(
1087                        "virtual row {key:?} in {collection:?} has {actual} native roots; \
1088                         shell left empty"
1089                    );
1090                    #[cfg(feature = "test")]
1091                    test::record_live_diagnostic(message.clone());
1092                    eprintln!("windows-reactor warning: {message}");
1093                }
1094            }
1095        }
1096        let closed = host
1097            .borrow()
1098            .as_ref()
1099            .is_some_and(|host| host.closed_in_flight.contains(&token));
1100        if !closed
1101            && rearm
1102            && let Err(error) = live.schedule_dispatch()
1103        {
1104            fault = Some(runtime_error(error));
1105            live.shutdown();
1106            exit_ui_thread();
1107        }
1108        let mut finalize = None;
1109        if let Some(host) = host.borrow_mut().as_mut() {
1110            host.in_flight.remove(&token);
1111            let closed = host.closed_in_flight.remove(&token);
1112            if let Some(error) = fault {
1113                host.fault = Some(error);
1114            } else if closed {
1115                finalize = Some((live, host.is_empty()));
1116            } else {
1117                host.windows.insert(token, live);
1118            }
1119        }
1120        if let Some((live, empty)) = finalize {
1121            finalize_closed_window(live, empty);
1122        }
1123    });
1124}
1125
1126pub(crate) fn dispatch_window_closed(token: WindowToken) {
1127    let (live, empty) = HOST.with(|host| {
1128        let mut host = host.borrow_mut();
1129        let Some(host) = host.as_mut() else {
1130            return (None, false);
1131        };
1132        if host.in_flight.contains(&token) {
1133            host.closed_in_flight.insert(token);
1134            return (None, false);
1135        }
1136        let live = host.windows.remove(&token);
1137        (live, host.is_empty())
1138    });
1139    if let Some(live) = live {
1140        finalize_closed_window(live, empty);
1141    }
1142}
1143
1144fn finalize_closed_window(mut live: Box<dyn LivePump>, empty: bool) {
1145    live.close_scheduler();
1146    live.native_window_closed();
1147    let pending = Rc::new(RefCell::new(Some(live)));
1148    let pending_drop = Rc::clone(&pending);
1149    let drop_window = DispatcherQueueHandler::new(move || {
1150        drop(pending_drop.borrow_mut().take());
1151        if empty {
1152            exit_ui_thread();
1153        }
1154    });
1155    let queued = DispatcherQueue::GetForCurrentThread().and_then(|dispatcher| {
1156        dispatcher.TryEnqueueWithPriority(DispatcherQueuePriority::High, &drop_window)
1157    });
1158    if !matches!(queued, Ok(true)) {
1159        eprintln!("windows-reactor could not finalize a closed window");
1160        std::process::abort();
1161    }
1162}
1163
1164#[cfg(feature = "test")]
1165fn queue_live_delayed(
1166    dispatcher: DispatcherQueue,
1167    continuation: impl FnOnce() + 'static,
1168) -> windows_core::Result<()> {
1169    let timer = dispatcher.CreateTimer()?;
1170    timer.SetInterval(TimeSpan { duration: 100_000 })?;
1171    timer.SetIsRepeating(false)?;
1172    let continuation = Rc::new(RefCell::new(Some(continuation)));
1173    let revoker = Rc::new(RefCell::new(None));
1174    let tick_timer = timer.clone();
1175    let tick_continuation = Rc::clone(&continuation);
1176    let tick_revoker = Rc::clone(&revoker);
1177    *revoker.borrow_mut() = Some(timer.Tick(move |_, _| {
1178        _ = tick_timer.Stop();
1179        tick_revoker.borrow_mut().take();
1180        if let Some(continuation) = tick_continuation.borrow_mut().take() {
1181            continuation();
1182        }
1183    })?);
1184    timer.Start()
1185}
1186
1187fn is_packaged_process() -> windows_core::Result<bool> {
1188    let mut length = 0;
1189    let rc = unsafe { GetCurrentPackageFullName(&mut length, windows_core::PWSTR::null()) };
1190    match rc {
1191        ERROR_INSUFFICIENT_BUFFER => Ok(true),
1192        APPMODEL_ERROR_NO_PACKAGE => Ok(false),
1193        _ => Err(windows_core::HRESULT::from(windows_core::WIN32_ERROR(rc as u32)).into()),
1194    }
1195}
1196
1197fn pump_error(error: PumpError) -> windows_core::Error {
1198    match error {
1199        PumpError::NativeApplyFailed(error) => {
1200            eprintln!(
1201                "windows-reactor fatal native command failure at {}: {:?}",
1202                error.command, error.error
1203            );
1204            std::process::abort();
1205        }
1206        error => windows_core::Error::new(E_FAIL, format!("{error:?}")),
1207    }
1208}
1209
1210fn runtime_error(error: RuntimeError) -> windows_core::Error {
1211    windows_core::Error::new(E_FAIL, format!("{error:?}"))
1212}
1213
1214fn winui_runtime_error(error: windows_core::Error) -> RuntimeError {
1215    RuntimeError::Native(error.code().0)
1216}
1217
1218pub(crate) fn fail_native_scheduler(error: RuntimeError) {
1219    SCHEDULER_FAULT.with(|fault| {
1220        if fault.borrow().is_none() {
1221            *fault.borrow_mut() = Some(runtime_error(error));
1222        }
1223    });
1224    exit_ui_thread();
1225}