1use std::rc::Rc;
11
12use inset_embedder::{AppExitResponse, AppExitType, AppLifecycleState};
13use inset_foundation::{App, ChangeNotifierData, Handle, Task};
14use inset_scheduler::SchedulerBinding;
15
16use crate::SystemChrome;
17
18pub trait ServicesBindingOverridesObject: Sized + 'static {
23 fn handle_request_app_exit(self: Handle<Self>, app: &mut App) -> Task<AppExitResponse>;
25}
26
27pub trait ServicesBindingOverrides {
29 fn handle_request_app_exit(&self, app: &mut App) -> Task<AppExitResponse>;
31}
32
33impl<T: ServicesBindingOverridesObject> ServicesBindingOverrides for Handle<T> {
34 fn handle_request_app_exit(&self, app: &mut App) -> Task<AppExitResponse> {
35 T::handle_request_app_exit(*self, app)
36 }
37}
38
39#[derive(Default)]
42pub struct ServicesBinding {
43 overrides: Option<Rc<dyn ServicesBindingOverrides>>,
44 clipboard: Option<Handle<ChangeNotifierData>>,
46 initialized: bool,
48}
49
50impl ServicesBinding {
51 pub fn instance(app: &mut App) -> Handle<ServicesBinding> {
56 let this: Handle<ServicesBinding> = app.singleton();
57 if !app.get(this).initialized {
58 app.get_mut(this).initialized = true;
59 let clipboard = app.create(ChangeNotifierData::new());
60 app.get_mut(this).clipboard = Some(clipboard);
61 this.read_initial_lifecycle_state_from_native_window(app);
62 }
63 this
64 }
65
66 pub fn clipboard(self: Handle<Self>, app: &App) -> Handle<ChangeNotifierData> {
74 app.get(self)
75 .clipboard
76 .expect("ServicesBinding::instance creates the clipboard listenable")
77 }
78
79 pub fn handle_clipboard_changed(self: Handle<Self>, app: &mut App) {
81 self.clipboard(app).notify_listeners(app);
82 }
83
84 pub fn set_overrides(
87 self: Handle<Self>,
88 app: &mut App,
89 overrides: Rc<dyn ServicesBindingOverrides>,
90 ) {
91 app.get_mut(self).overrides = Some(overrides);
92 }
93
94 pub fn read_initial_lifecycle_state_from_native_window(self: Handle<Self>, app: &mut App) {
107 let initial_lifecycle_state = app.platform().initial_lifecycle_state();
108 let Some(initial_lifecycle_state) = initial_lifecycle_state else {
109 return;
110 };
111 if SchedulerBinding::lifecycle_state(app).is_some() {
112 return;
113 }
114 self.handle_lifecycle_message(app, initial_lifecycle_state);
115 }
116
117 pub fn handle_lifecycle_message(self: Handle<Self>, app: &mut App, state: AppLifecycleState) {
122 let generated = generate_state_transitions(SchedulerBinding::lifecycle_state(app), state);
123 for state_change in generated {
124 SchedulerBinding::handle_app_lifecycle_state_changed(app, state_change);
125 SystemChrome::handle_app_lifecycle_state_changed(app, state_change);
126 }
127 }
128
129 pub fn handle_request_app_exit(self: Handle<Self>, app: &mut App) -> Task<AppExitResponse> {
148 let overrides = app.get(self).overrides.clone();
149 match overrides {
150 Some(overrides) => overrides.handle_request_app_exit(app),
151 None => Task::ready(AppExitResponse::Exit),
152 }
153 }
154
155 pub fn exit_application(
190 self: Handle<Self>,
191 app: &App,
192 exit_type: AppExitType,
193 exit_code: i32,
194 ) -> Task<AppExitResponse> {
195 let platform = app.platform();
196 let Some(app_exit) = platform.app_exit() else {
197 return Task::ready(AppExitResponse::Cancel);
198 };
199 match exit_type {
200 AppExitType::Required => {
201 app_exit.quit(exit_code);
202 Task::ready(AppExitResponse::Exit)
203 }
204 AppExitType::Cancelable => app.spawn(async move |cx| {
205 let response = cx.update(|app| self.handle_request_app_exit(app)).await;
206 if response == AppExitResponse::Exit {
207 cx.update(|app| quit(app, exit_code));
208 }
209 response
210 }),
211 }
212 }
213}
214
215fn quit(app: &App, exit_code: i32) {
217 if let Some(app_exit) = app.platform().app_exit() {
218 app_exit.quit(exit_code);
219 }
220}
221
222fn generate_state_transitions(
225 previous_state: Option<AppLifecycleState>,
226 state: AppLifecycleState,
227) -> Vec<AppLifecycleState> {
228 if previous_state == Some(state) {
229 return Vec::new();
230 }
231 let mut state_changes = Vec::new();
232 match previous_state {
233 None => state_changes.push(state),
235 Some(previous_state) => {
236 let previous_state_index = state_index(previous_state);
237 let state_index = state_index(state);
238 if state == AppLifecycleState::Detached {
239 state_changes.extend(STATE_ORDER[previous_state_index + 1..].iter().copied());
240 state_changes.push(AppLifecycleState::Detached);
241 } else if previous_state_index > state_index {
242 state_changes.extend(STATE_ORDER[state_index..previous_state_index].iter().rev());
243 } else {
244 state_changes.extend(&STATE_ORDER[previous_state_index + 1..=state_index]);
245 }
246 }
247 }
248 debug_assert!(
249 {
250 let mut starting = previous_state;
251 state_changes.iter().all(|&ending| {
252 let valid = debug_verify_lifecycle_change(starting, ending);
253 starting = Some(ending);
254 valid
255 })
256 },
257 "Invalid lifecycle state transition generated from {previous_state:?} to {state:?} (generated {state_changes:?})"
258 );
259 state_changes
260}
261
262const STATE_ORDER: [AppLifecycleState; 5] = [
264 AppLifecycleState::Detached,
265 AppLifecycleState::Resumed,
266 AppLifecycleState::Inactive,
267 AppLifecycleState::Hidden,
268 AppLifecycleState::Paused,
269];
270
271fn state_index(state: AppLifecycleState) -> usize {
272 STATE_ORDER
273 .iter()
274 .position(|&ordered| ordered == state)
275 .expect("every state is in the order")
276}
277
278fn debug_verify_lifecycle_change(
279 starting: Option<AppLifecycleState>,
280 ending: AppLifecycleState,
281) -> bool {
282 let Some(starting) = starting else {
283 return true;
285 };
286 if starting == ending {
287 return false;
289 }
290 match starting {
291 AppLifecycleState::Resumed => ending == AppLifecycleState::Inactive,
293 AppLifecycleState::Detached => {
294 ending == AppLifecycleState::Resumed || ending == AppLifecycleState::Paused
295 }
296 AppLifecycleState::Inactive => {
297 ending == AppLifecycleState::Resumed || ending == AppLifecycleState::Hidden
298 }
299 AppLifecycleState::Hidden => {
300 ending == AppLifecycleState::Paused || ending == AppLifecycleState::Inactive
301 }
302 AppLifecycleState::Paused => {
303 ending == AppLifecycleState::Hidden || ending == AppLifecycleState::Detached
304 }
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use std::cell::Cell;
311 use std::rc::Rc;
312
313 use std::future::Future;
314
315 use inset_embedder::AppExit;
316 use inset_foundation::{AppCell, Completer};
317 use inset_test::TestPlatform;
318
319 use super::*;
320
321 #[derive(Default)]
323 struct RecordingAppExit {
324 quit_with: Cell<Option<i32>>,
325 }
326
327 impl AppExit for RecordingAppExit {
328 fn quit(&self, exit_code: i32) {
329 self.quit_with.set(Some(exit_code));
330 }
331 }
332
333 fn quitting_through(host: &Rc<RecordingAppExit>) -> Rc<AppCell> {
335 AppCell::with_platform(Rc::new(
336 TestPlatform::new().with_app_exit(Rc::clone(host) as Rc<dyn AppExit>),
337 ))
338 }
339
340 fn answer_with<T: ServicesBindingOverridesObject + 'static>(cell: &Rc<AppCell>, binding: T) {
342 let mut app = cell.borrow_mut();
343 let binding = app.create(binding);
344 ServicesBinding::instance(&mut app).set_overrides(&mut app, Rc::new(binding));
345 }
346
347 fn awaited(
350 cell: &Rc<AppCell>,
351 answer: impl Future<Output = AppExitResponse> + 'static,
352 ) -> Rc<Cell<Option<AppExitResponse>>> {
353 let awaited = Rc::new(Cell::new(None));
354 let out = Rc::clone(&awaited);
355 cell.borrow()
356 .spawn(async move |_cx| out.set(Some(answer.await)));
357 cell.checkpoint();
358 awaited
359 }
360
361 fn exit_application(cell: &Rc<AppCell>, exit_type: AppExitType) -> AppExitResponse {
362 let response = {
363 let mut app = cell.borrow_mut();
364 ServicesBinding::instance(&mut app).exit_application(&app, exit_type, 0)
365 };
366 let answer = awaited(cell, response).get();
367 answer.expect("the exit was answered")
368 }
369
370 fn handle_request_app_exit(cell: &Rc<AppCell>) -> Rc<Cell<Option<AppExitResponse>>> {
371 let response = {
372 let mut app = cell.borrow_mut();
373 ServicesBinding::instance(&mut app).handle_request_app_exit(&mut app)
374 };
375 awaited(cell, response)
376 }
377
378 #[test]
380 fn the_states_between_are_generated_in_order() {
381 use AppLifecycleState::*;
382 let steps = [
383 (None, Paused, vec![Paused]),
384 (Some(Paused), Resumed, vec![Hidden, Inactive, Resumed]),
385 (Some(Resumed), Paused, vec![Inactive, Hidden, Paused]),
386 (Some(Paused), Inactive, vec![Hidden, Inactive]),
387 (Some(Inactive), Hidden, vec![Hidden]),
388 (Some(Hidden), Paused, vec![Paused]),
389 (Some(Paused), Detached, vec![Detached]),
390 (Some(Detached), Resumed, vec![Resumed]),
391 (
392 Some(Resumed),
393 Detached,
394 vec![Inactive, Hidden, Paused, Detached],
395 ),
396 (Some(Hidden), Hidden, vec![]),
397 ];
398 for (previous_state, state, generated) in steps {
399 assert_eq!(
400 generate_state_transitions(previous_state, state),
401 generated,
402 "from {previous_state:?} to {state:?}"
403 );
404 }
405 }
406
407 #[test]
408 fn the_initial_state_is_read_when_the_binding_is_made() {
409 let cell = AppCell::with_platform(Rc::new(
410 TestPlatform::new().with_initial_lifecycle_state(AppLifecycleState::Hidden),
411 ));
412 let mut app = cell.borrow_mut();
413 ServicesBinding::instance(&mut app);
414 assert_eq!(
415 SchedulerBinding::lifecycle_state(&mut app),
416 Some(AppLifecycleState::Hidden)
417 );
418 assert!(!SchedulerBinding::frames_enabled(&mut app));
419 }
420
421 #[test]
422 fn a_host_without_an_initial_state_leaves_the_app_without_one() {
423 let cell = AppCell::new();
424 let mut app = cell.borrow_mut();
425 ServicesBinding::instance(&mut app);
426 assert_eq!(SchedulerBinding::lifecycle_state(&mut app), None);
427 }
428
429 #[test]
430 fn a_required_exit_quits_without_asking() {
431 let host = Rc::default();
432 let cell = quitting_through(&host);
433 answer_with(&cell, Refusing);
434 let response = exit_application(&cell, AppExitType::Required);
435 assert_eq!(host.quit_with.get(), Some(0));
436 assert_eq!(response, AppExitResponse::Exit);
437 }
438
439 #[test]
440 fn a_cancelable_exit_quits_once_the_app_agrees() {
441 let host = Rc::default();
442 let cell = quitting_through(&host);
443 let response = exit_application(&cell, AppExitType::Cancelable);
444 assert_eq!(host.quit_with.get(), Some(0));
445 assert_eq!(response, AppExitResponse::Exit);
446 }
447
448 #[test]
449 fn a_cancelable_exit_the_app_refuses_does_not_quit() {
450 let host = Rc::default();
451 let cell = quitting_through(&host);
452 answer_with(&cell, Refusing);
453 let response = exit_application(&cell, AppExitType::Cancelable);
454 assert_eq!(host.quit_with.get(), None);
455 assert_eq!(response, AppExitResponse::Cancel);
456 }
457
458 #[test]
459 fn exit_application_is_canceled_where_the_host_cannot_exit() {
460 let cell = AppCell::with_platform(Rc::new(TestPlatform::new()));
461 let response = exit_application(&cell, AppExitType::Cancelable);
462 assert_eq!(response, AppExitResponse::Cancel);
463 }
464
465 #[test]
466 fn default_handle_request_app_exit_returns_exit() {
467 let cell = AppCell::new();
468 let response = handle_request_app_exit(&cell);
469 assert_eq!(response.get(), Some(AppExitResponse::Exit));
470 }
471
472 struct Refusing;
474
475 impl ServicesBindingOverridesObject for Refusing {
476 fn handle_request_app_exit(self: Handle<Self>, _app: &mut App) -> Task<AppExitResponse> {
477 Task::ready(AppExitResponse::Cancel)
478 }
479 }
480
481 struct Deciding {
483 decision: Completer<AppExitResponse>,
484 }
485
486 impl ServicesBindingOverridesObject for Deciding {
487 fn handle_request_app_exit(self: Handle<Self>, app: &mut App) -> Task<AppExitResponse> {
488 let decision = app.get(self).decision.future();
489 app.spawn(async move |_cx| decision.await)
490 }
491 }
492
493 #[test]
494 fn the_binding_above_answers_in_place_of_the_default_once_it_has_decided() {
495 let cell = AppCell::new();
496 let decision = Completer::new();
497 answer_with(
498 &cell,
499 Deciding {
500 decision: decision.clone(),
501 },
502 );
503 let response = handle_request_app_exit(&cell);
504 assert_eq!(response.get(), None, "still deciding");
505
506 decision.complete(&mut cell.borrow_mut(), AppExitResponse::Cancel);
507 cell.checkpoint();
508 assert_eq!(response.get(), Some(AppExitResponse::Cancel));
509 }
510}