multi_window_manager 1.0.6
multi_window_manager: ^1.0.6 copied to clipboard
Flutter desktop plugin for creating and managing multiple windows, including resizing, repositioning, and inter-window communication.
MultiWindowManager #
Flutter desktop plugin for creating and managing multiple windows: resizing, repositioning, and inter-window communication.
Fork and re-work of window_manager_plus, which itself is based on window_manager. Key additions: window reuse cache (avoid re-creating Flutter engines), cross-window registry backed by shared native state, fixed critical errors fixed restart and crash after close secondary window, fast isolate-isolate channel fast channel for realtime windows notify (in process).
Linux is not currently supported.
Platform Support #
| Linux | macOS | Windows |
|---|---|---|
| n/a | + | + |
Setup #
Windows setup #
Edit windows/runner/main.cpp:
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
+#include <iostream>
#include "flutter_window.h"
#include "utils.h"
+#include "multi_window_manager/multi_window_manager_plugin.h"
int APIENTRY wWinMain(...) {
...
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.CreateAndShow(L"my_app", origin, size)) {
return EXIT_FAILURE;
}
- window.SetQuitOnClose(true);
+ window.SetQuitOnClose(false); // let MultiWindowManager decide when to quit
+ MultiWindowManagerPluginSetWindowCreatedCallback(
+ [](std::vector<std::string> command_line_arguments) {
+ flutter::DartProject project(L"data");
+ project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
+
+ auto window = std::make_shared<FlutterWindow>(project);
+ Win32Window::Point origin(10, 10);
+ Win32Window::Size size(1280, 720);
+ if (!window->CreateAndShow(L"my_app", origin, size)) {
+ std::cerr << "Failed to create window" << std::endl;
+ }
+ window->SetQuitOnClose(false);
+ return std::move(window);
+ });
::MSG msg;
...
SetQuitOnClose(false) prevents each child window from terminating the process when it closes.
macOS setup #
Edit macos/Runner/MainFlutterWindow.swift:
import Cocoa
import FlutterMacOS
+import multi_window_manager
class MainFlutterWindow: NSPanel {
override func awakeFromNib() {
...
RegisterGeneratedPlugins(registry: flutterViewController)
+ MultiWindowManagerPlugin.RegisterGeneratedPlugins = RegisterGeneratedPlugins
super.awakeFromNib()
}
+ override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) {
+ super.order(place, relativeTo: otherWin)
+ hiddenWindowAtLaunch()
+ }
}
Edit macos/Runner/AppDelegate.swift:
import Cocoa
import FlutterMacOS
+import multi_window_manager
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
- return true
+ // Close the app only when windows not contains MainFlutterWindow
+ return !NSApp.windows.contains(where: { $0 is MainFlutterWindow })
}
}
Usage #
Initialization #
Call ensureInitialized once before accessing MultiWindowManager.current.
The args list passed to main() carries the window ID in args[0] (absent for the main window).
import 'package:flutter/material.dart';
import 'package:multi_window_manager/multi_window_manager.dart';
void main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized();
final windowId = args.isEmpty ? 0 : int.tryParse(args[0]) ?? 0;
if (windowId == 0) {
// Main window - never participates in the reuse cache.
await MultiWindowManager.ensureInitialized(windowId);
} else {
// Secondary window - optionally enable reuse cache.
await MultiWindowManager.ensureInitializedSecondary(
windowId,
isEnabledReuse: true, // hide instead of destroy on close
);
}
WindowOptions windowOptions = const WindowOptions(
size: Size(800, 600),
center: true,
backgroundColor: Colors.transparent,
titleBarStyle: TitleBarStyle.hidden,
);
MultiWindowManager.current.waitUntilReadyToShow(windowOptions, () async {
await MultiWindowManager.current.show();
await MultiWindowManager.current.focus();
});
runApp(const MyApp());
}
Create a window #
// Spawn a new Flutter engine window, passing optional string arguments.
final newWindow = await MultiWindowManager.createWindow(['type=dashboard', 'userId=42']);
if (newWindow != null) {
print('Created window ${newWindow.id}');
}
The arguments are available in main(List<String> args) of the new window as args[1], args[2], etc.
Reuse cached windows #
createWindowOrReuse avoids spawning a new Flutter engine when a previously-hidden reuse-enabled window is available. Claiming a hidden window is atomic - concurrent calls from different windows cannot pick the same target.
// Any window can call this - main or secondary.
final window = await MultiWindowManager.createWindowOrReuse(
args: ['type=dashboard', 'userId=42'],
);
For a window to participate in the reuse cache:
- Initialize it with
isEnabledReuse: true(see Initialization). - Use
ReuseWindowas the root widget:
// Secondary window entry-point:
runApp(MaterialApp(
home: ReuseWindow(
initialArgs: parsedArgs,
windowOptions: const WindowOptions(
size: Size(1280, 720),
center: true,
titleBarStyle: TitleBarStyle.hidden,
),
loadingBuilder: (context) => const Center(child: CircularProgressIndicator()),
builder: (context, args) {
// Called on first show AND on every reuse with the latest args.
return MyPage(args: args);
},
),
));
When the user closes a ReuseWindow, the native layer hides the OS window instead of destroying the Flutter engine. createWindowOrReuse can later reclaim it with new args, rebuilding the content without the overhead of creating a new engine.
setPreventClose works independently from the reuse mechanism, so inner widgets can still show "are you sure?" dialogs without breaking reuse.
Communication between windows #
// Send an event from any window to window with id 1.
final result = await MultiWindowManager.current.invokeMethodToWindow(
1,
'showNotification',
{'message': 'Hello from window ${MultiWindowManager.current.id}'},
);
// In window 1 - implement WindowListener.onEventFromWindow:
class _MyWidgetState extends State<MyWidget> with WindowListener {
@override
void initState() {
super.initState();
MultiWindowManager.current.addListener(this);
}
@override
void dispose() {
MultiWindowManager.current.removeListener(this);
super.dispose();
}
@override
Future<dynamic> onEventFromWindow(
String eventName, int fromWindowId, dynamic arguments) async {
if (eventName == 'showNotification') {
print('Message from $fromWindowId: ${arguments['message']}');
return 'acknowledged';
}
return null;
}
}
To get all registered window IDs:
final ids = await MultiWindowManager.getAllWindowManagerIds();
Window events #
class _MyWidgetState extends State<MyWidget> with WindowListener {
@override
void initState() {
super.initState();
MultiWindowManager.current.addListener(this);
}
@override
void dispose() {
MultiWindowManager.current.removeListener(this);
super.dispose();
}
@override
void onWindowFocus([int? windowId]) => setState(() {});
@override
void onWindowClose([int? windowId]) { /* ... */ }
@override
void onWindowMaximize([int? windowId]) { /* ... */ }
@override
void onWindowMinimize([int? windowId]) { /* ... */ }
// Catches every event by name, including reuse events.
@override
void onWindowEvent(String eventName, [int? windowId]) {
print('event: $eventName window: $windowId');
}
}
For global listeners (receive events from all windows with windowId set):
MultiWindowManager.addGlobalListener(myGlobalListener);
MultiWindowManager.removeGlobalListener(myGlobalListener);
Window registry #
MultiWindowManager.registry is a per-isolate reactive view backed by the shared native state. Use it to drive UI that reflects the live window list.
// In a widget:
ValueListenableBuilder<List<int>>(
valueListenable: MultiWindowManager.registry.activeWindows,
builder: (context, ids, _) => Text('Open windows: $ids'),
);
ValueListenableBuilder<List<int>>(
valueListenable: MultiWindowManager.registry.hiddenWindows,
builder: (context, ids, _) => Text('Cached (reusable) windows: $ids'),
);
For a guaranteed-fresh read bypassing the local cache:
final activeIds = await MultiWindowManager.registry.getActiveWindowIds();
final hiddenIds = await MultiWindowManager.registry.getHiddenWindowIds();
Confirm before closing #
class _MyWidgetState extends State<MyWidget> with WindowListener {
@override
void initState() {
super.initState();
MultiWindowManager.current.addListener(this);
MultiWindowManager.current.setPreventClose(true);
}
@override
void dispose() {
MultiWindowManager.current.removeListener(this);
super.dispose();
}
@override
void onWindowClose([int? windowId]) async {
if(!await MultiWindowManager.current.isPreventClose()) return;
final confirmed = await showDialog<bool>(
context: context,
builder: (_) => AlertDialog(
title: const Text('Close window?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('Close'),
),
],
),
);
if (confirmed == true) {
await MultiWindowManager.current.setPreventClose(false);
await MultiWindowManager.current.close();
}
}
}
Quit on close #
macOS
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
- return true
+ return false
}
Windows
Set SetQuitOnClose(false) for every window (see Windows setup).
To exit when the main window closes, handle onWindowClose and call exit(0).
Hidden at launch #
Windows
Edit windows/runner/win32_window.cpp to create the window without WS_VISIBLE:
HWND window = CreateWindow(
- window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+ window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
...);
Edit windows/runner/flutter_window.cpp to remove the auto-show:
flutter_controller_->engine()->SetNextFrameCallback([&]() {
- this->Show();
});
API #
MultiWindowManager #
Instance methods
addListener(WindowListener listener) -> void
Subscribe to window events for this window.
removeListener(WindowListener listener) -> void
Unsubscribe from window events.
show({bool inactive = false}) -> Future<void>
Shows and gives focus to the window.
hide() -> Future<void>
Hides the window.
close() -> Future<void>
Try to close the window (respects setPreventClose).
destroy() -> Future<void>
Force-close app, bypassing setPreventClose.
focus() -> Future<void>
Focuses on the window.
blur() -> Future<void>
Removes focus from the window.
isFocused() -> Future<bool>
Returns whether the window is currently focused.
center({bool animate = false}) -> Future<void>
Moves the window to the center of the screen.
setPosition(Offset position, {bool animate = false}) -> Future<void>
Moves the window to a position.
getPosition() -> Future<Offset>
Returns the current window position.
setAlignment(Alignment alignment, {bool animate = false}) -> Future<void>
Moves the window to a screen-aligned position.
setSize(Size size, {bool animate = false}) -> Future<void>
Resizes the window.
getSize() -> Future<Size>
Returns the current window size.
setBounds(Rect? bounds, {Offset? position, Size? size, bool animate = false}) -> Future<void>
Resizes and moves the window to the supplied bounds.
getBounds() -> Future<Rect>
Returns the window bounds as a Rect.
setMinimumSize(Size size) -> Future<void>
Sets the minimum window size.
setMaximumSize(Size size) -> Future<void>
Sets the maximum window size.
maximize({bool vertically = false}) -> Future<void>
Maximizes the window. vertically simulates aero snap (Windows only).
unmaximize() -> Future<void>
Unmaximizes the window.
isMaximized() -> Future<bool>
Returns whether the window is maximized.
isMaximizable() -> Future<bool>
Returns whether the window can be maximized by the user.
setMaximizable(bool isMaximizable) -> Future<void>
Sets whether the window can be maximized by the user.
minimize() -> Future<void>
Minimizes the window.
restore() -> Future<void>
Restores the window from a minimized state.
isMinimized() -> Future<bool>
Returns whether the window is minimized.
isMinimizable() -> Future<bool>
Returns whether the window can be minimized by the user.
setMinimizable(bool isMinimizable) -> Future<void>
Sets whether the window can be minimized by the user.
setFullScreen(bool isFullScreen) -> Future<void>
Sets whether the window should be in full-screen mode.
isFullScreen() -> Future<bool>
Returns whether the window is in full-screen mode.
isVisible() -> Future<bool>
Returns whether the window is visible to the user.
setTitle(String title) -> Future<void>
Changes the title of the native window.
getTitle() -> Future<String>
Returns the title of the native window.
setTitleBarStyle(TitleBarStyle titleBarStyle, {bool windowButtonVisibility = true}) -> Future<void>
Changes the title bar style of the native window.
getTitleBarHeight() -> Future<int>
Returns the title bar height in logical pixels.
setAlwaysOnTop(bool isAlwaysOnTop) -> Future<void>
Sets whether the window is always on top of other windows.
isAlwaysOnTop() -> Future<bool>
Returns whether the window is always on top.
setAlwaysOnBottom(bool isAlwaysOnBottom) -> Future<void>
Sets whether the window is always below other windows.
isAlwaysOnBottom() -> Future<bool>
Returns whether the window is always on the bottom.
setOpacity(double opacity) -> Future<void>
Sets the window opacity (0.0 fully transparent, 1.0 fully opaque).
getOpacity() -> Future<double>
Returns the current window opacity.
setBackgroundColor(Color backgroundColor) -> Future<void>
Sets the background color of the window.
setPreventClose(bool isPreventClose) -> Future<void>
Intercepts the native close signal. Use with onWindowClose to show a confirmation dialog.
isPreventClose() -> Future<bool>
Returns whether the native close signal is being intercepted.
setResizable(bool isResizable) -> Future<void>
Sets whether the window can be resized by the user.
isResizable() -> Future<bool>
Returns whether the window is resizable.
setMovable(bool isMovable) -> Future<void>
Sets whether the window can be moved by the user.
isMovable() -> Future<bool>
Returns whether the window is movable.
setClosable(bool isClosable) -> Future<void>
Sets whether the window can be manually closed by the user.
isClosable() -> Future<bool>
Returns whether the window can be closed by the user.
setSkipTaskbar(bool isSkipTaskbar) -> Future<void>
Makes the window not show in the taskbar / dock.
isSkipTaskbar() -> Future<bool>
Returns whether the window is hidden from the taskbar.
setHasShadow(bool hasShadow) -> Future<void>
Sets whether the window has a shadow (frameless windows only on Windows).
hasShadow() -> Future<bool>
Returns whether the window has a shadow.
setProgressBar(double progress) -> Future<void>
Sets the taskbar progress bar value (0.0 to 1.0). Windows only.
setIcon(String iconPath) -> Future<void>
Sets the window / taskbar icon.
dock({required DockSide side, required int width}) -> Future<void>
Docks the window to a screen edge. Windows only.
undock() -> Future<bool>
Undocks the window. Windows only.
isDocked() -> Future<DockSide?>
Returns the current dock side, or null if not docked.
setAsFrameless() -> Future<void>
Removes the native window frame (title bar, border, etc.).
setAspectRatio(double aspectRatio) -> Future<void>
Locks the window to a fixed aspect ratio.
setBrightness(Brightness brightness) -> Future<void>
Sets the brightness (light/dark) of the window.
setIgnoreMouseEvents(bool ignore, {bool forward = false}) -> Future<void>
Makes the window ignore all mouse events.
popUpWindowMenu() -> Future<void>
Pops up the native window menu.
startDragging() -> Future<void>
Starts a window drag from a custom title bar widget.
startResizing(ResizeEdge resizeEdge) -> Future<void>
Starts a window resize from a custom border widget.
waitUntilReadyToShow([WindowOptions? options, VoidCallback? callback]) -> Future<void>
Applies WindowOptions and calls callback once the window is ready to display.
invokeMethodToWindow(int targetWindowId, String method, [dynamic args]) -> Future
Sends an event to another window. The result is the return value of WindowListener.onEventFromWindow in the target window.
getDevicePixelRatio() -> double
Returns the device pixel ratio for this window.
toString() -> String
Returns a string representation including the window ID.
Static methods
ensureInitialized(int windowId) -> Future<void>
Initialize for the main window (windowId = 0). Must be called before accessing MultiWindowManager.current.
ensureInitializedSecondary(int windowId, {bool isEnabledReuse = false}) -> Future<void>
Initialize for a secondary window. Set isEnabledReuse: true to enable the reuse cache for this window.
createWindow([List<String>? args]) -> Future<MultiWindowManager?>
Spawns a new window. Returns a MultiWindowManager instance for the new window, or null on failure.
createWindowOrReuse({List<String>? args}) -> Future<MultiWindowManager?>
Atomically claims a hidden reuse-cached window and reinitializes it with args, or spawns a new one if none are available. Safe to call from any window (main or secondary).
fromWindowId(int windowId) -> MultiWindowManager
Returns a MultiWindowManager instance for any registered window by ID.
getAllWindowManagerIds() -> Future<List<int>>
Returns the IDs of all windows currently registered in the process.
addGlobalListener(WindowListener listener) -> void
Subscribes to events from all windows. The windowId parameter in callbacks identifies the source window.
removeGlobalListener(WindowListener listener) -> void
Unsubscribes a global listener.
registry -> WindowRegistry
Process-wide reactive window registry. See WindowRegistry.
WindowListener #
Mixin used with addListener / addGlobalListener. When used as a global listener, windowId identifies the source window; when used as a local listener it is always null.
onWindowClose([int? windowId]) -> void
Emitted when the window is going to be closed.
onWindowFocus([int? windowId]) -> void
Emitted when the window gains focus.
onWindowBlur([int? windowId]) -> void
Emitted when the window loses focus.
onWindowMaximize([int? windowId]) -> void
Emitted when the window is maximized.
onWindowUnmaximize([int? windowId]) -> void
Emitted when the window exits a maximized state.
onWindowMinimize([int? windowId]) -> void
Emitted when the window is minimized.
onWindowRestore([int? windowId]) -> void
Emitted when the window is restored from a minimized state.
onWindowResize([int? windowId]) -> void
Emitted while the window is being resized.
onWindowResized([int? windowId]) -> void
Emitted once when the window finishes resizing. Windows / macOS only.
onWindowMove([int? windowId]) -> void
Emitted while the window is being moved.
onWindowMoved([int? windowId]) -> void
Emitted once when the window finishes moving. Windows / macOS only.
onWindowEnterFullScreen([int? windowId]) -> void
Emitted when the window enters full-screen mode.
onWindowLeaveFullScreen([int? windowId]) -> void
Emitted when the window leaves full-screen mode.
onWindowDocked([int? windowId]) -> void
Emitted when the window enters a docked state. Windows only.
onWindowUndocked([int? windowId]) -> void
Emitted when the window leaves a docked state. Windows only.
onWindowEvent(String eventName, [int? windowId]) -> void
Emitted for every window event, including reuse lifecycle events.
onEventFromWindow(String eventName, int fromWindowId, dynamic arguments) -> Future
Receives inter-window messages sent via invokeMethodToWindow. The return value is forwarded back to the caller.
WindowRegistry #
Accessible via MultiWindowManager.registry. Backed by shared native C++ state, so it is consistent across all Flutter engines in the process.
activeWindows -> ValueNotifier<List<int>>
IDs of currently visible / active windows. Updated automatically on every lifecycle event.
hiddenWindows -> ValueNotifier<List<int>>
IDs of reuse-cached hidden windows available for reclaiming. Updated automatically.
hasHiddenWindows -> bool
Whether at least one reusable window is available (reflects the last refresh result).
getActiveWindowIds() -> Future<List<int>>
Queries the native layer directly. Always returns a fresh value.
getHiddenWindowIds() -> Future<List<int>>
Queries the native layer directly. Always returns a fresh value.
refresh() -> Future<void>
Re-queries native state and updates activeWindows and hiddenWindows. Called automatically by MultiWindowManager on every lifecycle event.
License #
MIT