Skip to content

Commit 35b8aca

Browse files
Implement dialog windows for the Linux platform (flutter#177817)
1 parent 93b4de5 commit 35b8aca

File tree

2 files changed

+222
-13
lines changed

2 files changed

+222
-13
lines changed

dev/integration_tests/windowing_test/test_driver/main_test.dart

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,16 +122,11 @@ void main() {
122122
onPlatform: {'linux': Skip('isMinimized is not supported on Wayland')},
123123
);
124124

125-
test(
126-
'Can open dialog',
127-
() async {
128-
await driver.requestData(jsonEncode({'type': 'open_dialog'}));
129-
await driver.waitFor(find.byValueKey('close_dialog'));
130-
await driver.requestData(jsonEncode({'type': 'close_dialog'}));
131-
},
132-
timeout: Timeout.none,
133-
onPlatform: {'linux': Skip('Dialogs are not yet supported on Wayland')},
134-
);
125+
test('Can open dialog', () async {
126+
await driver.requestData(jsonEncode({'type': 'open_dialog'}));
127+
await driver.waitFor(find.byValueKey('close_dialog'));
128+
await driver.requestData(jsonEncode({'type': 'close_dialog'}));
129+
}, timeout: Timeout.none);
135130

136131
test(
137132
'Can set constraints and see the resize',

packages/flutter/lib/src/widgets/_window_linux.dart

Lines changed: 217 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ const int _GDK_WINDOW_STATE_ICONIFIED = 1 << 1;
188188
const int _GDK_WINDOW_STATE_MAXIMIZED = 1 << 2;
189189
const int _GDK_WINDOW_STATE_FULLSCREEN = 1 << 4;
190190

191+
const int _GDK_WINDOW_TYPE_HINT_DIALOG = 1;
192+
191193
/// Wraps GtkWindow
192194
class _GtkWindow extends _GtkContainer {
193195
/// Create a new GtkWindow
@@ -198,6 +200,21 @@ class _GtkWindow extends _GtkContainer {
198200
_gtkWindowPresent(instance);
199201
}
200202

203+
/// Sets the parent window.
204+
void setTransientFor(_GtkWindow parent) {
205+
_gtkWindowSetTransientFor(instance, parent.instance);
206+
}
207+
208+
/// Set if this window is modal to its parent.
209+
void setModal(bool modal) {
210+
_gtkWindowSetModal(instance, modal);
211+
}
212+
213+
/// Set the type of this window.
214+
void setTypeHint(int hint) {
215+
_gtkWindowSetTypeHint(instance, hint);
216+
}
217+
201218
/// Sets the title of the window.
202219
void setTitle(String title) {
203220
final ffi.Pointer<ffi.Uint8> titleBuffer = _stringToNative(title);
@@ -293,6 +310,24 @@ class _GtkWindow extends _GtkContainer {
293310
@ffi.Native<ffi.Void Function(ffi.Pointer<ffi.NativeType>)>(symbol: 'gtk_window_present')
294311
external static void _gtkWindowPresent(ffi.Pointer<ffi.NativeType> window);
295312

313+
@ffi.Native<ffi.Void Function(ffi.Pointer<ffi.NativeType>, ffi.Bool)>(
314+
symbol: 'gtk_window_set_modal',
315+
)
316+
external static void _gtkWindowSetModal(ffi.Pointer<ffi.NativeType> window, bool modal);
317+
318+
@ffi.Native<ffi.Void Function(ffi.Pointer<ffi.NativeType>, ffi.Int)>(
319+
symbol: 'gtk_window_set_type_hint',
320+
)
321+
external static void _gtkWindowSetTypeHint(ffi.Pointer<ffi.NativeType> window, int hint);
322+
323+
@ffi.Native<ffi.Void Function(ffi.Pointer<ffi.NativeType>, ffi.Pointer<ffi.NativeType>)>(
324+
symbol: 'gtk_window_set_transient_for',
325+
)
326+
external static void _gtkWindowSetTransientFor(
327+
ffi.Pointer<ffi.NativeType> window,
328+
ffi.Pointer<ffi.NativeType> parent,
329+
);
330+
296331
@ffi.Native<ffi.Void Function(ffi.Pointer<ffi.NativeType>, ffi.Pointer<ffi.Uint8>)>(
297332
symbol: 'gtk_window_set_title',
298333
)
@@ -512,6 +547,9 @@ class WindowingOwnerLinux extends WindowingOwner {
512547
);
513548
}
514549

550+
/// GTK windows keyed by view ID.
551+
final Map<int, _GtkWindow> _windows = <int, _GtkWindow>{};
552+
515553
@internal
516554
@override
517555
RegularWindowController createRegularWindowController({
@@ -520,12 +558,15 @@ class WindowingOwnerLinux extends WindowingOwner {
520558
String? title,
521559
required RegularWindowControllerDelegate delegate,
522560
}) {
523-
return RegularWindowControllerLinux(
561+
final RegularWindowControllerLinux controller = RegularWindowControllerLinux(
562+
owner: this,
524563
delegate: delegate,
525564
preferredSize: preferredSize,
526565
preferredConstraints: preferredConstraints,
527566
title: title,
528567
);
568+
_windows[controller.rootView.viewId] = controller._window;
569+
return controller;
529570
}
530571

531572
@internal
@@ -537,7 +578,16 @@ class WindowingOwnerLinux extends WindowingOwner {
537578
BaseWindowController? parent,
538579
String? title,
539580
}) {
540-
throw UnimplementedError('Dialog windows are not yet implemented on Linux.');
581+
final DialogWindowControllerLinux controller = DialogWindowControllerLinux(
582+
owner: this,
583+
delegate: delegate,
584+
preferredSize: preferredSize,
585+
preferredConstraints: preferredConstraints,
586+
parent: parent,
587+
title: title,
588+
);
589+
_windows[controller.rootView.viewId] = controller._window;
590+
return controller;
541591
}
542592
}
543593

@@ -561,11 +611,13 @@ class RegularWindowControllerLinux extends RegularWindowController {
561611
/// * [RegularWindowController], the base class for regular windows.
562612
@internal
563613
RegularWindowControllerLinux({
614+
required WindowingOwnerLinux owner,
564615
required RegularWindowControllerDelegate delegate,
565616
Size? preferredSize,
566617
BoxConstraints? preferredConstraints,
567618
String? title,
568-
}) : _delegate = delegate,
619+
}) : _owner = owner,
620+
_delegate = delegate,
569621
_window = _GtkWindow(),
570622
super.empty() {
571623
if (!isWindowingEnabled) {
@@ -608,6 +660,7 @@ class RegularWindowControllerLinux extends RegularWindowController {
608660
_window.present();
609661
}
610662

663+
final WindowingOwnerLinux _owner;
611664
final RegularWindowControllerDelegate _delegate;
612665
final _GtkWindow _window;
613666
late final _FlWindowMonitor _windowMonitor;
@@ -626,6 +679,7 @@ class RegularWindowControllerLinux extends RegularWindowController {
626679
_windowMonitor.close();
627680
_windowMonitor.unref();
628681
_destroyed = true;
682+
_owner._windows.remove(rootView.viewId);
629683
}
630684

631685
@override
@@ -709,3 +763,163 @@ class RegularWindowControllerLinux extends RegularWindowController {
709763
}
710764
}
711765
}
766+
767+
/// Implementation of [DialogWindowController] for the Linux platform.
768+
///
769+
/// {@macro flutter.widgets.windowing.experimental}
770+
///
771+
/// See also:
772+
///
773+
/// * [DialogWindowController], the base class for dialog windows.
774+
class DialogWindowControllerLinux extends DialogWindowController {
775+
/// Creates a new dialog window controller for Linux.
776+
///
777+
/// When this constructor completes the native window has been created and
778+
/// has a view associated with it.
779+
///
780+
/// {@macro flutter.widgets.windowing.experimental}
781+
///
782+
/// See also:
783+
///
784+
/// * [DialogWindowController], the base class for dialog windows.
785+
@internal
786+
DialogWindowControllerLinux({
787+
required WindowingOwnerLinux owner,
788+
required DialogWindowControllerDelegate delegate,
789+
Size? preferredSize,
790+
BoxConstraints? preferredConstraints,
791+
BaseWindowController? parent,
792+
String? title,
793+
}) : _owner = owner,
794+
_delegate = delegate,
795+
_parent = parent,
796+
_window = _GtkWindow(),
797+
super.empty() {
798+
if (!isWindowingEnabled) {
799+
throw UnsupportedError(_kWindowingDisabledErrorMessage);
800+
}
801+
802+
_window.setTypeHint(_GDK_WINDOW_TYPE_HINT_DIALOG);
803+
if (parent != null) {
804+
final _GtkWindow? parentWindow = owner._windows[parent.rootView.viewId];
805+
if (parentWindow != null) {
806+
_window.setTransientFor(parentWindow);
807+
_window.setModal(true);
808+
}
809+
}
810+
811+
_windowMonitor = _FlWindowMonitor(
812+
_window,
813+
// onConfigure
814+
notifyListeners,
815+
// onStateChanged
816+
notifyListeners,
817+
// onIsActiveNotify
818+
notifyListeners,
819+
// onTitleNotify
820+
notifyListeners,
821+
// onClose
822+
() {
823+
_delegate.onWindowCloseRequested(this);
824+
},
825+
// onDestroy
826+
_delegate.onWindowDestroyed,
827+
);
828+
if (preferredSize != null) {
829+
_window.setDefaultSize(preferredSize.width.toInt(), preferredSize.height.toInt());
830+
}
831+
if (preferredConstraints != null) {
832+
setConstraints(preferredConstraints);
833+
}
834+
if (title != null) {
835+
setTitle(title);
836+
}
837+
final _FlView view = _FlView();
838+
final int viewId = view.getId();
839+
rootView = WidgetsBinding.instance.platformDispatcher.views.firstWhere(
840+
(FlutterView view) => view.viewId == viewId,
841+
);
842+
view.show();
843+
_window.add(view);
844+
_window.present();
845+
}
846+
847+
final WindowingOwnerLinux _owner;
848+
final DialogWindowControllerDelegate _delegate;
849+
final _GtkWindow _window;
850+
final BaseWindowController? _parent;
851+
late final _FlWindowMonitor _windowMonitor;
852+
bool _destroyed = false;
853+
854+
@override
855+
@internal
856+
Size get contentSize => _window.getSize();
857+
858+
@override
859+
void destroy() {
860+
if (_destroyed) {
861+
return;
862+
}
863+
_window.destroy();
864+
_windowMonitor.close();
865+
_windowMonitor.unref();
866+
_destroyed = true;
867+
_owner._windows.remove(rootView.viewId);
868+
}
869+
870+
@override
871+
@internal
872+
BaseWindowController? get parent => _parent;
873+
874+
@override
875+
@internal
876+
String get title => _window.getTitle();
877+
878+
@override
879+
@internal
880+
bool get isActivated => _window.isActive();
881+
882+
@override
883+
@internal
884+
// NOTE: On Wayland this is never set, see https://gitlab.gnome.org/GNOME/gtk/-/issues/67
885+
bool get isMinimized => (_window.getWindow().getState() & _GDK_WINDOW_STATE_ICONIFIED) != 0;
886+
887+
@override
888+
@internal
889+
void setSize(Size size) {
890+
_window.resize(size.width.toInt(), size.height.toInt());
891+
}
892+
893+
@override
894+
@internal
895+
void setConstraints(BoxConstraints constraints) {
896+
_window.setGeometryHints(
897+
minWidth: constraints.minWidth.toInt(),
898+
minHeight: constraints.minHeight.toInt(),
899+
maxWidth: constraints.maxWidth.isInfinite ? 0x7fffffff : constraints.maxWidth.toInt(),
900+
maxHeight: constraints.maxHeight.isInfinite ? 0x7fffffff : constraints.maxHeight.toInt(),
901+
);
902+
}
903+
904+
@override
905+
@internal
906+
void setTitle(String title) {
907+
_window.setTitle(title);
908+
}
909+
910+
@override
911+
@internal
912+
void activate() {
913+
_window.present();
914+
}
915+
916+
@override
917+
@internal
918+
void setMinimized(bool minimized) {
919+
if (minimized) {
920+
_window.iconify();
921+
} else {
922+
_window.deiconify();
923+
}
924+
}
925+
}

0 commit comments

Comments
 (0)