Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Multiwindow Preview based on FFI #165072

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

knopp
Copy link
Member

@knopp knopp commented Mar 12, 2025

This is initial prototype for multi-window implementation based on FFI. Contains basic implementation for macOS, Windows and Linux platforms.

The purpose of this PR is to gather feedback on the approach and design. It definitely needs polish and work.

The PR is a bit noisy right now since it contains code from other PRs that are not yet merged.

Interesting bits are inside
packages/flutter/lib/src/widgets/window.dart
packages/flutter/lib/src/widgets/window_win32.dart
packages/flutter/lib/src/widgets/window_macos.dart
packages/flutter/lib/src/widgets/window_linux.dart
engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterWindowController.mm
engine/src/flutter/shell/platform/linux/fl_windowing.cc
engine/src/flutter/shell/platform/windows/flutter_host_window_controller.cc

Basic usage

Creating a Window

  final RegularWindowController controller = RegularWindowController(
    size: const Size(800, 600),
    sizeConstraints: const BoxConstraints(minWidth: 640, minHeight: 480),
    title: "Multi-Window Reference Application",
  );

This will synchronously create new platform window and the corresponding FlutterView. When the controller constructor returns the FlutterView can immediately be placed to widget hierarchy:

  runWidget(
    RegularWindow(
      controller: controller,
      child: MaterialApp(home: MainWindow(mainController: controller)),
    ),
  );

RegularWindowController is a factory and returned instance is a platform specific subclass, so it can be casted to access platform specific features.

for example

final macOSController = controller as RegularWindowControllerMacOS;
macOSController.setWindowCollectionBehavior(...);

or

final windowsController = controller as RegularWindowControllerWin32;
// Register a custom WIN32 message handler for this window.
windowsController.registerMessageHandler(handler);

(This being synchronous and FFI based we are actually able to route windows messages to Dart where they can be handled if needed, allowing for example for a custom NC_HITTEST implementation that can do actual Flutter hittesting to determine draggable window area).

Out of tree embedders

This approach scales to out of tree embedders as well. Out of tree embedder would subclass WidgetBinding and override the createWindowingOwner method. That is entry point for windowing implementation responsible for creating platform specific window controllers.

The out of tree embedders can then provide RegularWindowControllerHarmony for example, which must implement the common functionality required by RegularWindowController, but can also provide custom, platform specific functionality.

One benefit of this approach is that the platform interface is defined in Dart, rather than being an informal platform channel protocol, so when changes are made to common interface (RegularWindowController) that out of tree embedder does not expect, it will fail at compile time rather than runtime.

The out of tree embedder code can be provided as package.

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@github-actions github-actions bot added tool Affects the "flutter" command-line tool. See also t: labels. framework flutter/packages/flutter repository. See also f: labels. engine flutter/engine repository. See also e: labels. platform-windows Building on or for Windows specifically d: examples Sample code and demos platform-linux Building on or for Linux specifically a: desktop Running on desktop platform-macos labels Mar 12, 2025
Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's tricky to review this because of the older PRs that this is branched off of, but it looks great from what I see.

  • Easiest case for app developers: I'm writing a cross-platform desktop app and I don't care about the details and just want to create a couple of windows and control their size/title/state.
    • Use RegularWindowController and its .modify method.
  • Still doable: I'm writing say a Linux-only app, and I want to use something that's only available in the Linux windowing API.
    • Use LinuxWindowController.

And it seems like we as framework/engine developers are able to modify Linux/Windows/MacOSWindowControllers independently if we want to add some new platform API.

Those were the main things I was looking for and they all seem to work the way I hoped they would 👍

Comment on lines +160 to +163

@Native<Void Function(Pointer<Void>, Int64)>(symbol: 'flutter_set_window_state')
external static void _setWindowState(Pointer<Void> windowHandle, int state);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if in the future Linux adds some new window method that we want to support, we can just add it here without messing with RegularWindowController, WindowController, etc. right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's exactly right. We can add platform specific functionality independently, and then when if we later decide that something is generic enough to support on all platform add it to RegularWindowController.

}

FLUTTER_EXPORT
void flutter_set_window_state(HWND hwnd, int64_t state) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to implement this in C++ instead of Dart? Did you consider FFI'ing straight to win32? Something like this: https://github.com/halildurmus/win32/blob/27496b847b9ce831db798203238e4aa847e87d54/packages/win32/lib/src/win32/user32.g.dart#L9200-L9214

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For trivial things I already call win32 API directly, for example _setWindowTitle calls SetWindowTextW, _destroyWindow calls DestroyWindow. Here i went this way because it seemed simpler (needs less declarations and constants), in reality there is nothing preventing us from calling win32 api directly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a completely reasonable answer, but I suspect that more lines of code is a good trade-off for moving more of the logic to Dart.

Customers know how to debug Dart code. However, they might be significantly less comfortable debugging C++ code. The more logic is in the Dart layer, the more accessible that logic is to our customers.

Of course, this is a balancing act. If there are areas where Dart FFI to win32 is significantly less readable/maintainable, we should stick to C++.


/// Creates the [WindowingOwner] instance available via [windowingOwner].
/// Can be overriden in subclasses to create embedder-specific [WindowingOwner]
/// implementation.
Copy link
Member

@loic-sharma loic-sharma Mar 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it hard to switch an app to a WidgetsBinding subclass? What would an app entrypoint look like if I need to support a third-party embedder? I imagine you won't be able to use runApp anymore?

Spitballing: should we instead have a model where you set your desired windowing owner if you have a third-party embedder?

In today's world, something like:

void main() {
  if (onPlatformFoo) {
    WidgetsBinding.instance.defaultWindowingOwner = WindowingOwnerFoo();
  }

  runApp(...);
}

Of course the third-party platform could provide a helper to do all the necessary registration.


If the "custom platforms set stuff" model works, we could extend this to allow embedders to inject Dart code that's invoked at Isolate start up, before main executes. For example, today we allow embedders to inject a Dart plugin registrant which the engine invokes before invoking main. This would allow embedders to register their custom Dart implementations automatically without the user needing to add code to their main function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, WidgetsBinding.platformMenuDelegate appears to be useful prior art. It's a member on the widgets binding that a third-party platform can override to implement their own functionality. See: https://api.flutter.dev/flutter/widgets/PlatformMenuDelegate-class.html

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was honestly only a first draft. If widget binding subclass is too much friction we can just make the owner field non-final.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was honestly only a first draft.

Yup, and it's a great first draft! Sorry for nitpicking so early in the process haha

If widget binding subclass is too much friction we can just make the owner field non-final.

That sounds good to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: desktop Running on desktop d: examples Sample code and demos engine flutter/engine repository. See also e: labels. framework flutter/packages/flutter repository. See also f: labels. platform-linux Building on or for Linux specifically platform-macos platform-windows Building on or for Windows specifically tool Affects the "flutter" command-line tool. See also t: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants