blob: 1b2684cdca61ba1c2e98410ed162a31be1c3cdc5 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Do not import this file in production applications or packages published
// to pub.dev. Flutter will make breaking changes to this file, even in patch
// versions.
//
// All APIs in this file must be private or must:
//
// 1. Have the `@internal` attribute.
// 2. Throw an `UnsupportedError` if `isWindowingEnabled`
// is `false`.
//
// See: https://github.com/flutter/flutter/issues/30701.
import 'dart:async';
import 'dart:ffi' as ffi;
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' show Display, FlutterView;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import '../foundation/_features.dart';
import '_window.dart';
import '_window_positioner.dart';
import 'binding.dart';
/// A Win32 window handle.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
typedef HWND = ffi.Pointer<ffi.Void>;
const int _WM_DESTROY = 0x0002;
const int _WM_SIZE = 0x0005;
const int _WM_ACTIVATE = 0x0006;
const int _WM_CLOSE = 0x0010;
const int _WA_INACTIVE = 0;
const int _SW_RESTORE = 9;
const int _SW_MAXIMIZE = 3;
const int _SW_MINIMIZE = 6;
const String _kWindowingDisabledErrorMessage = '''
Windowing APIs are not enabled.
Windowing APIs are currently experimental. Do not use windowing APIs in
production applications or plugins published to pub.dev.
To try experimental windowing APIs:
1. Switch to Flutter's main release channel.
2. Turn on the windowing feature flag.
See: https://github.com/flutter/flutter/issues/30701.
''';
/// Abstract handler class for Windows messages.
///
/// Implementations of this class should register with
/// [WindowingOwnerWin32.addMessageHandler] to begin receiving messages.
/// When finished handling messages, implementations should deregister
/// themselves with [WindowingOwnerWin32.removeMessageHandler].
abstract class _WindowsMessageHandler {
/// Handles a window message.
///
/// Returned value, if not null will be returned to the system as LRESULT
/// and will stop all other handlers from being called. See
/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-wndproc
/// for more information.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
int? handleWindowsMessage(
FlutterView view,
HWND windowHandle,
int message,
int wParam,
int lParam,
);
}
/// [WindowingOwner] implementation for Windows.
///
/// If [Platform.isWindows] is false, then the constructor will throw an
/// [UnsupportedError].
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [WindowingOwner], the abstract class that manages native windows.
@internal
class WindowingOwnerWin32 extends WindowingOwner {
/// Creates a new [WindowingOwnerWin32] instance.
///
/// If [Platform.isWindows] is false, then this constructor will throw an
/// [UnsupportedError]
///
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [WindowingOwner], the abstract class that manages native windows.
@internal
WindowingOwnerWin32() : allocator = _CallocAllocator() {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
if (!Platform.isWindows) {
throw UnsupportedError('Only available on the Win32 platform');
}
assert(
WidgetsBinding.instance.platformDispatcher.engineId != null,
'WindowingOwnerWin32 must be created after the engine has been initialized.',
);
_Win32PlatformInterface.initializeWindowing(
allocator,
WidgetsBinding.instance.platformDispatcher.engineId!,
_onMessage,
);
}
final List<_WindowsMessageHandler> _messageHandlers = <_WindowsMessageHandler>[];
/// The [Allocator] used for allocating native memory in this owner.
///
/// This can be overridden via the [WindowingOwnerWin32.test] constructor.
///
/// {@macro flutter.widgets.windowing.experimental}
@internal
final ffi.Allocator allocator;
@internal
@override
RegularWindowController createRegularWindowController({
Size? preferredSize,
BoxConstraints? preferredConstraints,
String? title,
required RegularWindowControllerDelegate delegate,
}) {
return RegularWindowControllerWin32(
owner: this,
delegate: delegate,
preferredSize: preferredSize,
preferredConstraints: preferredConstraints,
title: title,
);
}
@internal
@override
DialogWindowController createDialogWindowController({
required DialogWindowControllerDelegate delegate,
Size? preferredSize,
BoxConstraints? preferredConstraints,
BaseWindowController? parent,
String? title,
}) {
return DialogWindowControllerWin32(
owner: this,
delegate: delegate,
preferredSize: preferredSize,
preferredConstraints: preferredConstraints,
title: title,
parent: parent,
);
}
@internal
@override
TooltipWindowController createTooltipWindowController({
required TooltipWindowControllerDelegate delegate,
required BoxConstraints preferredConstraints,
required bool isSizedToContent,
required Rect anchorRect,
required WindowPositioner positioner,
required BaseWindowController parent,
}) {
return TooltipWindowControllerWin32(
owner: this,
delegate: delegate,
contentSizeConstraints: preferredConstraints,
isSizedToContent: isSizedToContent,
anchorRect: anchorRect,
positioner: positioner,
parent: parent,
);
}
@internal
@override
PopupWindowController createPopupWindowController({
required PopupWindowControllerDelegate delegate,
required BoxConstraints preferredConstraints,
required Rect anchorRect,
required WindowPositioner positioner,
required BaseWindowController parent,
}) {
throw UnimplementedError('Popup windows are not yet implemented on Windows.');
}
/// Register a new [WindowsMessageHandler].
///
/// The handler will be triggered for unhandled messages for all top level
/// windows.
///
/// Adding a handler multiple times has no effect.
///
/// Handlers are called in the order that they are added.
///
/// Callers must remove their message handlers using
/// [WindowingOwnerWin32._removeMessageHandler].
void _addMessageHandler(_WindowsMessageHandler handler) {
if (_messageHandlers.contains(handler)) {
return;
}
_messageHandlers.add(handler);
}
/// Unregister a [WindowsMessageHandler].
///
/// If the handler has not been registered, this method has no effect.
void _removeMessageHandler(_WindowsMessageHandler handler) {
_messageHandlers.remove(handler);
}
void _onMessage(ffi.Pointer<_WindowsMessage> message) {
final FlutterView flutterView = WidgetsBinding.instance.platformDispatcher.views.firstWhere(
(FlutterView view) => view.viewId == message.ref.viewId,
);
final int handlesLength = _messageHandlers.length;
for (final _WindowsMessageHandler handler in _messageHandlers) {
assert(
_messageHandlers.length == handlesLength,
'Message handler list changed while processing message: $message',
);
final int? result = handler.handleWindowsMessage(
flutterView,
message.ref.windowHandle,
message.ref.message,
message.ref.wParam,
message.ref.lParam,
);
if (result != null) {
message.ref.handled = true;
message.ref.lResult = result;
return;
}
}
}
}
class _RegularWindowMesageHandler implements _WindowsMessageHandler {
_RegularWindowMesageHandler({required this.controller});
final RegularWindowControllerWin32 controller;
@override
int? handleWindowsMessage(
FlutterView view,
HWND windowHandle,
int message,
int wParam,
int lParam,
) {
return controller._handleWindowsMessage(view, windowHandle, message, wParam, lParam);
}
}
/// Implementation of [RegularWindowController] for the Windows platform.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController], the base class for regular windows.
class RegularWindowControllerWin32 extends RegularWindowController {
/// Creates a new regular window controller for Win32.
///
/// When this constructor completes the native window has been created and
/// has a view associated with it.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [RegularWindowController], the base class for regular windows.
@internal
RegularWindowControllerWin32({
required WindowingOwnerWin32 owner,
required RegularWindowControllerDelegate delegate,
Size? preferredSize,
BoxConstraints? preferredConstraints,
String? title,
}) : _owner = owner,
_delegate = delegate,
super.empty() {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
_handler = _RegularWindowMesageHandler(controller: this);
owner._addMessageHandler(_handler);
final int viewId = _Win32PlatformInterface.createRegularWindow(
_owner.allocator,
WidgetsBinding.instance.platformDispatcher.engineId!,
preferredSize,
preferredConstraints,
title,
);
if (viewId < 0) {
throw Exception('Windows failed to create a regular window with a valid view id.');
}
final FlutterView flutterView = WidgetsBinding.instance.platformDispatcher.views.firstWhere(
(FlutterView view) => view.viewId == viewId,
);
rootView = flutterView;
}
final WindowingOwnerWin32 _owner;
final RegularWindowControllerDelegate _delegate;
late final _RegularWindowMesageHandler _handler;
bool _destroyed = false;
@override
@internal
Size get contentSize {
_ensureNotDestroyed();
final _ActualContentSize size = _Win32PlatformInterface.getWindowContentSize(getWindowHandle());
final result = Size(size.width, size.height);
return result;
}
@override
@internal
String get title {
_ensureNotDestroyed();
return _Win32PlatformInterface.getWindowTitle(_owner.allocator, getWindowHandle());
}
@override
@internal
bool get isActivated {
_ensureNotDestroyed();
return _Win32PlatformInterface.getForegroundWindow() == getWindowHandle();
}
@override
@internal
bool get isMaximized {
_ensureNotDestroyed();
return _Win32PlatformInterface.isZoomed(getWindowHandle()) != 0;
}
@override
@internal
bool get isMinimized {
_ensureNotDestroyed();
return _Win32PlatformInterface.isIconic(getWindowHandle()) != 0;
}
@override
@internal
bool get isFullscreen {
_ensureNotDestroyed();
return _Win32PlatformInterface.getFullscreen(getWindowHandle());
}
@override
@internal
void setSize(Size? size) {
_ensureNotDestroyed();
_Win32PlatformInterface.setWindowContentSize(_owner.allocator, getWindowHandle(), size);
}
@override
@internal
void setConstraints(BoxConstraints constraints) {
_ensureNotDestroyed();
_Win32PlatformInterface.setWindowConstraints(_owner.allocator, getWindowHandle(), constraints);
notifyListeners();
}
@override
@internal
void setTitle(String title) {
_ensureNotDestroyed();
_Win32PlatformInterface.setWindowTitle(_owner.allocator, getWindowHandle(), title);
notifyListeners();
}
@override
@internal
void activate() {
_ensureNotDestroyed();
_Win32PlatformInterface.showWindow(getWindowHandle(), _SW_RESTORE);
}
@override
@internal
void setMaximized(bool maximized) {
_ensureNotDestroyed();
if (maximized) {
_Win32PlatformInterface.showWindow(getWindowHandle(), _SW_MAXIMIZE);
} else {
_Win32PlatformInterface.showWindow(getWindowHandle(), _SW_RESTORE);
}
}
@override
@internal
void setMinimized(bool minimized) {
_ensureNotDestroyed();
if (minimized) {
_Win32PlatformInterface.showWindow(getWindowHandle(), _SW_MINIMIZE);
} else {
_Win32PlatformInterface.showWindow(getWindowHandle(), _SW_RESTORE);
}
}
@override
@internal
void setFullscreen(bool fullscreen, {Display? display}) {
_Win32PlatformInterface.setFullscreen(
_owner.allocator,
getWindowHandle(),
fullscreen,
display: display,
);
}
/// Returns HWND pointer to the top level window.
@internal
HWND getWindowHandle() {
_ensureNotDestroyed();
return _Win32PlatformInterface.getWindowHandle(
WidgetsBinding.instance.platformDispatcher.engineId!,
rootView.viewId,
);
}
void _ensureNotDestroyed() {
if (_destroyed) {
throw StateError('Window has been destroyed.');
}
}
@override
void destroy() {
if (_destroyed) {
return;
}
_Win32PlatformInterface.destroyWindow(getWindowHandle());
_destroyed = true;
}
int? _handleWindowsMessage(
FlutterView view,
HWND windowHandle,
int message,
int wParam,
int lParam,
) {
if (view.viewId != rootView.viewId) {
return null;
}
if (message == _WM_CLOSE) {
_delegate.onWindowCloseRequested(this);
return 0;
} else if (message == _WM_DESTROY) {
_destroyed = true;
_owner._removeMessageHandler(_handler);
_delegate.onWindowDestroyed();
return 0;
} else if (message == _WM_SIZE || message == _WM_ACTIVATE) {
notifyListeners();
}
return null;
}
}
class _DialogWindowMesageHandler implements _WindowsMessageHandler {
_DialogWindowMesageHandler({required this.controller});
final DialogWindowControllerWin32 controller;
@override
int? handleWindowsMessage(
FlutterView view,
HWND windowHandle,
int message,
int wParam,
int lParam,
) {
return controller._handleWindowsMessage(view, windowHandle, message, wParam, lParam);
}
}
/// Implementation of [DialogWindowController] for the Windows platform.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [DialogWindowController], the base class for dialog windows.
class DialogWindowControllerWin32 extends DialogWindowController {
/// Creates a new dialog window controller for Win32.
///
/// When this constructor completes the native window has been created and
/// has a view associated with it.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [DialogWindowController], the base class for dialog windows.
@internal
DialogWindowControllerWin32({
required WindowingOwnerWin32 owner,
required DialogWindowControllerDelegate delegate,
Size? preferredSize,
BoxConstraints? preferredConstraints,
String? title,
BaseWindowController? parent,
}) : _owner = owner,
_delegate = delegate,
_parent = parent,
super.empty() {
if (!isWindowingEnabled) {
throw UnsupportedError(_kWindowingDisabledErrorMessage);
}
_handler = _DialogWindowMesageHandler(controller: this);
owner._addMessageHandler(_handler);
final int viewId = _Win32PlatformInterface.createDialogWindow(
_owner.allocator,
WidgetsBinding.instance.platformDispatcher.engineId!,
preferredSize,
preferredConstraints,
title,
parent != null
? _Win32PlatformInterface.getWindowHandle(
WidgetsBinding.instance.platformDispatcher.engineId!,
parent.rootView.viewId,
)
: null,
);
if (viewId < 0) {
throw Exception('Windows failed to create a dialog window with a valid view id.');
}
final FlutterView flutterView = WidgetsBinding.instance.platformDispatcher.views.firstWhere(
(FlutterView view) => view.viewId == viewId,
);
rootView = flutterView;
}
final WindowingOwnerWin32 _owner;
final DialogWindowControllerDelegate _delegate;
final BaseWindowController? _parent;
late final _DialogWindowMesageHandler _handler;
bool _destroyed = false;
@override
@internal
Size get contentSize {
_ensureNotDestroyed();
final _ActualContentSize size = _Win32PlatformInterface.getWindowContentSize(getWindowHandle());
final result = Size(size.width, size.height);
return result;
}
@override
@internal
String get title {
_ensureNotDestroyed();
return _Win32PlatformInterface.getWindowTitle(_owner.allocator, getWindowHandle());
}
@override
@internal
bool get isActivated {
_ensureNotDestroyed();
return _Win32PlatformInterface.getForegroundWindow() == getWindowHandle();
}
@override
@internal
bool get isMinimized {
_ensureNotDestroyed();
return _Win32PlatformInterface.isIconic(getWindowHandle()) != 0;
}
@override
@internal
void setSize(Size? size) {
_ensureNotDestroyed();
_Win32PlatformInterface.setWindowContentSize(_owner.allocator, getWindowHandle(), size);
// Note that we do not notify the listener when setting the size,
// as that will happen when the WM_SIZE message is received in
// _handleWindowsMessage.
}
@override
@internal
void setConstraints(BoxConstraints constraints) {
_ensureNotDestroyed();
_Win32PlatformInterface.setWindowConstraints(_owner.allocator, getWindowHandle(), constraints);
notifyListeners();
}
@override
@internal
void setTitle(String title) {
_ensureNotDestroyed();
_Win32PlatformInterface.setWindowTitle(_owner.allocator, getWindowHandle(), title);
notifyListeners();
}
@override
@internal
void activate() {
_ensureNotDestroyed();
_Win32PlatformInterface.showWindow(getWindowHandle(), _SW_RESTORE);
}
@override
@internal
void setMinimized(bool minimized) {
if (parent != null) {
return;
}
_ensureNotDestroyed();
if (minimized) {
_Win32PlatformInterface.showWindow(getWindowHandle(), _SW_MINIMIZE);
} else {
_Win32PlatformInterface.showWindow(getWindowHandle(), _SW_RESTORE);
}
}
@override
@internal
BaseWindowController? get parent => _parent;
/// Returns HWND pointer to the top level window.
@internal
HWND getWindowHandle() {
_ensureNotDestroyed();
return _Win32PlatformInterface.getWindowHandle(
WidgetsBinding.instance.platformDispatcher.engineId!,
rootView.viewId,
);
}
void _ensureNotDestroyed() {
if (_destroyed) {
throw StateError('Window has been destroyed.');
}
}
@override
void destroy() {
if (_destroyed) {
return;
}
_Win32PlatformInterface.destroyWindow(getWindowHandle());
}
int? _handleWindowsMessage(
FlutterView view,
HWND windowHandle,
int message,
int wParam,
int lParam,
) {
if (view.viewId != rootView.viewId) {
return null;
}
if (message == _WM_CLOSE) {
_delegate.onWindowCloseRequested(this);
return 0;
} else if (message == _WM_DESTROY) {
_destroyed = true;
_owner._removeMessageHandler(_handler);
_delegate.onWindowDestroyed();
return 0;
} else if (message == _WM_SIZE || message == _WM_ACTIVATE) {
notifyListeners();
}
return null;
}
}
typedef _GetWindowPositionNative =
ffi.Pointer<_Rect> Function(
ffi.Pointer<_Size> childSize,
ffi.Pointer<_Rect> parentRect,
ffi.Pointer<_Rect> outputRect,
);
/// Implementation of [TooltipWindowController] for the Windows platform.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
///
/// * [TooltipWindowController], the base class for tooltip windows.
class TooltipWindowControllerWin32 extends TooltipWindowController
implements _WindowsMessageHandler {
/// Creates a new tooltip window controller for Win32.
///
/// When this constructor completes, the native window has been created and
/// has a view associated with it.
///
/// {@macro flutter.widgets.windowing.experimental}
///
/// See also:
/// * [TooltipWindowController], the base class for tooltip windows.
@internal
TooltipWindowControllerWin32({
required WindowingOwnerWin32 owner,
required TooltipWindowControllerDelegate delegate,
required BoxConstraints contentSizeConstraints,
required bool isSizedToContent,
required BaseWindowController parent,
required Rect anchorRect,
required WindowPositioner positioner,
}) : _delegate = delegate,
_owner = owner,
_parent = parent,
_anchorRect = anchorRect,
_positioner = positioner,
super.empty() {
_owner._addMessageHandler(this);
_onGetWindowPosition = ffi.NativeCallable<_GetWindowPositionNative>.isolateLocal(
_handleGetWindowPosition,
);
final int viewId = _Win32PlatformInterface.createTooltipWindow(
_owner.allocator,
PlatformDispatcher.instance.engineId!,
contentSizeConstraints,
isSizedToContent,
_Win32PlatformInterface.getWindowHandle(
PlatformDispatcher.instance.engineId!,
parent.rootView.viewId,
),
_onGetWindowPosition.nativeFunction,
);
if (viewId < 0) {
throw Exception('Windows failed to create a tooltip window with a valid view id.');
}
final FlutterView flutterView = PlatformDispatcher.instance.views.firstWhere(
(FlutterView view) => view.viewId == viewId,
);
rootView = flutterView;
}
final WindowingOwnerWin32 _owner;
final TooltipWindowControllerDelegate _delegate;
final BaseWindowController _parent;
WindowPositioner _positioner;
Rect _anchorRect;
bool _destroyed = false;
ffi.Pointer<_Rect> _handleGetWindowPosition(
ffi.Pointer<_Size> childSize,
ffi.Pointer<_Rect> parentRect,
ffi.Pointer<_Rect> outputRect,
) {
final ffi.Pointer<_Rect> result = _owner.allocator<_Rect>();
final double scale = PlatformDispatcher.instance.views
.firstWhere((FlutterView view) => view.viewId == rootView.viewId)
.devicePixelRatio;
final scaledAnchorRect = Rect.fromLTWH(
_anchorRect.left * scale,
_anchorRect.top * scale,
_anchorRect.width * scale,
_anchorRect.height * scale,
);
final Offset scaledOffset = _positioner.offset * scale;
final WindowPositioner scaledPositioner = _positioner.copyWith(offset: scaledOffset);
final Rect targetRect = scaledPositioner.placeWindow(
childSize: childSize.ref.toSize(),
anchorRect: scaledAnchorRect.translate(
parentRect.ref.left.toDouble(),
parentRect.ref.top.toDouble(),
),
parentRect: parentRect.ref.toRect(),
displayRect: outputRect.ref.toRect(),
);
result.ref.left = targetRect.left.toInt();
result.ref.top = targetRect.top.toInt();
result.ref.width = targetRect.width.toInt();
result.ref.height = targetRect.height.toInt();
return result;
}
/// Returns HWND pointer to the top level window.
@internal
HWND getWindowHandle() {
_ensureNotDestroyed();
return _Win32PlatformInterface.getWindowHandle(
PlatformDispatcher.instance.engineId!,
rootView.viewId,
);
}
@override
Size get contentSize {
_ensureNotDestroyed();
final _ActualContentSize size = _Win32PlatformInterface.getWindowContentSize(getWindowHandle());
return Size(size.width, size.height);
}
void _ensureNotDestroyed() {
if (_destroyed) {
throw StateError('Window has been destroyed.');
}
}
@override
void destroy() {
if (_destroyed) {
return;
}
_Win32PlatformInterface.destroyWindow(getWindowHandle());
_destroyed = true;
}
@override
void updatePosition({Rect? anchorRect, WindowPositioner? positioner}) {
if (anchorRect != null) {
_anchorRect = anchorRect;
}
if (positioner != null) {
_positioner = positioner;
}
_Win32PlatformInterface.updateTooltipWindowPosition(getWindowHandle());
}
late final ffi.NativeCallable<_GetWindowPositionNative> _onGetWindowPosition;
@override
int? handleWindowsMessage(
FlutterView view,
HWND windowHandle,
int message,
int wParam,
int lParam,
) {
if (view.viewId == parent.rootView.viewId) {
if (message == _WM_SIZE) {
// Tooltips should close when their parent window is resized.
// Queue the destroy on a microtask to avoid destroying the window
// while processing its message.
scheduleMicrotask(destroy);
} else if (message == _WM_ACTIVATE && wParam == _WA_INACTIVE) {
// Tooltips should close when their parent window is deactivated.
// Queue the destroy on a microtask to avoid destroying the window
// while processing its message.
scheduleMicrotask(destroy);
}
return null;
}
if (view.viewId != rootView.viewId) {
return null;
}
if (message == _WM_DESTROY) {
_destroyed = true;
_onGetWindowPosition.close();
_owner._removeMessageHandler(this);
_delegate.onWindowDestroyed();
return 0;
}
return null;
}
@override
BaseWindowController get parent => _parent;
@override
void setConstraints(BoxConstraints constraints) {}
}
final class _Size extends ffi.Struct {
@ffi.Int32()
external int width;
@ffi.Int32()
external int height;
@override
String toString() {
return 'Size(width: $width, height: $height)';
}
Size toSize() {
return Size(width.toDouble(), height.toDouble());
}
}
final class _Rect extends ffi.Struct {
@ffi.Int32()
external int left;
@ffi.Int32()
external int top;
@ffi.Int32()
external int width;
@ffi.Int32()
external int height;
Rect toRect() {
return Rect.fromLTWH(left.toDouble(), top.toDouble(), width.toDouble(), height.toDouble());
}
@override
String toString() {
return 'Rect(left: $left, top: $top, width: $width, height: $height)';
}
}
class _Win32PlatformInterface {
static void initializeWindowing(
ffi.Allocator allocator,
int engineId,
void Function(ffi.Pointer<_WindowsMessage>) onMessage,
) {
final ffi.Pointer<_WindowingInitRequest> request = allocator<_WindowingInitRequest>();
try {
request.ref.onMessage =
ffi.NativeCallable<ffi.Void Function(ffi.Pointer<_WindowsMessage>)>.isolateLocal(
onMessage,
).nativeFunction;
_initializeWindowing(engineId, request);
} finally {
allocator.free(request);
}
}
@ffi.Native<ffi.Void Function(ffi.Int64, ffi.Pointer<_WindowingInitRequest>)>(
symbol: 'InternalFlutterWindows_WindowManager_Initialize',
)
external static void _initializeWindowing(
int engineId,
ffi.Pointer<_WindowingInitRequest> request,
);
static int createRegularWindow(
ffi.Allocator allocator,
int engineId,
Size? preferredSize,
BoxConstraints? preferredConstraints,
String? title,
) {
final ffi.Pointer<_RegularWindowCreationRequest> request =
allocator<_RegularWindowCreationRequest>();
try {
request.ref.preferredSize.from(preferredSize);
request.ref.preferredConstraints.from(preferredConstraints);
request.ref.title = (title ?? 'Regular window').toNativeUtf16(allocator: allocator);
return _createRegularWindow(engineId, request);
} finally {
allocator.free(request);
}
}
@ffi.Native<ffi.Int64 Function(ffi.Int64, ffi.Pointer<_RegularWindowCreationRequest>)>(
symbol: 'InternalFlutterWindows_WindowManager_CreateRegularWindow',
)
external static int _createRegularWindow(
int engineId,
ffi.Pointer<_RegularWindowCreationRequest> request,
);
static int createDialogWindow(
ffi.Allocator allocator,
int engineId,
Size? preferredSize,
BoxConstraints? preferredConstraints,
String? title,
HWND? parent,
) {
final ffi.Pointer<_DialogWindowCreationRequest> request =
allocator<_DialogWindowCreationRequest>();
try {
request.ref.preferredSize.from(preferredSize);
request.ref.preferredConstraints.from(preferredConstraints);
request.ref.title = (title ?? 'Dialog window').toNativeUtf16(allocator: allocator);
request.ref.parentOrNull = parent ?? ffi.Pointer<ffi.Void>.fromAddress(0);
return _createDialogWindow(engineId, request);
} finally {
allocator.free(request);
}
}
@ffi.Native<ffi.Int64 Function(ffi.Int64, ffi.Pointer<_DialogWindowCreationRequest>)>(
symbol: 'InternalFlutterWindows_WindowManager_CreateDialogWindow',
)
external static int _createDialogWindow(
int engineId,
ffi.Pointer<_DialogWindowCreationRequest> request,
);
static int createTooltipWindow(
ffi.Allocator allocator,
int engineId,
BoxConstraints preferredConstraints,
bool isSizedToContent,
HWND parent,
ffi.Pointer<
ffi.NativeFunction<
ffi.Pointer<_Rect> Function(
ffi.Pointer<_Size> childSize,
ffi.Pointer<_Rect> parentRect,
ffi.Pointer<_Rect> outputRect,
)
>
>
onGetWindowPosition,
) {
final ffi.Pointer<_TooltipWindowCreationRequest> request =
allocator<_TooltipWindowCreationRequest>();
try {
request.ref.preferredConstraints.from(preferredConstraints);
request.ref.isSizedToContent = isSizedToContent;
request.ref.parent = parent;
request.ref.onGetWindowPosition = onGetWindowPosition;
return _createTooltipWindow(engineId, request);
} finally {
allocator.free(request);
}
}
@ffi.Native<ffi.Int64 Function(ffi.Int64, ffi.Pointer<_TooltipWindowCreationRequest>)>(
symbol: 'InternalFlutterWindows_WindowManager_CreateTooltipWindow',
)
external static int _createTooltipWindow(
int engineId,
ffi.Pointer<_TooltipWindowCreationRequest> request,
);
@ffi.Native<HWND Function(ffi.Int64, ffi.Int64)>(
symbol: 'InternalFlutterWindows_WindowManager_GetTopLevelWindowHandle',
)
external static HWND getWindowHandle(int engineId, int viewId);
@ffi.Native<ffi.Void Function(HWND)>(
symbol: 'InternalFlutterWindows_WindowManager_OnDestroyWindow',
)
external static void destroyWindow(HWND windowHandle);
@ffi.Native<_ActualContentSize Function(HWND)>(
symbol: 'InternalFlutterWindows_WindowManager_GetWindowContentSize',
)
external static _ActualContentSize getWindowContentSize(HWND windowHandle);
static void setWindowTitle(ffi.Allocator allocator, HWND windowHandle, String title) {
final ffi.Pointer<_Utf16> titlePointer = title.toNativeUtf16(allocator: allocator);
try {
_setWindowTitle(windowHandle, titlePointer);
} finally {
allocator.free(titlePointer);
}
}
@ffi.Native<ffi.Void Function(HWND, ffi.Pointer<_Utf16>)>(symbol: 'SetWindowTextW')
external static void _setWindowTitle(HWND windowHandle, ffi.Pointer<_Utf16> title);
static void setWindowContentSize(ffi.Allocator allocator, HWND windowHandle, Size? size) {
final ffi.Pointer<_WindowSizeRequest> request = allocator<_WindowSizeRequest>();
try {
request.ref.from(size);
_setWindowContentSize(windowHandle, request);
} finally {
allocator.free(request);
}
}
@ffi.Native<ffi.Void Function(HWND, ffi.Pointer<_WindowSizeRequest>)>(
symbol: 'InternalFlutterWindows_WindowManager_SetWindowSize',
)
external static void _setWindowContentSize(
HWND windowHandle,
ffi.Pointer<_WindowSizeRequest> size,
);
static void setWindowConstraints(
ffi.Allocator allocator,
HWND windowHandle,
BoxConstraints? constraints,
) {
final ffi.Pointer<_WindowConstraintsRequest> request = allocator<_WindowConstraintsRequest>();
try {
request.ref.from(constraints);
_setWindowConstraints(windowHandle, request);
} finally {
allocator.free(request);
}
}
@ffi.Native<ffi.Void Function(HWND, ffi.Pointer<_WindowConstraintsRequest>)>(
symbol: 'InternalFlutterWindows_WindowManager_SetWindowConstraints',
)
external static void _setWindowConstraints(
HWND windowHandle,
ffi.Pointer<_WindowConstraintsRequest> constraints,
);
@ffi.Native<ffi.Void Function(HWND, ffi.Int32)>(symbol: 'ShowWindow')
external static void showWindow(HWND windowHandle, int command);
@ffi.Native<ffi.Int32 Function(HWND)>(symbol: 'IsIconic')
external static int isIconic(HWND windowHandle);
@ffi.Native<ffi.Int32 Function(HWND)>(symbol: 'IsZoomed')
external static int isZoomed(HWND windowHandle);
static void setFullscreen(
ffi.Allocator allocator,
HWND windowHandle,
bool fullscreen, {
Display? display,
}) {
final ffi.Pointer<_WindowFullscreenRequest> request = allocator<_WindowFullscreenRequest>();
try {
request.ref.fullscreen = fullscreen;
request.ref.hasDisplayId = display != null;
request.ref.displayId = display?.id ?? 0;
_setFullscreen(windowHandle, request);
} finally {
allocator.free(request);
}
}
@ffi.Native<ffi.Void Function(HWND, ffi.Pointer<_WindowFullscreenRequest>)>(
symbol: 'InternalFlutterWindows_WindowManager_SetFullscreen',
)
external static void _setFullscreen(
HWND windowHandle,
ffi.Pointer<_WindowFullscreenRequest> request,
);
@ffi.Native<ffi.Bool Function(HWND)>(symbol: 'InternalFlutterWindows_WindowManager_GetFullscreen')
external static bool getFullscreen(HWND windowHandle);
@ffi.Native<ffi.Int32 Function(HWND)>(symbol: 'GetWindowTextLengthW')
external static int _getWindowTextLength(HWND windowHandle);
@ffi.Native<ffi.Int32 Function(HWND, ffi.Pointer<_Utf16>, ffi.Int32)>(symbol: 'GetWindowTextW')
external static int _getWindowText(
HWND windowHandle,
ffi.Pointer<_Utf16> lpString,
int maxLength,
);
static String getWindowTitle(ffi.Allocator allocator, HWND windowHandle) {
final int length = _getWindowTextLength(windowHandle);
if (length == 0) {
return '';
}
final ffi.Pointer<ffi.Uint16> data = allocator<ffi.Uint16>(length + 1);
try {
final ffi.Pointer<_Utf16> buffer = data.cast<_Utf16>();
_getWindowText(windowHandle, buffer, length + 1);
return buffer.toDartString();
} finally {
allocator.free(data);
}
}
@ffi.Native<HWND Function()>(symbol: 'GetForegroundWindow')
external static HWND getForegroundWindow();
@ffi.Native<ffi.Void Function(HWND)>(
symbol: 'InternalFlutterWindows_WindowManager_UpdateTooltipPosition',
)
external static void updateTooltipWindowPosition(HWND windowHandle);
}
/// Payload for the creation method used by [_Win32PlatformInterface.createRegularWindow].
final class _RegularWindowCreationRequest extends ffi.Struct {
external _WindowSizeRequest preferredSize;
external _WindowConstraintsRequest preferredConstraints;
external ffi.Pointer<_Utf16> title;
}
/// Payload for the creation method used by [_Win32PlatformInterface.createDialogWindow].
final class _DialogWindowCreationRequest extends ffi.Struct {
external _WindowSizeRequest preferredSize;
external _WindowConstraintsRequest preferredConstraints;
external ffi.Pointer<_Utf16> title;
external HWND parentOrNull;
}
final class _TooltipWindowCreationRequest extends ffi.Struct {
external _WindowConstraintsRequest preferredConstraints;
@ffi.Bool()
external bool isSizedToContent;
external HWND parent;
external ffi.Pointer<
ffi.NativeFunction<
ffi.Pointer<_Rect> Function(
ffi.Pointer<_Size> childSize,
ffi.Pointer<_Rect> parentRect,
ffi.Pointer<_Rect> outputRect,
)
>
>
onGetWindowPosition;
}
/// Payload for the initialization request for the windowing subsystem used
/// by the constructor for [WindowingOwnerWin32].
final class _WindowingInitRequest extends ffi.Struct {
external ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<_WindowsMessage>)>>
onMessage;
}
/// Payload for the size of a window used by [_RegularWindowCreationRequest] and
/// [_Win32PlatformInterface.setWindowContentSize].
final class _WindowSizeRequest extends ffi.Struct {
@ffi.Bool()
external bool hasSize;
@ffi.Double()
external double width;
@ffi.Double()
external double height;
void from(Size? size) {
hasSize = size != null;
width = size?.width ?? 0;
height = size?.height ?? 0;
}
}
/// Payload for the constraints of a window used by [_RegularWindowCreationRequest] and
/// [_Win32PlatformInterface.setWindowConstraints].
final class _WindowConstraintsRequest extends ffi.Struct {
@ffi.Bool()
external bool hasConstraints;
@ffi.Double()
external double minWidth;
@ffi.Double()
external double minHeight;
@ffi.Double()
external double maxWidth;
@ffi.Double()
external double maxHeight;
void from(BoxConstraints? constraints) {
hasConstraints = constraints != null;
minWidth = constraints?.minWidth ?? 0;
minHeight = constraints?.minHeight ?? 0;
maxWidth = constraints?.maxWidth ?? double.maxFinite;
maxHeight = constraints?.maxHeight ?? double.maxFinite;
}
}
/// A message received for all toplevel windows, used by [_WindowingInitRequest].
final class _WindowsMessage extends ffi.Struct {
@ffi.Int64()
external int viewId;
external HWND windowHandle;
@ffi.Int32()
external int message;
@ffi.Int64()
external int wParam;
@ffi.Int64()
external int lParam;
@ffi.Int64()
external int lResult;
@ffi.Bool()
external bool handled;
}
/// Holds the real size of a window as retrieved from
/// [_Win32PlatformInterface.getWindowContentSize].
final class _ActualContentSize extends ffi.Struct {
@ffi.Double()
external double width;
@ffi.Double()
external double height;
}
/// Payload for the [_Win32PlatformInterface.setFullscreen] request.
final class _WindowFullscreenRequest extends ffi.Struct {
@ffi.Bool()
external bool fullscreen;
@ffi.Bool()
external bool hasDisplayId;
@ffi.Uint64()
external int displayId;
}
/// The contents of a native zero-terminated array of UTF-16 code units.
///
/// The Utf16 type itself has no functionality, it's only intended to be used
/// through a `Pointer<Utf16>` representing the entire array. This pointer is
/// the equivalent of a char pointer (`const wchar_t*`) in C code. The
/// individual UTF-16 code units are stored in native byte order.
final class _Utf16 extends ffi.Opaque {}
/// Extension method for converting a`Pointer<Utf16>` to a [String].
extension _Utf16Pointer on ffi.Pointer<_Utf16> {
/// Converts this UTF-16 encoded string to a Dart string.
///
/// Decodes the UTF-16 code units of this zero-terminated code unit array as
/// Unicode code points and creates a Dart string containing those code
/// points.
///
/// If [length] is provided, zero-termination is ignored and the result can
/// contain NUL characters.
///
/// If [length] is not provided, the returned string is the string up til
/// but not including the first NUL character.
String toDartString({int? length}) {
_ensureNotNullptr('toDartString');
final ffi.Pointer<ffi.Uint16> codeUnits = cast<ffi.Uint16>();
if (length == null) {
return _toUnknownLengthString(codeUnits);
} else {
RangeError.checkNotNegative(length, 'length');
return _toKnownLengthString(codeUnits, length);
}
}
static String _toKnownLengthString(ffi.Pointer<ffi.Uint16> codeUnits, int length) =>
String.fromCharCodes(codeUnits.asTypedList(length));
static String _toUnknownLengthString(ffi.Pointer<ffi.Uint16> codeUnits) {
final buffer = StringBuffer();
var i = 0;
while (true) {
final int char = (codeUnits + i).value;
if (char == 0) {
return buffer.toString();
}
buffer.writeCharCode(char);
i++;
}
}
void _ensureNotNullptr(String operation) {
if (this == ffi.nullptr) {
throw UnsupportedError("Operation '$operation' not allowed on a 'nullptr'.");
}
}
}
/// Extension method for converting a [String] to a `Pointer<Utf16>`.
extension _StringUtf16Pointer on String {
/// Creates a zero-terminated [Utf16] code-unit array from this String.
///
/// If this [String] contains NUL characters, converting it back to a string
/// using [Utf16Pointer.toDartString] will truncate the result if a length is
/// not passed.
///
/// Returns an [allocator]-allocated pointer to the result.
ffi.Pointer<_Utf16> toNativeUtf16({required ffi.Allocator allocator}) {
final List<int> units = codeUnits;
final ffi.Pointer<ffi.Uint16> result = allocator<ffi.Uint16>(units.length + 1);
final Uint16List nativeString = result.asTypedList(units.length + 1);
nativeString.setRange(0, units.length, units);
nativeString[units.length] = 0;
return result.cast();
}
}
typedef _WinCoTaskMemAllocNative = ffi.Pointer<ffi.NativeType> Function(ffi.Size);
typedef _WinCoTaskMemAlloc = ffi.Pointer<ffi.NativeType> Function(int);
typedef _WinCoTaskMemFreeNative = ffi.Void Function(ffi.Pointer<ffi.NativeType>);
typedef _WinCoTaskMemFree = void Function(ffi.Pointer<ffi.NativeType>);
final class _CallocAllocator implements ffi.Allocator {
_CallocAllocator() {
_ole32lib = ffi.DynamicLibrary.open('ole32.dll');
_winCoTaskMemAlloc = _ole32lib.lookupFunction<_WinCoTaskMemAllocNative, _WinCoTaskMemAlloc>(
'CoTaskMemAlloc',
);
_winCoTaskMemFreePointer = _ole32lib.lookup('CoTaskMemFree');
_winCoTaskMemFree = _winCoTaskMemFreePointer.asFunction();
}
late final ffi.DynamicLibrary _ole32lib;
late final _WinCoTaskMemAlloc _winCoTaskMemAlloc;
late final ffi.Pointer<ffi.NativeFunction<_WinCoTaskMemFreeNative>> _winCoTaskMemFreePointer;
late final _WinCoTaskMemFree _winCoTaskMemFree;
/// Fills a block of memory with a specified value.
// ignore: always_specify_types
void _fillMemory(ffi.Pointer destination, int length, int fill) {
final ffi.Pointer<ffi.Uint8> ptr = destination.cast<ffi.Uint8>();
for (var i = 0; i < length; i++) {
ptr[i] = fill;
}
}
/// Fills a block of memory with zeros.
// ignore: always_specify_types
void _zeroMemory(ffi.Pointer destination, int length) => _fillMemory(destination, length, 0);
/// Allocates [byteCount] bytes of zero-initialized of memory on the native
/// heap.
@override
ffi.Pointer<T> allocate<T extends ffi.NativeType>(int byteCount, {int? alignment}) {
ffi.Pointer<T> result;
result = _winCoTaskMemAlloc(byteCount).cast();
if (result.address == 0) {
throw ArgumentError('Could not allocate $byteCount bytes.');
}
if (Platform.isWindows) {
_zeroMemory(result, byteCount);
}
return result;
}
/// Releases memory allocated on the native heap.
@override
// ignore: always_specify_types
void free(ffi.Pointer pointer) {
_winCoTaskMemFree(pointer);
}
/// Returns a pointer to a native free function.
ffi.Pointer<ffi.NativeFinalizerFunction> get nativeFree => _winCoTaskMemFreePointer;
}