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

Commit a189b31

Browse files
committed
Add platform channel System.exitApplication and System.requestAppExit support
1 parent adaa7cb commit a189b31

3 files changed

Lines changed: 168 additions & 1 deletion

File tree

shell/platform/linux/fl_platform_plugin.cc

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,26 @@ static constexpr char kChannelName[] = "flutter/platform";
1414
static constexpr char kBadArgumentsError[] = "Bad Arguments";
1515
static constexpr char kUnknownClipboardFormatError[] =
1616
"Unknown Clipboard Format";
17+
static constexpr char kInProgressError[] = "In Progress";
1718
static constexpr char kFailedError[] = "Failed";
1819
static constexpr char kGetClipboardDataMethod[] = "Clipboard.getData";
1920
static constexpr char kSetClipboardDataMethod[] = "Clipboard.setData";
2021
static constexpr char kClipboardHasStringsMethod[] = "Clipboard.hasStrings";
22+
static constexpr char kExitApplicationMethod[] = "System.exitApplication";
23+
static constexpr char kRequestAppExitMethod[] = "System.requestAppExit";
2124
static constexpr char kPlaySoundMethod[] = "SystemSound.play";
2225
static constexpr char kSystemNavigatorPopMethod[] = "SystemNavigator.pop";
2326
static constexpr char kTextKey[] = "text";
2427
static constexpr char kValueKey[] = "value";
2528

29+
static constexpr char kExitTypeKey[] = "type";
30+
static constexpr char kExitTypeCancelable[] = "cancelable";
31+
static constexpr char kExitTypeRequired[] = "required";
32+
33+
static constexpr char kExitResponseKey[] = "response";
34+
static constexpr char kExitResponseCancel[] = "cancel";
35+
static constexpr char kExitResponseExit[] = "exit";
36+
2637
static constexpr char kTextPlainFormat[] = "text/plain";
2738

2839
static constexpr char kSoundTypeAlert[] = "SystemSoundType.alert";
@@ -32,6 +43,8 @@ struct _FlPlatformPlugin {
3243
GObject parent_instance;
3344

3445
FlMethodChannel* channel;
46+
FlMethodCall* exit_application_method_call;
47+
GCancellable* cancellable;
3548
};
3649

3750
G_DEFINE_TYPE(FlPlatformPlugin, fl_platform_plugin, G_TYPE_OBJECT)
@@ -140,6 +153,124 @@ static FlMethodResponse* clipboard_has_strings_async(
140153
return nullptr;
141154
}
142155

156+
// Get the exit response from a System.requestAppExit method call.
157+
static gchar* get_exit_response(FlMethodResponse* response) {
158+
if (response == nullptr) {
159+
return nullptr;
160+
}
161+
162+
g_autoptr(GError) error = nullptr;
163+
g_autoptr(FlValue) result = fl_method_response_get_result(response, &error);
164+
if (result == nullptr) {
165+
g_warning("Error returned from System.requestAppExit: %s", error->message);
166+
return nullptr;
167+
}
168+
if (fl_value_get_type(result) != FL_VALUE_TYPE_MAP) {
169+
g_warning("System.requestAppExit result argument map missing or malformed");
170+
return nullptr;
171+
}
172+
173+
FlValue* response_value = fl_value_lookup_string(result, kExitResponseKey);
174+
if (fl_value_get_type(response_value) != FL_VALUE_TYPE_STRING) {
175+
g_warning("Invalid response from System.requestAppExit");
176+
return nullptr;
177+
}
178+
return g_strdup(fl_value_get_string(response_value));
179+
}
180+
181+
// Handle response of System.requestAppExit.
182+
static void request_app_exit_response_cb(GObject* object,
183+
GAsyncResult* result,
184+
gpointer user_data) {
185+
FlPlatformPlugin* self = FL_PLATFORM_PLUGIN(user_data);
186+
187+
g_autoptr(GError) error = nullptr;
188+
g_autoptr(FlMethodResponse) method_response =
189+
fl_method_channel_invoke_method_finish(FL_METHOD_CHANNEL(object), result,
190+
&error);
191+
g_autofree gchar* exit_response = nullptr;
192+
if (method_response == nullptr) {
193+
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
194+
return;
195+
}
196+
g_warning("Failed to complete System.requestAppExit: %s", error->message);
197+
} else {
198+
exit_response = get_exit_response(method_response);
199+
}
200+
if (exit_response == nullptr) {
201+
exit_response = g_strdup(kExitResponseCancel);
202+
}
203+
204+
if (g_str_equal(exit_response, kExitResponseExit)) {
205+
GApplication* app = g_application_get_default();
206+
if (app == nullptr) {
207+
g_warning("Unable to quit, no GApplication");
208+
} else {
209+
g_application_quit(app);
210+
}
211+
} else if (g_str_equal(exit_response, kExitResponseCancel)) {
212+
// Canceled - no action to take.
213+
}
214+
215+
// If request was due to a request from Flutter, pass result back.
216+
if (self->exit_application_method_call != nullptr) {
217+
g_autoptr(FlValue) exit_result = fl_value_new_map();
218+
fl_value_set_string_take(exit_result, kExitResponseKey,
219+
fl_value_new_string(exit_response));
220+
g_autoptr(FlMethodResponse) exit_response =
221+
FL_METHOD_RESPONSE(fl_method_success_response_new(exit_result));
222+
if (!fl_method_call_respond(self->exit_application_method_call,
223+
exit_response, &error)) {
224+
g_warning("Failed to send response to System.exitApplication: %s",
225+
error->message);
226+
}
227+
g_clear_object(&self->exit_application_method_call);
228+
}
229+
}
230+
231+
// Send a request to Flutter to exit the application.
232+
static void request_app_exit(FlPlatformPlugin* self, const char* type) {
233+
g_autoptr(FlValue) args = fl_value_new_map();
234+
fl_value_set_string_take(args, kExitTypeKey, fl_value_new_string(type));
235+
fl_method_channel_invoke_method(self->channel, kRequestAppExitMethod, args,
236+
self->cancellable,
237+
request_app_exit_response_cb, self);
238+
}
239+
240+
// Called when Flutter wants to exit the application.
241+
static FlMethodResponse* system_exit_application(FlPlatformPlugin* self,
242+
FlMethodCall* method_call) {
243+
FlValue* args = fl_method_call_get_args(method_call);
244+
if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) {
245+
return FL_METHOD_RESPONSE(fl_method_error_response_new(
246+
kBadArgumentsError, "Argument map missing or malformed", nullptr));
247+
}
248+
249+
FlValue* type_value = fl_value_lookup_string(args, kExitTypeKey);
250+
if (type_value == nullptr ||
251+
fl_value_get_type(type_value) != FL_VALUE_TYPE_STRING) {
252+
return FL_METHOD_RESPONSE(fl_method_error_response_new(
253+
kBadArgumentsError, "Missing type argument", nullptr));
254+
}
255+
const char* type = fl_value_get_string(type_value);
256+
257+
// Save method call to respond to when our request to Flutter completes.
258+
if (self->exit_application_method_call != nullptr) {
259+
return FL_METHOD_RESPONSE(fl_method_error_response_new(
260+
kInProgressError, "Request already in progress", nullptr));
261+
}
262+
self->exit_application_method_call =
263+
FL_METHOD_CALL(g_object_ref(method_call));
264+
265+
// Send the request back to Flutter to follow the standard process.
266+
request_app_exit(self, g_str_equal(type, kExitTypeCancelable)
267+
? kExitTypeCancelable
268+
: kExitTypeRequired);
269+
270+
// Will respond later.
271+
return nullptr;
272+
}
273+
143274
// Called when Flutter wants to play a sound.
144275
static FlMethodResponse* system_sound_play(FlPlatformPlugin* self,
145276
FlValue* args) {
@@ -192,6 +323,8 @@ static void method_call_cb(FlMethodChannel* channel,
192323
response = clipboard_get_data_async(self, method_call);
193324
} else if (strcmp(method, kClipboardHasStringsMethod) == 0) {
194325
response = clipboard_has_strings_async(self, method_call);
326+
} else if (strcmp(method, kExitApplicationMethod) == 0) {
327+
response = system_exit_application(self, method_call);
195328
} else if (strcmp(method, kPlaySoundMethod) == 0) {
196329
response = system_sound_play(self, args);
197330
} else if (strcmp(method, kSystemNavigatorPopMethod) == 0) {
@@ -208,7 +341,11 @@ static void method_call_cb(FlMethodChannel* channel,
208341
static void fl_platform_plugin_dispose(GObject* object) {
209342
FlPlatformPlugin* self = FL_PLATFORM_PLUGIN(object);
210343

344+
g_cancellable_cancel(self->cancellable);
345+
211346
g_clear_object(&self->channel);
347+
g_clear_object(&self->exit_application_method_call);
348+
g_clear_object(&self->cancellable);
212349

213350
G_OBJECT_CLASS(fl_platform_plugin_parent_class)->dispose(object);
214351
}
@@ -217,7 +354,9 @@ static void fl_platform_plugin_class_init(FlPlatformPluginClass* klass) {
217354
G_OBJECT_CLASS(klass)->dispose = fl_platform_plugin_dispose;
218355
}
219356

220-
static void fl_platform_plugin_init(FlPlatformPlugin* self) {}
357+
static void fl_platform_plugin_init(FlPlatformPlugin* self) {
358+
self->cancellable = g_cancellable_new();
359+
}
221360

222361
FlPlatformPlugin* fl_platform_plugin_new(FlBinaryMessenger* messenger) {
223362
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
@@ -233,3 +372,9 @@ FlPlatformPlugin* fl_platform_plugin_new(FlBinaryMessenger* messenger) {
233372

234373
return self;
235374
}
375+
376+
void fl_platform_plugin_request_app_exit(FlPlatformPlugin* self) {
377+
g_return_if_fail(FL_IS_PLATFORM_PLUGIN(self));
378+
// Request a cancellable exit.
379+
request_app_exit(self, kExitTypeCancelable);
380+
}

shell/platform/linux/fl_platform_plugin.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ G_DECLARE_FINAL_TYPE(FlPlatformPlugin,
3333
*/
3434
FlPlatformPlugin* fl_platform_plugin_new(FlBinaryMessenger* messenger);
3535

36+
/**
37+
* fl_platform_plugin_request_app_exit:
38+
* @plugin: an #FlPlatformPlugin
39+
*
40+
* Request the application exits (i.e. due to the window being requested to be
41+
* closed).
42+
*/
43+
void fl_platform_plugin_request_app_exit(FlPlatformPlugin* plugin);
44+
3645
G_END_DECLS
3746

3847
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_PLATFORM_PLUGIN_H_

shell/platform/linux/fl_view.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ G_DEFINE_TYPE_WITH_CODE(
9090
G_IMPLEMENT_INTERFACE(fl_text_input_view_delegate_get_type(),
9191
fl_view_text_input_delegate_iface_init))
9292

93+
// Signal handler for GtkWidget::delete-event
94+
static gboolean window_delete_event_cb(GtkWidget* widget, FlView* self) {
95+
fl_platform_plugin_request_app_exit(self->platform_plugin);
96+
// Stop the event from propagating.
97+
return FALSE;
98+
}
99+
93100
// Initialize keyboard manager.
94101
static void init_keyboard(FlView* self) {
95102
FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(self->engine);
@@ -623,6 +630,12 @@ static void fl_view_realize(GtkWidget* widget) {
623630
gtk_widget_set_window(widget, window);
624631
gtk_widget_register_window(widget, window);
625632

633+
// Handle requests by the user to close the application.
634+
GdkWindow* toplevel_window =
635+
gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(self)));
636+
g_signal_connect(toplevel_window, "delete-event",
637+
G_CALLBACK(window_delete_event_cb), self);
638+
626639
init_keyboard(self);
627640

628641
if (!fl_renderer_start(self->renderer, self, &error)) {

0 commit comments

Comments
 (0)