MixinNetwork / MixinNetwork/flutter-plugins
[desktop_multi_window]Linux: closing a child window can crash the app
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 512
- Forks
- 292
- Avg merge
- 15h 8m
- Merged PRs (30d)
- 7
Description
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 windowwindowManager.destroy()requested by the host window- Clicking the native title-bar
Xbutton
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.yamlwith the content below. - Replace
lib/main.dartwith 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.6stable - 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.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the crash with the supplied Flutter project and Linux runner patch, starting at linux/runner/my_application.cc and the desktop_multi_window child-engine registration callback. Trace child-window cleanup around the implicit Flutter view and compare the close(), destroy(), and native X paths. Done means all three paths close the child, release its resources, and leave the host running without implicit-view, GTK/GLX, or segmentation-fault errors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, dart, linux
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100