Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/platform/headless/event_loop.rs
1use std::mem::ManuallyDrop;
2use std::sync::mpsc::{Receiver, Sender};
3 
4use crate::{
5 platform::{
6 self,
7 app::{AppCallbackDispatcher, ApproveTerminateResult, TerminationResult},
8 TerminationMode,
9 },
10 AppContext, WindowId,
11};
12 
13/// Application events handled on the headless platform's main thread.
14pub(super) enum AppEvent {
15 /// Run the wrapped task on the main thread.
16 RunTask(ManuallyDrop<async_task::Runnable>),
17 /// Run a synchronous callback on the main thread.
18 RunCallback(Box<dyn FnOnce(&mut AppContext) + Send + Sync>),
19 /// Close a window.
20 CloseWindow(WindowId),
21 /// Active window changed.
22 ActiveWindowChanged(Option<WindowId>),
23 /// Exit the event loop, terminating the application.
24 Terminate(TerminationMode),
25}
26 
27/// Run a simple, blocking event loop that processes AppEvent messages until termination.
28pub(super) fn run(
29 mut ui_app: crate::App,
30 callbacks: &mut AppCallbackDispatcher,
31 init_fn: platform::app::AppInitCallbackFn,
32 receiver: Receiver<AppEvent>,
33 sender: Sender<AppEvent>,
34) -> TerminationResult {
35 // Set up Ctrl-C handler to gracefully terminate the app
36 setup_signal_handler(sender);
37 
38 // First, initialize the app.
39 callbacks.initialize_app(init_fn);
40 
41 // Then, process events until termination.
42 for event in receiver.iter() {
43 match event {
44 AppEvent::RunCallback(callback) => ui_app.update(callback),
45 AppEvent::RunTask(task) => {
46 // Poll a task on the main thread.
47 let task = ManuallyDrop::into_inner(task);
48 task.run();
49 }
50 AppEvent::Terminate(termination_mode) => {
51 let should_terminate = match termination_mode {
52 TerminationMode::Cancellable => {
53 matches!(
54 callbacks.should_terminate_app(),
55 ApproveTerminateResult::Terminate
56 )
57 }
58 TerminationMode::ForceTerminate | TerminationMode::ContentTransferred => true,
59 };
60 if should_terminate {
61 break;
62 }
63 }
64 AppEvent::CloseWindow(window_id) => {
65 // Notify the app that a window is closing. The app will then remove the window
66 // from WindowManager.
67 callbacks.window_will_close(window_id);
68 }
69 AppEvent::ActiveWindowChanged(window_id) => {
70 callbacks.active_window_changed(window_id);
71 }
72 }
73 }
74 
75 // Drop the receiver so the Ctrl+C signal handler's channel send will fail,
76 // causing it to fall through to `process::exit(130)`. Without this, the
77 // send succeeds (since the receiver is still in scope) but nobody is reading
78 // from the channel, making Ctrl+C ineffective during shutdown.
79 drop(receiver);
80 
81 callbacks.app_will_terminate();
82 
83 ui_app.termination_result().unwrap_or(Ok(()))
84}
85 
86/// Set up a signal handler for Ctrl-C (SIGINT) to gracefully terminate the app.
87///
88/// When Ctrl-C is received, this will send a Terminate event to the event loop,
89/// allowing the app to shut down gracefully via the existing termination logic.
90#[cfg(not(target_family = "wasm"))]
91fn setup_signal_handler(sender: Sender<AppEvent>) {
92 let result = ctrlc::set_handler(move || {
93 log::info!("Received Ctrl-C signal in headless mode, terminating application");
94 // Send a ForceTerminate event to ensure the app exits cleanly.
95 // We use ForceTerminate rather than Cancellable to ensure the app exits
96 // even if there are unsaved changes or other conditions that might prevent shutdown.
97 if sender
98 .send(AppEvent::Terminate(TerminationMode::ForceTerminate))
99 .is_err()
100 {
101 log::warn!("Failed to send termination event - event loop may have already stopped");
102 // If we can't send the event, force exit
103 std::process::exit(130); // 128 + SIGINT (2) = 130
104 }
105 });
106 
107 if let Err(e) = result {
108 log::warn!("Failed to set up Ctrl-C handler: {e}");
109 }
110}
111 
112#[cfg(target_family = "wasm")]
113fn setup_signal_handler(_sender: Sender<AppEvent>) {
114 // No signal handling on WASM
115}
116