multi_window_manager 1.0.6 copy "multi_window_manager: ^1.0.6" to clipboard
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 #

pub version

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:

  1. Initialize it with isEnabledReuse: true (see Initialization).
  2. Use ReuseWindow as 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

2
likes
0
points
378
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter desktop plugin for creating and managing multiple windows, including resizing, repositioning, and inter-window communication.

Repository (GitHub)
View/report issues

Topics

#window #window-resize #window-manager #multi-window #desktop

License

unknown (license)

Dependencies

flutter, meta, path, screen_retriever

More

Packages that depend on multi_window_manager

Packages that implement multi_window_manager