Skip to content

Commit

Permalink
Prevents closing window only when the SDK is initialized.
Browse files Browse the repository at this point in the history
This is on Linux.
  • Loading branch information
zonble committed Nov 25, 2023
1 parent 4a554c1 commit 372ceec
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 117 deletions.
1 change: 0 additions & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_platform_alert/flutter_platform_alert.dart';
import 'package:flutter_window_close/flutter_window_close.dart';
// import 'package:menubar/menubar.dart';

void main() {
runApp(const MyApp());
Expand Down
70 changes: 41 additions & 29 deletions lib/flutter_window_close.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,45 @@ import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';

/// A plug-in that helps your Flutter desktop app to handle window close event.
///
/// It supports Windows, macOS and Linux.
class FlutterWindowClose {
FlutterWindowClose._();

static Future<bool> Function()? _onWindowShoudClose;
static Future<bool> Function()? _onWindowShouldClose;
static MethodChannel? _notificationChannel;
static const MethodChannel _channel = MethodChannel('flutter_window_close');

static Future<void> _initIfRequired() async {
if (_notificationChannel != null) {
return;
}

WidgetsFlutterBinding.ensureInitialized();
await _channel.invokeMethod('init');

var channel = const MethodChannel('flutter_window_close_notification');
channel.setMethodCallHandler((call) async {
if (call.method == 'onWindowClose') {
final handler = FlutterWindowClose._onWindowShouldClose;

// Note: the 'destroyWindow' method just close the window without
// any confirming.
if (handler != null) {
final result = await handler();
if (result) _channel.invokeMethod('destroyWindow');
} else {
_channel.invokeMethod('destroyWindow');
}
}
return null;
});
_notificationChannel = _channel;
}

/// Sets a function to handle window close events.
///
/// When a user click on the close button on a window, the plug-in redirects
Expand Down Expand Up @@ -47,29 +75,11 @@ class FlutterWindowClose {
/// ```
///
/// The method does not support Flutter Web.
static void setWindowShouldCloseHandler(Future<bool> Function()? handler) {
static Future<void> setWindowShouldCloseHandler(
Future<bool> Function()? handler) async {
if (kIsWeb) throw Exception('The method does not work in Flutter Web.');

_onWindowShoudClose = handler;
if (_notificationChannel == null) {
var channel = const MethodChannel('flutter_window_close_notification');
channel.setMethodCallHandler((call) async {
if (call.method == 'onWindowClose') {
final handler = FlutterWindowClose._onWindowShoudClose;

// Note: the 'destroyWindow' method just close the window without
// any confirming.
if (handler != null) {
final result = await handler();
if (result) _channel.invokeMethod('destroyWindow');
} else {
_channel.invokeMethod('destroyWindow');
}
}
return null;
});
_notificationChannel = _channel;
}
_onWindowShouldClose = handler;
await _initIfRequired();
}

/// Sends a message to close the window hosting your Flutter app.
Expand All @@ -79,20 +89,22 @@ class FlutterWindowClose {
/// - On macOS, it calls [-\[NSWindow performClose:\]](https://developer.apple.com/documentation/appkit/nswindow/1419288-performclose?language=objc)
/// - On Linux, it calls [gtk_window_close](https://gnome.pages.gitlab.gnome.org/gtk/gtk4/method.Window.close.html)
/// - The method does not support Flutter Web.
static void closeWindow() {
static Future<void> closeWindow() async {
if (kIsWeb) throw Exception('The method does not work in Flutter Web.');
_channel.invokeMethod('closeWindow');
await _initIfRequired();
await _channel.invokeMethod('closeWindow');
}

static void destroyWindow() {
static Future<void> destroyWindow() async {
if (kIsWeb) throw Exception('The method does not work in Flutter Web.');
_channel.invokeMethod('destroyWindow');
await _initIfRequired();
await _channel.invokeMethod('destroyWindow');
}

/// Sets a return value when the current window or tab is being closed
/// when your app is running in Flutter Web.
static void setWebReturnValue(String? returnValue) {
static Future<void> setWebReturnValue(String? returnValue) async {
if (!kIsWeb) throw Exception('The method only works in Flutter Web.');
_channel.invokeMethod('setWebReturnValue', returnValue);
await _channel.invokeMethod('setWebReturnValue', returnValue);
}
}
181 changes: 94 additions & 87 deletions linux/flutter_window_close_plugin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,110 +8,117 @@
#include <thread>

#define FLUTTER_WINDOW_CLOSE_PLUGIN(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj), flutter_window_close_plugin_get_type(), \
FlutterWindowClosePlugin))
(G_TYPE_CHECK_INSTANCE_CAST((obj), flutter_window_close_plugin_get_type(), \
FlutterWindowClosePlugin))

struct _FlutterWindowClosePlugin {
GObject parent_instance;
GtkWidget* widget;
GObject parent_instance;
GtkWidget *widget;
bool initialized;
};

G_DEFINE_TYPE(FlutterWindowClosePlugin, flutter_window_close_plugin, g_object_get_type())
G_DEFINE_TYPE(FlutterWindowClosePlugin, flutter_window_close_plugin,
g_object_get_type())

// Called when a method call is received from Flutter.
static void flutter_window_close_plugin_handle_method_call(
FlutterWindowClosePlugin* self,
FlMethodCall* method_call)
{

const gchar* method = fl_method_call_get_name(method_call);

if (strcmp(method, "closeWindow") == 0) {
g_autoptr(FlMethodResponse) response = nullptr;
gtk_window_close((GtkWindow*)self->widget);
response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
fl_method_call_respond(method_call, response, nullptr);
} else if (strcmp(method, "destroyWindow") == 0) {
g_autoptr(FlMethodResponse) response = nullptr;
response
= FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
fl_method_call_respond(method_call, response, nullptr);
std::thread([=]() {
g_signal_emit_by_name(G_OBJECT((GtkWindow*)self->widget), "destroy");
}).detach();
} else {
g_autoptr(FlMethodResponse) response = nullptr;
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
fl_method_call_respond(method_call, response, nullptr);
}
static void
flutter_window_close_plugin_handle_method_call(FlutterWindowClosePlugin *self,
FlMethodCall *method_call) {

const gchar *method = fl_method_call_get_name(method_call);

if (strcmp(method, "closeWindow") == 0) {
g_autoptr(FlMethodResponse) response = nullptr;
gtk_window_close((GtkWindow *)self->widget);
response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
fl_method_call_respond(method_call, response, nullptr);
} else if (strcmp(method, "destroyWindow") == 0) {
g_autoptr(FlMethodResponse) response = nullptr;
response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
fl_method_call_respond(method_call, response, nullptr);
std::thread([=]() {
g_signal_emit_by_name(G_OBJECT((GtkWindow *)self->widget), "destroy");
}).detach();
} else if (strcmp(method, "init") == 0) {
self->initialized = true;
g_autoptr(FlMethodResponse) response = nullptr;
response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
fl_method_call_respond(method_call, response, nullptr);
} else {
g_autoptr(FlMethodResponse) response = nullptr;
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
fl_method_call_respond(method_call, response, nullptr);
}
}

static void flutter_window_close_plugin_dispose(GObject* object)
{
G_OBJECT_CLASS(flutter_window_close_plugin_parent_class)->dispose(object);
static void flutter_window_close_plugin_dispose(GObject *object) {
G_OBJECT_CLASS(flutter_window_close_plugin_parent_class)->dispose(object);
}

static void flutter_window_close_plugin_class_init(FlutterWindowClosePluginClass* klass)
{
G_OBJECT_CLASS(klass)->dispose = flutter_window_close_plugin_dispose;
static void
flutter_window_close_plugin_class_init(FlutterWindowClosePluginClass *klass) {
G_OBJECT_CLASS(klass)->dispose = flutter_window_close_plugin_dispose;
}

static void flutter_window_close_plugin_init(FlutterWindowClosePlugin* self) { }
static void flutter_window_close_plugin_init(FlutterWindowClosePlugin *self) {}

static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call,
gpointer user_data)
{
FlutterWindowClosePlugin* plugin = FLUTTER_WINDOW_CLOSE_PLUGIN(user_data);
flutter_window_close_plugin_handle_method_call(plugin, method_call);
static void method_call_cb(FlMethodChannel *channel, FlMethodCall *method_call,
gpointer user_data) {
FlutterWindowClosePlugin *plugin = FLUTTER_WINDOW_CLOSE_PLUGIN(user_data);
flutter_window_close_plugin_handle_method_call(plugin, method_call);
}

static FlMethodChannel* notificationChannel;
static FlMethodChannel *notificationChannel;

static gboolean
main_window_close(GtkWidget* window, gpointer data)
{
FlValue* value = fl_value_new_null();
fl_method_channel_invoke_method(notificationChannel,
"onWindowClose",
value, NULL, NULL, NULL);
return TRUE;
static gboolean main_window_close(GtkWidget *window, GdkEvent *event,
gpointer user_data) {
FlutterWindowClosePlugin *plugin = FLUTTER_WINDOW_CLOSE_PLUGIN(user_data);
if (plugin->initialized == false) {
return FALSE;
}

FlValue *value = fl_value_new_null();
fl_method_channel_invoke_method(notificationChannel, "onWindowClose", value,
NULL, NULL, NULL);
return TRUE;
}

void flutter_window_close_plugin_register_with_registrar(FlPluginRegistrar* registrar)
{
GtkWidget* window = gtk_widget_get_ancestor((GtkWidget*)fl_plugin_registrar_get_view(registrar), GTK_TYPE_WINDOW);

// See
// https://github.com/leanflutter/window_manager/blob/main/linux/window_manager_plugin.cc
//
// Disconnect all delete-event handlers first in flutter 3.10.1, which
// causes delete_event not working. Issues from flutter/engine:
// https://github.com/flutter/engine/pull/40033
guint handler_id = g_signal_handler_find(window, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
void flutter_window_close_plugin_register_with_registrar(
FlPluginRegistrar *registrar) {
FlutterWindowClosePlugin *plugin = FLUTTER_WINDOW_CLOSE_PLUGIN(
g_object_new(flutter_window_close_plugin_get_type(), nullptr));
GtkWidget *window = gtk_widget_get_ancestor(
(GtkWidget *)fl_plugin_registrar_get_view(registrar), GTK_TYPE_WINDOW);

plugin->widget = window;

// See
// https://github.com/leanflutter/window_manager/blob/main/linux/window_manager_plugin.cc
//
// Disconnect all delete-event handlers first in flutter 3.10.1, which
// causes delete_event not working. Issues from flutter/engine:
// https://github.com/flutter/engine/pull/40033
guint handler_id =
g_signal_handler_find(window, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
fl_plugin_registrar_get_view(registrar));
if (handler_id > 0) {
g_signal_handler_disconnect(window, handler_id);
}

g_signal_connect(G_OBJECT(window), "delete_event",
G_CALLBACK(main_window_close),
NULL);

FlutterWindowClosePlugin* plugin = FLUTTER_WINDOW_CLOSE_PLUGIN(
g_object_new(flutter_window_close_plugin_get_type(), nullptr));
plugin->widget = window;

g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
g_autoptr(FlMethodChannel) channel = fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
"flutter_window_close",
FL_METHOD_CODEC(codec));
fl_method_channel_set_method_call_handler(channel, method_call_cb,
g_object_ref(plugin),
g_object_unref);

g_object_unref(plugin);

notificationChannel = fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
"flutter_window_close_notification",
FL_METHOD_CODEC(codec));
if (handler_id > 0) {
g_signal_handler_disconnect(window, handler_id);
}

g_signal_connect(G_OBJECT(window), "delete_event",
G_CALLBACK(main_window_close), g_object_ref(plugin));

g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
g_autoptr(FlMethodChannel) channel =
fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
"flutter_window_close", FL_METHOD_CODEC(codec));

fl_method_channel_set_method_call_handler(
channel, method_call_cb, g_object_ref(plugin), g_object_unref);

g_object_unref(plugin);

notificationChannel = fl_method_channel_new(
fl_plugin_registrar_get_messenger(registrar),
"flutter_window_close_notification", FL_METHOD_CODEC(codec));
}

0 comments on commit 372ceec

Please sign in to comment.