Skip to content

Commit 22ddf4a

Browse files
committed
Add pre-show and pre-hide window hooks
Introduces setWillShowHook and setWillHideHook to WindowManager, allowing Dart code to execute before native windows are shown or hidden. Updates FFI bindings and example usage, removes NSWindow+Swizzle.swift and related macOS project references, and adds a TertiaryWindow to the example app.
1 parent 78a9ad9 commit 22ddf4a

8 files changed

Lines changed: 208 additions & 89 deletions

File tree

examples/multiple_window_example/lib/main.dart

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import 'package:flutter/src/widgets/_window.dart';
55
import 'package:nativeapi/nativeapi.dart';
66

77
void main() {
8+
WindowManager.instance.setWillShowHook((windowId) {
9+
print('Window $windowId will show');
10+
});
11+
WindowManager.instance.setWillHideHook((windowId) {
12+
print('Window $windowId will hide');
13+
});
814
runApp(const MyApp());
915
}
1016

@@ -19,7 +25,9 @@ class MyApp extends StatelessWidget {
1925
theme: ThemeData(
2026
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
2127
),
22-
home: ViewCollection(views: [SecondaryWindow(), PrimaryWindow()]),
28+
home: ViewCollection(
29+
views: [TertiaryWindow(), SecondaryWindow(), PrimaryWindow()],
30+
),
2331
);
2432
}
2533
}
@@ -95,3 +103,28 @@ class _SecondaryWindowState extends State<SecondaryWindow> {
95103
);
96104
}
97105
}
106+
107+
class TertiaryWindow extends StatefulWidget {
108+
const TertiaryWindow({super.key});
109+
110+
@override
111+
State<TertiaryWindow> createState() => _TertiaryWindowState();
112+
}
113+
114+
class _TertiaryWindowState extends State<TertiaryWindow> {
115+
final _windowController = RegularWindowController(
116+
preferredSize: const Size(800, 600),
117+
title: 'Tertiary Window',
118+
);
119+
120+
@override
121+
Widget build(BuildContext context) {
122+
return RegularWindow(
123+
controller: _windowController,
124+
child: Scaffold(
125+
appBar: AppBar(title: const Text('Tertiary Window')),
126+
body: const Center(child: Text('Tertiary Window')),
127+
),
128+
);
129+
}
130+
}

examples/multiple_window_example/macos/Runner.xcodeproj/project.pbxproj

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
2828
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
2929
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
30-
F52B082E2EBF40BA00DF8AA8 /* NSWindow+Swizzle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F52B082D2EBF40B500DF8AA8 /* NSWindow+Swizzle.swift */; };
3130
/* End PBXBuildFile section */
3231

3332
/* Begin PBXContainerItemProxy section */
@@ -79,7 +78,6 @@
7978
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
8079
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
8180
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
82-
F52B082D2EBF40B500DF8AA8 /* NSWindow+Swizzle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSWindow+Swizzle.swift"; sourceTree = "<group>"; };
8381
/* End PBXFileReference section */
8482

8583
/* Begin PBXFrameworksBuildPhase section */
@@ -165,7 +163,6 @@
165163
33FAB671232836740065AC1E /* Runner */ = {
166164
isa = PBXGroup;
167165
children = (
168-
F52B082D2EBF40B500DF8AA8 /* NSWindow+Swizzle.swift */,
169166
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
170167
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
171168
33E51914231749380026EE4D /* Release.entitlements */,
@@ -260,7 +257,7 @@
260257
);
261258
mainGroup = 33CC10E42044A3C60003C045;
262259
packageReferences = (
263-
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
260+
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
264261
);
265262
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
266263
projectDirPath = "";
@@ -346,7 +343,6 @@
346343
isa = PBXSourcesBuildPhase;
347344
buildActionMask = 2147483647;
348345
files = (
349-
F52B082E2EBF40BA00DF8AA8 /* NSWindow+Swizzle.swift in Sources */,
350346
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
351347
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
352348
);
@@ -704,7 +700,7 @@
704700
/* End XCConfigurationList section */
705701

706702
/* Begin XCLocalSwiftPackageReference section */
707-
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
703+
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
708704
isa = XCLocalSwiftPackageReference;
709705
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
710706
};

examples/multiple_window_example/macos/Runner.xcworkspace/contents.xcworkspacedata

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/multiple_window_example/macos/Runner/AppDelegate.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ class AppDelegate: FlutterAppDelegate {
1212
}
1313

1414
var engine: FlutterEngine?
15-
16-
override func applicationWillFinishLaunching(_ notification: Notification) {
17-
NSWindow.enableSwizzling()
18-
}
1915

2016
override func applicationDidFinishLaunching(_ notification: Notification) {
2117
engine = FlutterEngine(name: "project", project: nil)

examples/multiple_window_example/macos/Runner/NSWindow+Swizzle.swift

Lines changed: 0 additions & 74 deletions
This file was deleted.

packages/cnativeapi/lib/src/bindings_generated.dart

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3821,6 +3821,62 @@ class CNativeApiBindings {
38213821
);
38223822
late final _native_window_manager_shutdown =
38233823
_native_window_manager_shutdownPtr.asFunction<void Function()>();
3824+
3825+
/// Set (or clear) the "will show" hook.
3826+
/// @param callback Function called before window is shown (e.g., makeKeyAndOrderFront: on macOS). NULL to clear.
3827+
/// @param user_data Opaque pointer passed back to callback.
3828+
void native_window_manager_set_will_show_hook(
3829+
native_window_will_show_callback_t callback,
3830+
ffi.Pointer<ffi.Void> user_data,
3831+
) {
3832+
return _native_window_manager_set_will_show_hook(callback, user_data);
3833+
}
3834+
3835+
late final _native_window_manager_set_will_show_hookPtr =
3836+
_lookup<
3837+
ffi.NativeFunction<
3838+
ffi.Void Function(
3839+
native_window_will_show_callback_t,
3840+
ffi.Pointer<ffi.Void>,
3841+
)
3842+
>
3843+
>('native_window_manager_set_will_show_hook');
3844+
late final _native_window_manager_set_will_show_hook =
3845+
_native_window_manager_set_will_show_hookPtr
3846+
.asFunction<
3847+
void Function(
3848+
native_window_will_show_callback_t,
3849+
ffi.Pointer<ffi.Void>,
3850+
)
3851+
>();
3852+
3853+
/// Set (or clear) the "will hide" hook.
3854+
/// @param callback Function called before window is hidden (e.g., orderOut: on macOS). NULL to clear.
3855+
/// @param user_data Opaque pointer passed back to callback.
3856+
void native_window_manager_set_will_hide_hook(
3857+
native_window_will_hide_callback_t callback,
3858+
ffi.Pointer<ffi.Void> user_data,
3859+
) {
3860+
return _native_window_manager_set_will_hide_hook(callback, user_data);
3861+
}
3862+
3863+
late final _native_window_manager_set_will_hide_hookPtr =
3864+
_lookup<
3865+
ffi.NativeFunction<
3866+
ffi.Void Function(
3867+
native_window_will_hide_callback_t,
3868+
ffi.Pointer<ffi.Void>,
3869+
)
3870+
>
3871+
>('native_window_manager_set_will_hide_hook');
3872+
late final _native_window_manager_set_will_hide_hook =
3873+
_native_window_manager_set_will_hide_hookPtr
3874+
.asFunction<
3875+
void Function(
3876+
native_window_will_hide_callback_t,
3877+
ffi.Pointer<ffi.Void>,
3878+
)
3879+
>();
38243880
}
38253881

38263882
/// Representation of a point
@@ -4491,3 +4547,30 @@ typedef Dartnative_window_event_callback_tFunction =
44914547
ffi.Pointer<native_window_event_t> event,
44924548
ffi.Pointer<ffi.Void> user_data,
44934549
);
4550+
4551+
/// Hooks called BEFORE a native window is shown/hidden.
4552+
/// Passing NULL clears the corresponding hook.
4553+
typedef native_window_will_show_callback_t =
4554+
ffi.Pointer<ffi.NativeFunction<native_window_will_show_callback_tFunction>>;
4555+
typedef native_window_will_show_callback_tFunction =
4556+
ffi.Void Function(
4557+
native_window_id_t window_id,
4558+
ffi.Pointer<ffi.Void> user_data,
4559+
);
4560+
typedef Dartnative_window_will_show_callback_tFunction =
4561+
void Function(
4562+
Dartnative_window_id_t window_id,
4563+
ffi.Pointer<ffi.Void> user_data,
4564+
);
4565+
typedef native_window_will_hide_callback_t =
4566+
ffi.Pointer<ffi.NativeFunction<native_window_will_hide_callback_tFunction>>;
4567+
typedef native_window_will_hide_callback_tFunction =
4568+
ffi.Void Function(
4569+
native_window_id_t window_id,
4570+
ffi.Pointer<ffi.Void> user_data,
4571+
);
4572+
typedef Dartnative_window_will_hide_callback_tFunction =
4573+
void Function(
4574+
Dartnative_window_id_t window_id,
4575+
ffi.Pointer<ffi.Void> user_data,
4576+
);

packages/nativeapi/lib/src/window_manager.dart

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,22 @@ class WindowManager with EventEmitter, CNativeApiBindingsMixin {
8585
static bool _callbackInitialized = false;
8686
static int? _eventListenerId;
8787

88+
// Native callables for pre-show/hide hooks
89+
static NativeCallable<
90+
Void Function(
91+
native_window_id_t,
92+
Pointer<Void>,
93+
)>? _willShowCallback;
94+
static NativeCallable<
95+
Void Function(
96+
native_window_id_t,
97+
Pointer<Void>,
98+
)>? _willHideCallback;
99+
100+
// Dart-side hook handlers
101+
void Function(int windowId)? _onWillShowHook;
102+
void Function(int windowId)? _onWillHideHook;
103+
88104
/// Private constructor for singleton pattern.
89105
WindowManager._();
90106

@@ -321,5 +337,77 @@ class WindowManager with EventEmitter, CNativeApiBindingsMixin {
321337
// Shutdown the native window manager
322338
bindings.native_window_manager_shutdown();
323339
}
340+
341+
/// Set (or clear) the hook invoked BEFORE a native window is shown.
342+
/// Passing null clears the hook.
343+
void setWillShowHook(void Function(int windowId)? callback) {
344+
_onWillShowHook = callback;
345+
346+
// Clear current hook if requested
347+
if (callback == null) {
348+
bindings.native_window_manager_set_will_show_hook(nullptr, nullptr);
349+
_willShowCallback?.close();
350+
_willShowCallback = null;
351+
return;
352+
}
353+
354+
// (Re)create native callable and register
355+
_willShowCallback?.close();
356+
_willShowCallback = NativeCallable<
357+
Void Function(
358+
native_window_id_t,
359+
Pointer<Void>,
360+
)>.listener(_nativeOnWillShow);
361+
bindings.native_window_manager_set_will_show_hook(
362+
_willShowCallback!.nativeFunction,
363+
nullptr,
364+
);
365+
}
366+
367+
/// Set (or clear) the hook invoked BEFORE a native window is hidden.
368+
/// Passing null clears the hook.
369+
void setWillHideHook(void Function(int windowId)? callback) {
370+
_onWillHideHook = callback;
371+
372+
if (callback == null) {
373+
bindings.native_window_manager_set_will_hide_hook(nullptr, nullptr);
374+
_willHideCallback?.close();
375+
_willHideCallback = null;
376+
return;
377+
}
378+
379+
_willHideCallback?.close();
380+
_willHideCallback = NativeCallable<
381+
Void Function(
382+
native_window_id_t,
383+
Pointer<Void>,
384+
)>.listener(_nativeOnWillHide);
385+
bindings.native_window_manager_set_will_hide_hook(
386+
_willHideCallback!.nativeFunction,
387+
nullptr,
388+
);
389+
}
390+
391+
// Native -> Dart bridge for pre-show hook
392+
static void _nativeOnWillShow(
393+
Dartnative_window_id_t windowId,
394+
Pointer<Void> userData,
395+
) {
396+
final cb = _instance._onWillShowHook;
397+
if (cb != null) {
398+
cb(windowId);
399+
}
400+
}
401+
402+
// Native -> Dart bridge for pre-hide hook
403+
static void _nativeOnWillHide(
404+
Dartnative_window_id_t windowId,
405+
Pointer<Void> userData,
406+
) {
407+
final cb = _instance._onWillHideHook;
408+
if (cb != null) {
409+
cb(windowId);
410+
}
411+
}
324412
}
325413

0 commit comments

Comments
 (0)