@@ -14,15 +14,25 @@ static constexpr char kChannelName[] = "flutter/platform";
1414static constexpr char kBadArgumentsError [] = " Bad Arguments" ;
1515static constexpr char kUnknownClipboardFormatError [] =
1616 " Unknown Clipboard Format" ;
17- static constexpr char kFailedError [] = " Failed " ;
17+ static constexpr char kInProgressError [] = " In Progress " ;
1818static constexpr char kGetClipboardDataMethod [] = " Clipboard.getData" ;
1919static constexpr char kSetClipboardDataMethod [] = " Clipboard.setData" ;
2020static constexpr char kClipboardHasStringsMethod [] = " Clipboard.hasStrings" ;
21+ static constexpr char kExitApplicationMethod [] = " System.exitApplication" ;
22+ static constexpr char kRequestAppExitMethod [] = " System.requestAppExit" ;
2123static constexpr char kPlaySoundMethod [] = " SystemSound.play" ;
2224static constexpr char kSystemNavigatorPopMethod [] = " SystemNavigator.pop" ;
2325static constexpr char kTextKey [] = " text" ;
2426static constexpr char kValueKey [] = " value" ;
2527
28+ static constexpr char kExitTypeKey [] = " type" ;
29+ static constexpr char kExitTypeCancelable [] = " cancelable" ;
30+ static constexpr char kExitTypeRequired [] = " required" ;
31+
32+ static constexpr char kExitResponseKey [] = " response" ;
33+ static constexpr char kExitResponseCancel [] = " cancel" ;
34+ static constexpr char kExitResponseExit [] = " exit" ;
35+
2636static constexpr char kTextPlainFormat [] = " text/plain" ;
2737
2838static constexpr char kSoundTypeAlert [] = " SystemSoundType.alert" ;
@@ -32,6 +42,8 @@ struct _FlPlatformPlugin {
3242 GObject parent_instance;
3343
3444 FlMethodChannel* channel;
45+ FlMethodCall* exit_application_method_call;
46+ GCancellable* cancellable;
3547};
3648
3749G_DEFINE_TYPE (FlPlatformPlugin, fl_platform_plugin, G_TYPE_OBJECT)
@@ -140,6 +152,138 @@ static FlMethodResponse* clipboard_has_strings_async(
140152 return nullptr ;
141153}
142154
155+ // Get the exit response from a System.requestAppExit method call.
156+ static gchar* get_exit_response (FlMethodResponse* response) {
157+ if (response == nullptr ) {
158+ return nullptr ;
159+ }
160+
161+ g_autoptr (GError) error = nullptr ;
162+ FlValue* result = fl_method_response_get_result (response, &error);
163+ if (result == nullptr ) {
164+ g_warning (" Error returned from System.requestAppExit: %s" , error->message );
165+ return nullptr ;
166+ }
167+ if (fl_value_get_type (result) != FL_VALUE_TYPE_MAP) {
168+ g_warning (" System.requestAppExit result argument map missing or malformed" );
169+ return nullptr ;
170+ }
171+
172+ FlValue* response_value = fl_value_lookup_string (result, kExitResponseKey );
173+ if (fl_value_get_type (response_value) != FL_VALUE_TYPE_STRING) {
174+ g_warning (" Invalid response from System.requestAppExit" );
175+ return nullptr ;
176+ }
177+ return g_strdup (fl_value_get_string (response_value));
178+ }
179+
180+ // Quit this application
181+ static void quit_application () {
182+ GApplication* app = g_application_get_default ();
183+ if (app == nullptr ) {
184+ // Unable to gracefully quit, so just exit the process.
185+ exit (0 );
186+ } else {
187+ g_application_quit (app);
188+ }
189+ }
190+
191+ // Handle response of System.requestAppExit.
192+ static void request_app_exit_response_cb (GObject* object,
193+ GAsyncResult* result,
194+ gpointer user_data) {
195+ FlPlatformPlugin* self = FL_PLATFORM_PLUGIN (user_data);
196+
197+ g_autoptr (GError) error = nullptr ;
198+ g_autoptr (FlMethodResponse) method_response =
199+ fl_method_channel_invoke_method_finish (FL_METHOD_CHANNEL (object), result,
200+ &error);
201+ g_autofree gchar* exit_response = nullptr ;
202+ if (method_response == nullptr ) {
203+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
204+ return ;
205+ }
206+ g_warning (" Failed to complete System.requestAppExit: %s" , error->message );
207+ } else {
208+ exit_response = get_exit_response (method_response);
209+ }
210+ // If something went wrong, then just exit.
211+ if (exit_response == nullptr ) {
212+ exit_response = g_strdup (kExitResponseExit );
213+ }
214+
215+ if (g_str_equal (exit_response, kExitResponseExit )) {
216+ quit_application ();
217+ } else if (g_str_equal (exit_response, kExitResponseCancel )) {
218+ // Canceled - no action to take.
219+ }
220+
221+ // If request was due to a request from Flutter, pass result back.
222+ if (self->exit_application_method_call != nullptr ) {
223+ g_autoptr (FlValue) exit_result = fl_value_new_map ();
224+ fl_value_set_string_take (exit_result, kExitResponseKey ,
225+ fl_value_new_string (exit_response));
226+ g_autoptr (FlMethodResponse) exit_response =
227+ FL_METHOD_RESPONSE (fl_method_success_response_new (exit_result));
228+ if (!fl_method_call_respond (self->exit_application_method_call ,
229+ exit_response, &error)) {
230+ g_warning (" Failed to send response to System.exitApplication: %s" ,
231+ error->message );
232+ }
233+ g_clear_object (&self->exit_application_method_call );
234+ }
235+ }
236+
237+ // Send a request to Flutter to exit the application.
238+ static void request_app_exit (FlPlatformPlugin* self, const char * type) {
239+ g_autoptr (FlValue) args = fl_value_new_map ();
240+ fl_value_set_string_take (args, kExitTypeKey , fl_value_new_string (type));
241+ fl_method_channel_invoke_method (self->channel , kRequestAppExitMethod , args,
242+ self->cancellable ,
243+ request_app_exit_response_cb, self);
244+ }
245+
246+ // Called when Flutter wants to exit the application.
247+ static FlMethodResponse* system_exit_application (FlPlatformPlugin* self,
248+ FlMethodCall* method_call) {
249+ FlValue* args = fl_method_call_get_args (method_call);
250+ if (fl_value_get_type (args) != FL_VALUE_TYPE_MAP) {
251+ return FL_METHOD_RESPONSE (fl_method_error_response_new (
252+ kBadArgumentsError , " Argument map missing or malformed" , nullptr ));
253+ }
254+
255+ FlValue* type_value = fl_value_lookup_string (args, kExitTypeKey );
256+ if (type_value == nullptr ||
257+ fl_value_get_type (type_value) != FL_VALUE_TYPE_STRING) {
258+ return FL_METHOD_RESPONSE (fl_method_error_response_new (
259+ kBadArgumentsError , " Missing type argument" , nullptr ));
260+ }
261+ const char * type = fl_value_get_string (type_value);
262+
263+ // Save method call to respond to when our request to Flutter completes.
264+ if (self->exit_application_method_call != nullptr ) {
265+ return FL_METHOD_RESPONSE (fl_method_error_response_new (
266+ kInProgressError , " Request already in progress" , nullptr ));
267+ }
268+ self->exit_application_method_call =
269+ FL_METHOD_CALL (g_object_ref (method_call));
270+
271+ // Requested to immediately quit.
272+ if (g_str_equal (type, kExitTypeRequired )) {
273+ quit_application ();
274+ g_autoptr (FlValue) exit_result = fl_value_new_map ();
275+ fl_value_set_string_take (exit_result, kExitResponseKey ,
276+ fl_value_new_string (kExitResponseExit ));
277+ return FL_METHOD_RESPONSE (fl_method_success_response_new (exit_result));
278+ }
279+
280+ // Send the request back to Flutter to follow the standard process.
281+ request_app_exit (self, type);
282+
283+ // Will respond later.
284+ return nullptr ;
285+ }
286+
143287// Called when Flutter wants to play a sound.
144288static FlMethodResponse* system_sound_play (FlPlatformPlugin* self,
145289 FlValue* args) {
@@ -165,14 +309,7 @@ static FlMethodResponse* system_sound_play(FlPlatformPlugin* self,
165309
166310// Called when Flutter wants to quit the application.
167311static FlMethodResponse* system_navigator_pop (FlPlatformPlugin* self) {
168- GApplication* app = g_application_get_default ();
169- if (app == nullptr ) {
170- return FL_METHOD_RESPONSE (fl_method_error_response_new (
171- kFailedError , " Unable to get GApplication" , nullptr ));
172- }
173-
174- g_application_quit (app);
175-
312+ quit_application ();
176313 return FL_METHOD_RESPONSE (fl_method_success_response_new (nullptr ));
177314}
178315
@@ -192,6 +329,8 @@ static void method_call_cb(FlMethodChannel* channel,
192329 response = clipboard_get_data_async (self, method_call);
193330 } else if (strcmp (method, kClipboardHasStringsMethod ) == 0 ) {
194331 response = clipboard_has_strings_async (self, method_call);
332+ } else if (strcmp (method, kExitApplicationMethod ) == 0 ) {
333+ response = system_exit_application (self, method_call);
195334 } else if (strcmp (method, kPlaySoundMethod ) == 0 ) {
196335 response = system_sound_play (self, args);
197336 } else if (strcmp (method, kSystemNavigatorPopMethod ) == 0 ) {
@@ -208,7 +347,11 @@ static void method_call_cb(FlMethodChannel* channel,
208347static void fl_platform_plugin_dispose (GObject* object) {
209348 FlPlatformPlugin* self = FL_PLATFORM_PLUGIN (object);
210349
350+ g_cancellable_cancel (self->cancellable );
351+
211352 g_clear_object (&self->channel );
353+ g_clear_object (&self->exit_application_method_call );
354+ g_clear_object (&self->cancellable );
212355
213356 G_OBJECT_CLASS (fl_platform_plugin_parent_class)->dispose (object);
214357}
@@ -217,7 +360,9 @@ static void fl_platform_plugin_class_init(FlPlatformPluginClass* klass) {
217360 G_OBJECT_CLASS (klass)->dispose = fl_platform_plugin_dispose;
218361}
219362
220- static void fl_platform_plugin_init (FlPlatformPlugin* self) {}
363+ static void fl_platform_plugin_init (FlPlatformPlugin* self) {
364+ self->cancellable = g_cancellable_new ();
365+ }
221366
222367FlPlatformPlugin* fl_platform_plugin_new (FlBinaryMessenger* messenger) {
223368 g_return_val_if_fail (FL_IS_BINARY_MESSENGER (messenger), nullptr );
@@ -233,3 +378,9 @@ FlPlatformPlugin* fl_platform_plugin_new(FlBinaryMessenger* messenger) {
233378
234379 return self;
235380}
381+
382+ void fl_platform_plugin_request_app_exit (FlPlatformPlugin* self) {
383+ g_return_if_fail (FL_IS_PLATFORM_PLUGIN (self));
384+ // Request a cancellable exit.
385+ request_app_exit (self, kExitTypeCancelable );
386+ }
0 commit comments