Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 81c9e94

Browse files
committed
Add platform channel exit support.
1 parent 167c97b commit 81c9e94

12 files changed

+374
-7
lines changed

lib/ui/platform_dispatcher.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,6 +1733,42 @@ enum AppLifecycleState {
17331733
detached,
17341734
}
17351735

1736+
/// The possible responses to a request to exit the application.
1737+
///
1738+
/// The request is typically responded to by a [WidgetsBindingObserver].
1739+
// TODO(gspencergoog): Insert references here to AppLifecycleListener and to the
1740+
// actual function called on WidgetsBindingObserver once those have landed in
1741+
// the framework.
1742+
enum AppExitResponse {
1743+
/// Exiting the application can proceed.
1744+
exit,
1745+
/// Cancel the exit: do not exit the application.
1746+
cancel,
1747+
}
1748+
1749+
/// The type of application exit to perform when calling
1750+
/// `ServicesBinding.exitApplication`.
1751+
// TODO(gspencergoog): Insert references here to ServicesBinding.exitApplication
1752+
// that has landed in the framework.
1753+
enum AppExitType {
1754+
/// Requests that the application start an orderly exit, sending a request
1755+
/// back to the framework through the [WidgetsBinding], and if that responds
1756+
/// with [AppExitResponse.exit], then proceed with the same steps as a
1757+
/// [required] exit. if that responds with [AppExitResponse.cancel], then the
1758+
/// exit request is canceled and the application continues executing normally.
1759+
cancelable,
1760+
1761+
/// A non-cancelable orderly exit request. The engine will shut down the
1762+
/// engine and call the native UI toolkit's exit API.
1763+
///
1764+
/// If you need an even faster and more dangerous exit, then call `dart:io`'s
1765+
/// `exit()` directly, and even the native toolkit's exit API won't be called.
1766+
/// This is quite dangerous, though, since it's possible that the engine will
1767+
/// crash because it hasn't been properly shut down, causing the app to crash
1768+
/// on exit.
1769+
required,
1770+
}
1771+
17361772
/// A representation of distances for each of the four edges of a rectangle,
17371773
/// used to encode the view insets and padding that applications should place
17381774
/// around their user interface, as exposed by [FlutterView.viewInsets] and

lib/web_ui/lib/platform_dispatcher.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,16 @@ enum AppLifecycleState {
374374
detached,
375375
}
376376

377+
enum AppExitResponse {
378+
exit,
379+
cancel,
380+
}
381+
382+
enum AppExitType {
383+
cancelable,
384+
required,
385+
}
386+
377387
abstract class ViewPadding {
378388
const factory ViewPadding._(
379389
{required double left,

shell/platform/darwin/macos/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ _framework_binary_subpath = "Versions/A/$_flutter_framework_name"
3838
# the Flutter engine source root.
3939
_flutter_framework_headers = [
4040
"framework/Headers/FlutterAppDelegate.h",
41+
"framework/Headers/FlutterApplication.h",
4142
"framework/Headers/FlutterDartProject.h",
4243
"framework/Headers/FlutterEngine.h",
4344
"framework/Headers/FlutterMacOS.h",
@@ -57,6 +58,7 @@ source_set("flutter_framework_source") {
5758
"framework/Source/AccessibilityBridgeMac.h",
5859
"framework/Source/AccessibilityBridgeMac.mm",
5960
"framework/Source/FlutterAppDelegate.mm",
61+
"framework/Source/FlutterApplication.mm",
6062
"framework/Source/FlutterBackingStore.h",
6163
"framework/Source/FlutterBackingStore.mm",
6264
"framework/Source/FlutterChannelKeyResponder.h",
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_FLUTTERAPPLICATION_H_
6+
#define FLUTTER_FLUTTERAPPLICATION_H_
7+
8+
#import <Cocoa/Cocoa.h>
9+
10+
/**
11+
* A Flutter-specific subclass of NSApplication that overrides |terminate| and
12+
* provides an addition |terminateApplication| method so that Flutter can handle
13+
* requests for termination in an asynchronous fashion.
14+
*
15+
* When a call to |terminate| comes in, either from the OS through a Quit menu
16+
* item, through the Quit item in the dock context menu, or from the application
17+
* itself, a request is sent to the Flutter framework. If that request is
18+
* granted, this subclass will (in |terminateApplication|) call
19+
* |NSApplication|'s version of |terminate| to proceed with terminating the
20+
* application normally by calling |applicationShouldTerminate|, etc.
21+
*
22+
* If the termination request is denied by the framework, then the application
23+
* will continue to execute normally, as if no |terminate| call were made.
24+
*
25+
* The |FlutterAppDelegate| always returns |NSTerminateNow| from
26+
* |applicationShouldTerminate|, since it has already decided by that point that
27+
* it should terminate.
28+
*
29+
* In order for this class to be used in place of |NSApplication|, the
30+
* "NSPrincipalClass" entry in the Info.plist for the application must be set to
31+
* "FlutterApplication". If it is not, then the application will not be given
32+
* the chance to deny a termination request, and calls to requestAppExit on the
33+
* engine (from the framework, typically) will simply exit the application
34+
* without ceremony.
35+
*
36+
* If the |NSApp| global isn't of type |FlutterApplication|, a log message will
37+
* be printed once in debug mode when the application is first accessed through
38+
* the singleton's |sharedApplication|, describing how to fix this.
39+
*/
40+
@interface FlutterApplication : NSApplication
41+
42+
/**
43+
* FlutterApplication's implementation of |terminate| doesn't terminate the
44+
* application: that is left up to the engine, which will call this function if
45+
* it decides that termination request is granted, which will start the regular
46+
* Cocoa flow for terminating the application, calling
47+
* |applicationShouldTermnate|, etc.
48+
*
49+
* @param(sender) The id of the object requesting the termination, or nil.
50+
*/
51+
- (void)terminateApplication:(id)sender;
52+
@end
53+
54+
#endif // FLUTTER_FLUTTERAPPLICATION_H_

shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
#import "FlutterAppDelegate.h"
6+
#import "FlutterApplication.h"
67
#import "FlutterBinaryMessenger.h"
78
#import "FlutterChannels.h"
89
#import "FlutterCodecs.h"

shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
// found in the LICENSE file.
44

55
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
6+
#include <AppKit/AppKit.h>
7+
#import "flutter/fml/logging.h"
8+
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_internal.h"
9+
#import "flutter/shell/platform/embedder/embedder.h"
610

711
@interface FlutterAppDelegate ()
812

@@ -13,10 +17,16 @@ - (NSString*)applicationName;
1317

1418
@end
1519

16-
@implementation FlutterAppDelegate
20+
@implementation FlutterAppDelegate {
21+
FlutterEngineTerminationHandler* _terminationHandler;
22+
}
1723

18-
// TODO(stuartmorgan): Implement application lifecycle forwarding to plugins here, as is done
19-
// on iOS. Currently macOS plugins don't have access to lifecycle messages.
24+
- (instancetype)init {
25+
if (self = [super init]) {
26+
_terminationHandler = nil;
27+
}
28+
return self;
29+
}
2030

2131
- (void)applicationWillFinishLaunching:(NSNotification*)notification {
2232
// Update UI elements to match the application name.
@@ -28,6 +38,13 @@ - (void)applicationWillFinishLaunching:(NSNotification*)notification {
2838
}
2939
}
3040

41+
// This always returns NSTerminateNow, since by the time we get here, the
42+
// application has already been asked if it should terminate or not, and if not,
43+
// then termination never gets this far.
44+
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender {
45+
return NSTerminateNow;
46+
}
47+
3148
#pragma mark Private Methods
3249

3350
- (NSString*)applicationName {
@@ -39,4 +56,17 @@ - (NSString*)applicationName {
3956
return applicationName;
4057
}
4158

59+
- (void)setTerminationRequestHandler:(FlutterEngineTerminationHandler*)handler {
60+
_terminationHandler = handler;
61+
}
62+
63+
- (void)tryToTerminateApplication:(FlutterApplication*)application
64+
exitType:(FlutterAppExitType)type {
65+
if (_terminationHandler) {
66+
[_terminationHandler tryToTerminateApplication:application exitType:type result:nil];
67+
} else {
68+
[application terminateApplication:application];
69+
}
70+
}
71+
4272
@end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_FLUTTERAPPDELEGATE_INTERNAL_H_
6+
#define FLUTTER_FLUTTERAPPDELEGATE_INTERNAL_H_
7+
8+
#include "embedder.h"
9+
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
10+
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
11+
12+
@interface FlutterAppDelegate ()
13+
/**
14+
* Allows the engine to add a handler for termination requests
15+
*/
16+
- (void)setTerminationRequestHandler:(FlutterEngineTerminationHandler*)handler;
17+
18+
/**
19+
* Allows the delegate to respond to an attempt to terminate the application.
20+
*/
21+
- (void)tryToTerminateApplication:(FlutterApplication*)application
22+
exitType:(FlutterAppExitType)type;
23+
@end
24+
25+
#endif // FLUTTER_FLUTTERAPPDELEGATE_INTERNAL_H_
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h"
6+
#include "embedder.h"
7+
#include "shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
8+
#include "shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_internal.h"
9+
10+
#ifndef FLUTTER_RELEASE
11+
namespace {
12+
static bool warned_about_flutter_application = false;
13+
} // namespace
14+
#endif // !FLUTTER_RELEASE
15+
16+
// An NSApplication subclass that implements overrides necessary for some
17+
// Flutter features, like application lifecycle handling.
18+
@interface FlutterApplication () {
19+
}
20+
@end
21+
22+
@implementation FlutterApplication
23+
24+
// Initialize NSApplication using the custom subclass. Check whether NSApp was
25+
// already initialized using another class, because that would break some
26+
// things. Warn about the mismatch only once, and only in debug builds.
27+
+ (NSApplication*)sharedApplication {
28+
NSApplication* app = [super sharedApplication];
29+
30+
// +sharedApplication initializes the global NSApp, so if we're delivering
31+
// something other than a FlutterApplication, warn the developer once.
32+
#ifndef FLUTTER_RELEASE
33+
if (!warned_about_flutter_application && ![NSApp isKindOfClass:self]) {
34+
NSLog(@"NSApp should be of type %s, not %s. "
35+
"Some application lifecycle requests (e.g. ServicesBinding.exitApplication) "
36+
"and notifications will be unavailable.\n"
37+
"Modify the application's NSPrincipleClass to be %s"
38+
"in the Info.plist to fix this.",
39+
[[self className] UTF8String], [[NSApp className] UTF8String],
40+
[[self className] UTF8String]);
41+
warned_about_flutter_application = true;
42+
}
43+
#endif // !FLUTTER_RELEASE
44+
return app;
45+
}
46+
47+
// |terminate| is the entry point for orderly "quit" operations in Cocoa. This
48+
// includes the application menu's Quit menu item and keyboard equivalent, the
49+
// application's dock icon menu's Quit menu item, "quit" (not "force quit") in
50+
// the Activity Monitor, and quits triggered by user logout and system restart
51+
// and shutdown.
52+
//
53+
// We override the normal |terminate| implementation. Our implementation, which
54+
// is specific to the asyncronous nature of Flutter, works by asking the
55+
// application delegate to terminate using its |tryToTerminateApplication|
56+
// method instead of going through |applicationShouldTerminate|.
57+
//
58+
// The standard |applicationShouldTerminate| is not used because returning
59+
// NSTerminateLater from that function moves the run loop into a modal dialog
60+
// mode (NSModalPanelRunLoopMode), which stops the main run loop from processing
61+
// messages like, for instance, the response to the method channel call, and
62+
// code paths leading to it must be redirected to |tryToTerminateApplication|.
63+
//
64+
// |tryToTerminateApplication| differs from the standard
65+
// |applicationShouldTerminate| in that no special event loop is run in the case
66+
// that immediate termination is not possible (e.g., if dialog boxes allowing
67+
// the user to cancel have to be shown, or data needs to be saved). Instead,
68+
// tryToTerminateApplication sends a method channel call to the framework asking
69+
// it if it is OK to terminate. When that method channel call returns with a
70+
// result, the application either terminates or continues running.
71+
- (void)terminate:(id)sender {
72+
FlutterAppDelegate* appDelegate = static_cast<FlutterAppDelegate*>([NSApp delegate]);
73+
74+
[appDelegate tryToTerminateApplication:self exitType:kFlutterAppExitTypeCancelable];
75+
// Return, don't exit. The application delegate is responsible for exiting on
76+
// its own by calling |-terminateApplication|.
77+
}
78+
79+
// Starts the regular Cocoa application termination flow, so that plugins will
80+
// get the appropriate notifications after the application has already decided
81+
// to quit. This is called after the application has decided that
82+
// it's OK to terminate.
83+
- (void)terminateApplication:(id)sender {
84+
[super terminate:sender];
85+
}
86+
@end

0 commit comments

Comments
 (0)