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
723pub struct App;
727
728impl App {
729 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 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 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}