Linux: closing a desktop_multi_window child window can crash the app
On Linux/X11, all three tested child-window closing paths trigger an
application crash:
windowManager.close() requested by the host window
windowManager.destroy() requested by the host window
- Clicking the native title-bar
X button
The three paths trigger the same class of failure during child-window cleanup.
Reproduce Steps
flutter create --platforms=linux desktop_multi_window_crash
cd desktop_multi_window_crash
- Replace
pubspec.yaml with the content below.
- Replace
lib/main.dart with the content below.
- Apply the Linux runner patch below.
- Run the application:
flutter pub get
flutter build linux --debug
GDK_SYNCHRONIZE=1 \
build/linux/x64/debug/bundle/desktop_multi_window_crash
The host window provides three buttons. Each button creates one child window
with a different argument. Run one test at a time and close the previous child
window before starting another test.
- Automatic close: the host requests
windowManager.close() after three
seconds, then the process crashes during cleanup.
- Automatic destroy: the host requests
windowManager.destroy() after
three seconds, then the process crashes during cleanup.
- Manual X close: no automatic operation is performed. Click the native
X; the process then crashes during cleanup.
pubspec.yaml
name: desktop_multi_window_crash
description: Reproduction for a Linux child-window close crash.
publish_to: none
version: 1.0.0+1
environment:
sdk: ^3.12.2
dependencies:
flutter:
sdk: flutter
desktop_multi_window: 0.3.0
window_manager: 0.5.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
flutter:
uses-material-design: true
lib/main.dart
The argument selects the child-window behavior. The child content updates
every 16 ms so that the Flutter view remains active while the native window is
being closed.
import 'dart:async';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:window_manager/window_manager.dart';
const manualChildArgument = 'child-manual-close';
const automaticCloseChildArgument = 'child-auto-close';
const automaticDestroyChildArgument = 'child-auto-destroy';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final controller = await WindowController.fromCurrentEngine();
final argument = controller.arguments;
if (argument == manualChildArgument ||
argument == automaticCloseChildArgument ||
argument == automaticDestroyChildArgument) {
await windowManager.ensureInitialized();
final currentWindow = await WindowController.fromCurrentEngine();
await currentWindow.setWindowMethodHandler((call) async {
switch (call.method) {
case 'window_close':
await windowManager.close();
return null;
case 'window_destroy':
await windowManager.destroy();
return null;
default:
throw MissingPluginException('Not implemented: ${call.method}');
}
});
runApp(ChildApp(mode: argument));
return;
}
runApp(const HostApp());
}
class HostApp extends StatelessWidget {
const HostApp({super.key});
Future<void> _openChild(String argument) async {
await Future<void>.delayed(const Duration(milliseconds: 500));
final controller = await WindowController.create(
WindowConfiguration(arguments: argument, hiddenAtLaunch: false),
);
await controller.show();
if (argument == automaticCloseChildArgument) {
await Future<void>.delayed(const Duration(seconds: 3));
await controller.invokeMethod('window_close');
} else if (argument == automaticDestroyChildArgument) {
await Future<void>.delayed(const Duration(seconds: 3));
await controller.invokeMethod('window_destroy');
}
}
@override
Widget build(BuildContext context) => MaterialApp(
home: Scaffold(
body: Center(
child: Wrap(
spacing: 12,
children: [
ElevatedButton(
onPressed: () => unawaited(_openChild(automaticCloseChildArgument)),
child: const Text('Automatic close'),
),
ElevatedButton(
onPressed: () => unawaited(_openChild(automaticDestroyChildArgument)),
child: const Text('Automatic destroy'),
),
ElevatedButton(
onPressed: () => unawaited(_openChild(manualChildArgument)),
child: const Text('Manual X close'),
),
],
),
),
),
);
}
class ChildApp extends StatelessWidget {
const ChildApp({required this.mode, super.key});
final String mode;
@override
Widget build(BuildContext context) => MaterialApp(
home: ChildSession(mode: widget.mode),
);
}
class ChildSession extends StatefulWidget {
const ChildSession({required this.mode, super.key});
final String mode;
@override
State<ChildSession> createState() => _ChildSessionState();
}
class _ChildSessionState extends State<ChildSession> {
Timer? timer;
int frame = 0;
@override
void initState() {
super.initState();
timer = Timer.periodic(const Duration(milliseconds: 16), (_) {
if (mounted) setState(() => frame++);
});
}
@override
void dispose() {
timer?.cancel();
debugPrint('Child session disposed');
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ColoredBox(
color: Colors.primaries[frame % Colors.primaries.length],
child: SizedBox(
width: 240,
height: 120,
child: Center(
child: Text(switch (mode) {
automaticCloseChildArgument => 'Automatic close test',
automaticDestroyChildArgument => 'Automatic destroy test',
_ => 'Manual X close test',
}),
),
),
),
),
);
}
}
Linux runner patch
Register all generated plugins for each child Flutter engine:
diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc
@@
#include "flutter/generated_plugin_registrant.h"
+#include "desktop_multi_window/desktop_multi_window_plugin.h"
@@
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
+ desktop_multi_window_plugin_set_window_created_callback(
+ [](FlPluginRegistry* registry) {
+ // Register plugins for the child Flutter engine.
+ fl_register_plugins(registry);
+ });
This follows the plugin registration pattern documented by
desktop_multi_window.
Observed result
All three paths can produce the following error before the process crashes:
The implicit view cannot be removed.
Depending on GTK, GLX, and window-manager timing, the process may also report:
BadAccess (attempt to access private resource denied)
or invalid FlView/GtkWidget pointer warnings, followed by a segmentation
fault.
Expected behavior
Closing any child window should release that child window's resources and
leave the host application running. No implicit-view error, GTK/GLX error, or
segmentation fault should occur.
Version (please complete the following information):
- Flutter Version:
3.44.6 stable
- Dart Version:
3.12.2
- OS: Ubuntu/Linux with X11
- Plugin:
desktop_multi_window: 0.3.0
- Related plugin:
window_manager: 0.5.2
Additional context
desktop_multi_window creates a child window with its own Flutter engine and
an implicit Flutter view. During child-window close, Flutter tries to remove
that implicit view, but the engine rejects the operation with
kInvalidArguments. Native GTK/GLX cleanup then continues with an invalid or
already-invalid view state. The final error depends on timing, so it may be a
GLX error or a segmentation fault.
The three paths have the same observed failure class. This reproduction does
not show a reliable safety difference between close(), destroy(), and the
native X button.
Linux: closing a
desktop_multi_windowchild window can crash the appOn Linux/X11, all three tested child-window closing paths trigger an
application crash:
windowManager.close()requested by the host windowwindowManager.destroy()requested by the host windowXbuttonThe three paths trigger the same class of failure during child-window cleanup.
Reproduce Steps
flutter create --platforms=linux desktop_multi_window_crash cd desktop_multi_window_crashpubspec.yamlwith the content below.lib/main.dartwith the content below.The host window provides three buttons. Each button creates one child window
with a different argument. Run one test at a time and close the previous child
window before starting another test.
windowManager.close()after threeseconds, then the process crashes during cleanup.
windowManager.destroy()afterthree seconds, then the process crashes during cleanup.
X; the process then crashes during cleanup.pubspec.yamllib/main.dartThe argument selects the child-window behavior. The child content updates
every 16 ms so that the Flutter view remains active while the native window is
being closed.
Linux runner patch
Register all generated plugins for each child Flutter engine:
This follows the plugin registration pattern documented by
desktop_multi_window.Observed result
All three paths can produce the following error before the process crashes:
Depending on GTK, GLX, and window-manager timing, the process may also report:
or invalid
FlView/GtkWidgetpointer warnings, followed by a segmentationfault.
Expected behavior
Closing any child window should release that child window's resources and
leave the host application running. No implicit-view error, GTK/GLX error, or
segmentation fault should occur.
Version (please complete the following information):
3.44.6stable3.12.2desktop_multi_window: 0.3.0window_manager: 0.5.2Additional context
desktop_multi_windowcreates a child window with its own Flutter engine andan implicit Flutter view. During child-window close, Flutter tries to remove
that implicit view, but the engine rejects the operation with
kInvalidArguments. Native GTK/GLX cleanup then continues with an invalid oralready-invalid view state. The final error depends on timing, so it may be a
GLX error or a segmentation fault.
The three paths have the same observed failure class. This reproduction does
not show a reliable safety difference between
close(),destroy(), and thenative
Xbutton.