-
Notifications
You must be signed in to change notification settings - Fork 6k
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
Add support for multiple top-level windows on Windows #56090
base: main
Are you sure you want to change the base?
Conversation
FLUTTER_EXPORT FlutterDesktopViewControllerRef | ||
FlutterDesktopEngineCreateViewController( | ||
FlutterDesktopEngineRef engine, | ||
const FlutterDesktopViewControllerProperties* properties); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, we were holding off on making the multi-window APIs public until the Windows embedder properly supports multi-window.
Known problems with Window's multi-window support: https://flutter.dev/go/multi-window-status#heading=h.i5rwjhitgcmu
- Cursor support
- Keyboard support
- Text input support
- App lifecycle across multiple windows
- Accessibility support
- Plugins updated
Could we keep all these APIs non-public until we're sure these are production ready?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hope it can be public before the above 6 items are ready, so that more people can prototype this function;
it is enough for flutter to just warn that it is not production ready;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hope it can be public before the above 6 items are ready, so that more people can prototype this function; it is enough for flutter to just warn that it is not production ready;
The API is "pubternal"; advanced users can opt-in to using it either by building a custom engine or by linking against the API directly. I think that's a good compromise as it prevents folks from unintentionally using an API that we know is broken.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. I'll revert to the non-public C API.
|
||
namespace flutter { | ||
|
||
// A singleton controller for Flutter windows. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you expand why this needs to be a singleton? What would I do if my app has two Flutter engines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This approach seemed reasonable to avoid injecting the controller into each window instance, but we weren't taking into account the use of multiple engines. I'll change it to a non-singleton class.
|
||
#include <dwmapi.h> | ||
|
||
namespace { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shell/platform/windows/client_wrapper
layer is code that the Flutter tool copies into the end user's project. This code doesn't get compiled into flutter_windows.dll
. This doesn't seem like the right place to implement the flutter/windowing
platform channel.
Our other platform channels are registered in the FlutterWindowsEngine
here. Should we move the platform channel handling logic down to there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood. I'll move the handler to FlutterWindowsEngine
@hbatagelo would you be able to join the next Google / Canonical sync meeting to discuss this PR? |
Sure. I'll be there. |
when will be the next sync meeting. |
auto const& data{data_opt.value()}; | ||
result.Success(EncodableValue(EncodableMap{ | ||
{EncodableValue("viewId"), EncodableValue(data.view_id)}, | ||
{EncodableValue("archetype"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enumerated values in other channels are normally passed as strings, i.e. "Archetype.regular"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to know, thanks. I'll change it.
return; | ||
} | ||
|
||
std::wstring const title{ArchetypeToWideString(archetype)}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting the title seems to the archetype seems like debugging code that shouldn't be here anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's just a placeholder. Setting the title is straightforward. I'll do it.
{EncodableValue("viewId"), EncodableValue(data.view_id)}, | ||
{EncodableValue("archetype"), | ||
EncodableValue(static_cast<int>(data.archetype))}, | ||
{EncodableValue("size"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be easier to send "width" and "height" - less marshalling required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current approach aims to maintain symmetry between the Dart API and the serialized structure, although we haven't given it much thought yet. I agree that a flat structure would simplify the serialization, but the nested approach may be more readable and intuitive as we add more attributes.
Maybe we should decide whether to adopt a flat structure (easier to serialize, but possibly cluttering the payload with many keys) or a nested structure (mimicking the Dart syntax but a bit more complex to parse) and write this down in the design doc.
Nested structure (using createPopup
as an example):
{
"parentViewId": 42,
"preferredSize": { "width": 800, "height": 600 },
"anchorRect": { "left": 0, "top": 0, "width": 100, "height": 200 },
"positionerParentAnchor": "topLeft",
"positionerChildAnchor": "center",
"positionerOffset": { "dx": 10, "dy": 20 },
"positionerConstraintAdjustment": ["slideX", "slideY"]
}
Flat structure:
{
"parentViewId": 42,
"preferredWidth": 800,
"preferredHeight": 600,
"anchorRectLeft": 0,
"anchorRectTop": 0,
"anchorRectWidth": 100,
"anchorRectHeight": 200,
"positionerParentAnchor": "topLeft",
"positionerChildAnchor": "topLeft",
"positionerOffsetX": 10,
"positionerOffsetY": 20,
"positionerConstraintAdjustment": ["slideX", "slideY"]
}
Refactor multi-window support
Updates from canonical#1:
|
Design doc: https://docs.google.com/document/d/1eQG-IS7r4_S9_h50MY_hSGwUVtgSTRSLzu7MLzPNd2Y/edit?tab=t.0
This PR is a part of a group of PRs. Going from first to last, these must be merged in the following order:
See also the corresponding framework PRs for multi-win support, starting from: flutter/flutter#157515
What's New
This pull request enables support for multiple top-level windows in Flutter applications on Windows. Specifically, this PR:
Adds the
--enable-multi-window
command-line option, allowing applications to opt in to multi-window support (disabled by default).Introduces the
flutter/windowing
method channel, allowing windows to be created and destroyed from the framework.The following methods are available:
createWindow(Map)
: Creates a top-level window.Map
must contain a<String, List<int>>
key-value pair, where the key is"size"
, and the value is a list containing the requested width and height for the window's client rectangle, in logical coordinates.On success, a
Map
is returned with the following key-value pairs:String
)"viewId"
int
)"archetype"
WindowArchetype::regular
"size"
List<int>
)"parentViewId"
null
If the method fails, a
PlatformException
is thrown.destroyWindow(Map)
: Destroys a window.Map
must contain a<String, int>
key-value pair, where the key is"viewId"
, and the value is the view ID of the window to be destroyed. The method returnsvoid
on success. Otherwise, aPlatformException
is thrown.The following callback methods can be listened to:
onWindowCreated(Map)
: Called when a window is created.Map
contains the following key-value pairs:String
)"viewId"
int
)"parentViewId"
int
), ornull
if the window has no parentonWindowDestroyed(Map)
: Called when a window is destroyed.Map
contains a single<String, int>
key-value pair, where the key is"viewId"
, and the value is the view ID.onWindowChanged(Map)
: Called when a window property is changed (e.g., on resizing).Map
contains the following key-value pairs:String
)"viewId"
int
)"size"
List<int>
), ornull
if the window size hasn't changedHow It Works
With multi-window support enabled,
FlutterWindowsEngine
creates a uniquely ownedFlutterHostWindowController
that is responsible for managing the lifecycle of windows associated with the engine. The engine registers the platform channel usingWindowingHandler
.When the Dart app requests a new window using the
createX
methods,WindowingHandler
callsFlutterHostWindowController::CreateHostWindow
with the decoded arguments. The controller creates a uniquely ownedFlutterHostWindow
object which manages a native Win32 window with a child Flutter view in its client area. Each view, represented byFlutterWindowsViewController
, is owned by its correspondingFlutterHostWindow
. When the Dart app requests the destruction of a window (given a view ID) or when the native window is closed from the system, the controller destroys the correspondingFlutterHostWindow
, which, in turn, cleans up the associated view controller.Window messages are first handled by a static
FlutterHostWindow::WndProc
function that initializes the native Win32 window upon receivingWM_NCCREATE
. Other messages are forwarded toFlutterHostWindowController::HandleMessage
to manage global behaviors (e.g., hiding satellites of inactive top-level windows). Finally, messages are passed back toFlutterHostWindow::HandleMessage
for window-specific behavior.Pre-launch Checklist
///
).