diff --git a/lib/sdk/widgets/window_close_handler.dart b/lib/sdk/widgets/window_close_handler.dart index 8623ae57bb..10b96594b9 100644 --- a/lib/sdk/widgets/window_close_handler.dart +++ b/lib/sdk/widgets/window_close_handler.dart @@ -1,3 +1,6 @@ +import 'dart:io' show exit + if (dart.library.html) 'window_close_handler_exit_stub.dart' show exit; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -15,6 +18,7 @@ import 'package:web_dex/shared/utils/window/window.dart'; /// /// This widget uses different strategies based on the platform: /// - Desktop (Windows, macOS, Linux): Uses flutter_window_close for native window close handling +/// On Linux, native code uses workaround to bypass GTK cleanup to prevent crashes /// - Web: Uses showMessageBeforeUnload for browser beforeunload event /// - Mobile (iOS, Android): Uses WidgetsBindingObserver for lifecycle management /// and PopScope for exit confirmation @@ -47,9 +51,25 @@ class _WindowCloseHandlerState extends State /// Sets up the appropriate close handler based on the platform. void _setupCloseHandler() { if (PlatformTuner.isNativeDesktop) { - // Desktop platforms: Use flutter_window_close + // Desktop platforms: Use flutter_window_close for all platforms + // On Linux, we use flutter_window_close for dialog, but return false to prevent + // standard window closing, then manually trigger exit via SystemNavigator FlutterWindowClose.setWindowShouldCloseHandler(() async { - return await _handleWindowClose(); + final shouldClose = await _handleWindowClose(); + + // On Linux, if user confirmed, we need to manually exit instead of letting + // flutter_window_close handle it, to avoid GTK cleanup issues + if (!kIsWeb && defaultTargetPlatform == TargetPlatform.linux && shouldClose) { + // Hide window immediately + // Then exit after a short delay to allow any final cleanup + Future.delayed(const Duration(milliseconds: 200), () { + exit(0); + }); + // Return false to prevent flutter_window_close from closing the window + return false; + } + + return shouldClose; }); } else if (kIsWeb) { // Web platform: Use beforeunload event @@ -66,7 +86,8 @@ class _WindowCloseHandlerState extends State void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); - // Dispose SDK when app is terminated or detached from UI (mobile platforms) + // Dispose SDK when app is terminated or detached from UI + // This applies to mobile platforms if (state == AppLifecycleState.detached) { _disposeSDKIfNeeded(); } @@ -151,6 +172,7 @@ class _WindowCloseHandlerState extends State void dispose() { // Clean up based on platform if (PlatformTuner.isNativeDesktop) { + // Desktop platforms: Remove flutter_window_close handler FlutterWindowClose.setWindowShouldCloseHandler(null); } else if (!kIsWeb) { // Mobile platforms: Remove lifecycle observer diff --git a/lib/sdk/widgets/window_close_handler_exit_stub.dart b/lib/sdk/widgets/window_close_handler_exit_stub.dart new file mode 100644 index 0000000000..abdc980178 --- /dev/null +++ b/lib/sdk/widgets/window_close_handler_exit_stub.dart @@ -0,0 +1,7 @@ +// Stub file for web platform - exit is not available on web +void exit(int code) { + // On web, we can't exit the process + // This should never be called as we check kIsWeb before using exit + throw UnsupportedError('exit() is not available on web platform'); +} + diff --git a/linux/my_application.cc b/linux/my_application.cc index be94af558c..b572d3c713 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -1,5 +1,6 @@ #include "my_application.h" +#include #include #ifdef GDK_WINDOWING_X11 #include @@ -27,6 +28,23 @@ static void on_window_destroy(GtkWidget* widget, gpointer user_data) { self->main_window = nullptr; } +// Workaround for Flutter Linux shutdown issue (#132404) +// This handler intercepts delete-event early to prevent GTK from destroying the window +// and triggering FlutterEngineRemoveView. When flutter_window_close confirms closing, +// it will return false to prevent standard closing, and we'll handle exit manually. +// This handler acts as a safety net in case delete-event is still triggered. +static gboolean on_window_delete_event(GtkWidget* widget, GdkEvent* event, gpointer user_data) { + (void)widget; // Unused + (void)event; // Unused + (void)user_data; // Unused + + // On Linux, we want flutter_window_close to handle the dialog first + // So we return FALSE to let the event propagate to flutter_window_close + // If flutter_window_close returns false (user cancelled), nothing happens + // If flutter_window_close returns true, it will be intercepted and we'll exit manually + return FALSE; +} + // Implements GApplication::activate. static void my_application_activate(GApplication* application) { MyApplication* self = MY_APPLICATION(application); @@ -89,7 +107,12 @@ static void my_application_activate(GApplication* application) { gtk_widget_grab_focus(GTK_WIDGET(view)); self->main_window = window; + + // Connect destroy signal g_signal_connect(window, "destroy", G_CALLBACK(on_window_destroy), self); + // Connect delete-event signal for graceful shutdown workaround + // This prevents the crash when closing the window on Linux + g_signal_connect(window, "delete-event", G_CALLBACK(on_window_delete_event), self); } // Implements GApplication::local_command_line.