Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions lib/sdk/widgets/window_close_handler.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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
Expand Down Expand Up @@ -47,9 +51,25 @@ class _WindowCloseHandlerState extends State<WindowCloseHandler>
/// 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
Expand All @@ -66,7 +86,8 @@ class _WindowCloseHandlerState extends State<WindowCloseHandler>
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();
}
Expand Down Expand Up @@ -151,6 +172,7 @@ class _WindowCloseHandlerState extends State<WindowCloseHandler>
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
Expand Down
7 changes: 7 additions & 0 deletions lib/sdk/widgets/window_close_handler_exit_stub.dart
Original file line number Diff line number Diff line change
@@ -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');
}

23 changes: 23 additions & 0 deletions linux/my_application.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "my_application.h"

#include <cstdlib>
#include <flutter_linux/flutter_linux.h>
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand Down
Loading