diff --git a/flutter_inappwebview/example/lib/main.dart b/flutter_inappwebview/example/lib/main.dart index da75e5b6..d94bc1b5 100755 --- a/flutter_inappwebview/example/lib/main.dart +++ b/flutter_inappwebview/example/lib/main.dart @@ -71,41 +71,6 @@ PointerInterceptor myDrawer({required BuildContext context}) { }, ) ]; - } else if (defaultTargetPlatform == TargetPlatform.macOS || - defaultTargetPlatform == TargetPlatform.windows || - defaultTargetPlatform == TargetPlatform.linux) { - children = [ - // ListTile( - // title: Text('InAppWebView'), - // onTap: () { - // Navigator.pushReplacementNamed(context, '/'); - // }, - // ), - // ListTile( - // title: Text('InAppBrowser'), - // onTap: () { - // Navigator.pushReplacementNamed(context, '/InAppBrowser'); - // }, - // ), - ListTile( - title: Text('InAppBrowser'), - onTap: () { - Navigator.pushReplacementNamed(context, '/'); - }, - ), - ListTile( - title: Text('WebAuthenticationSession'), - onTap: () { - Navigator.pushReplacementNamed(context, '/WebAuthenticationSession'); - }, - ), - ListTile( - title: Text('HeadlessInAppWebView'), - onTap: () { - Navigator.pushReplacementNamed(context, '/HeadlessInAppWebView'); - }, - ), - ]; } else if (defaultTargetPlatform == TargetPlatform.macOS) { children = [ // ListTile( @@ -142,22 +107,16 @@ PointerInterceptor myDrawer({required BuildContext context}) { } else if (defaultTargetPlatform == TargetPlatform.windows || defaultTargetPlatform == TargetPlatform.linux) { children = [ - // ListTile( - // title: Text('InAppWebView'), - // onTap: () { - // Navigator.pushReplacementNamed(context, '/'); - // }, - // ), - // ListTile( - // title: Text('InAppBrowser'), - // onTap: () { - // Navigator.pushReplacementNamed(context, '/InAppBrowser'); - // }, - // ), + ListTile( + title: Text('InAppWebView'), + onTap: () { + Navigator.pushReplacementNamed(context, '/'); + }, + ), ListTile( title: Text('InAppBrowser'), onTap: () { - Navigator.pushReplacementNamed(context, '/'); + Navigator.pushReplacementNamed(context, '/InAppBrowser'); }, ), ListTile( @@ -219,12 +178,11 @@ class _MyAppState extends State { '/WebAuthenticationSession': (context) => WebAuthenticationSessionExampleScreen(), }); - } else if (defaultTargetPlatform == TargetPlatform.windows || + } else if (defaultTargetPlatform == TargetPlatform.windows || defaultTargetPlatform == TargetPlatform.linux) { return MaterialApp(initialRoute: '/', routes: { - // '/': (context) => InAppWebViewExampleScreen(), - // '/InAppBrowser': (context) => InAppBrowserExampleScreen(), - '/': (context) => InAppBrowserExampleScreen(), + '/': (context) => InAppWebViewExampleScreen(), + '/InAppBrowser': (context) => InAppBrowserExampleScreen(), '/HeadlessInAppWebView': (context) => HeadlessInAppWebViewExampleScreen(), }); diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart index aa9b525b..acf5da2d 100755 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart @@ -372,6 +372,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future show() { throw UnimplementedError('show is not implemented on the current platform'); @@ -384,6 +385,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future hide() { throw UnimplementedError('hide is not implemented on the current platform'); @@ -396,6 +398,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future close() { throw UnimplementedError( @@ -409,6 +412,7 @@ abstract class PlatformInAppBrowser extends PlatformInterface ///- Android native WebView ///- iOS ///- MacOS + ///- Windows ///{@endtemplate} Future isHidden() { throw UnimplementedError( @@ -964,6 +968,7 @@ abstract class PlatformInAppBrowserEvents { ///- Android native WebView ([Official API - WebChromeClient.onReceivedTitle](https://developer.android.com/reference/android/webkit/WebChromeClient#onReceivedTitle(android.webkit.WebView,%20java.lang.String))) ///- iOS ///- MacOS + ///- Windows ([Official API - IWebView2WebView.add_DocumentTitleChanged](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#add_documenttitlechanged)) void onTitleChanged(String? title) {} ///Event fired to respond to the results of an over-scroll operation. diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_controller.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_controller.dart index f9ce118b..a8f2d9ba 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_controller.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_controller.dart @@ -134,7 +134,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.url](https://developer.apple.com/documentation/webkit/wkwebview/1415005-url)) ///- MacOS ([Official API - WKWebView.url](https://developer.apple.com/documentation/webkit/wkwebview/1415005-url)) ///- Web - ///- Windows ([Official API - IWebView2WebView.get_Source](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/iwebview2webview?view=webview2-0.8.355#get_source)) + ///- Windows ([Official API - IWebView2WebView.get_Source](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#get_source)) ///{@endtemplate} Future getUrl() { throw UnimplementedError( @@ -151,6 +151,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.title](https://developer.apple.com/documentation/webkit/wkwebview/1415015-title)) ///- MacOS ([Official API - WKWebView.title](https://developer.apple.com/documentation/webkit/wkwebview/1415015-title)) ///- Web + ///- Windows ([Official API - IWebView2WebView.get_DocumentTitle](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#get_documenttitle)) ///{@endtemplate} Future getTitle() { throw UnimplementedError( @@ -345,6 +346,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.reload](https://developer.apple.com/documentation/webkit/wkwebview/1414969-reload)) ///- MacOS ([Official API - WKWebView.reload](https://developer.apple.com/documentation/webkit/wkwebview/1414969-reload)) ///- Web ([Official API - Location.reload](https://developer.mozilla.org/en-US/docs/Web/API/Location/reload)) + ///- Windows ([Official API - IWebView2WebView.Reload](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#reload)) ///{@endtemplate} Future reload() { throw UnimplementedError( @@ -361,6 +363,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.goBack](https://developer.apple.com/documentation/webkit/wkwebview/1414952-goback)) ///- MacOS ([Official API - WKWebView.goBack](https://developer.apple.com/documentation/webkit/wkwebview/1414952-goback)) ///- Web ([Official API - History.back](https://developer.mozilla.org/en-US/docs/Web/API/History/back)) + ///- Windows ([Official API - IWebView2WebView.GoBack](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#goback)) ///{@endtemplate} Future goBack() { throw UnimplementedError( @@ -390,6 +393,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.goForward](https://developer.apple.com/documentation/webkit/wkwebview/1414993-goforward)) ///- MacOS ([Official API - WKWebView.goForward](https://developer.apple.com/documentation/webkit/wkwebview/1414993-goforward)) ///- Web ([Official API - History.forward](https://developer.mozilla.org/en-US/docs/Web/API/History/forward)) + ///- Windows ([Official API - IWebView2WebView.GoForward](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#goforward)) ///{@endtemplate} Future goForward() { throw UnimplementedError( @@ -492,7 +496,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///Those changes remain visible to all scripts, regardless of which content world you specify. ///For more information about content worlds, see [ContentWorld]. ///Available on iOS 14.0+ and MacOS 11.0+. - ///**NOTE**: not used on Web. + ///**NOTE**: not used on Web and on Windows platforms. /// ///**NOTE**: This method shouldn't be called in the [PlatformWebViewCreationParams.onWebViewCreated] or [PlatformWebViewCreationParams.onLoadStart] events, ///because, in these events, the `WebView` is not ready to handle it yet. @@ -506,6 +510,7 @@ abstract class PlatformInAppWebViewController extends PlatformInterface ///- iOS ([Official API - WKWebView.evaluateJavascript](https://developer.apple.com/documentation/webkit/wkwebview/3656442-evaluatejavascript)) ///- MacOS ([Official API - WKWebView.evaluateJavascript](https://developer.apple.com/documentation/webkit/wkwebview/3656442-evaluatejavascript)) ///- Web ([Official API - Window.eval](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval?retiredLocale=it)) + ///- Windows ([Official API - IWebView2WebView.ExecuteScript](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.2210.55#executescript)) ///{@endtemplate} Future evaluateJavascript( {required String source, ContentWorld? contentWorld}) { diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_widget.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_widget.dart index 6f926191..c79090a6 100644 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_widget.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/platform_inappwebview_widget.dart @@ -192,6 +192,7 @@ class PlatformInAppWebViewWidgetCreationParams ///- Android native WebView ///- iOS ///- Web +///- Windows ///{@endtemplate} abstract class PlatformInAppWebViewWidget extends PlatformInterface implements Disposable { diff --git a/flutter_inappwebview_windows/lib/src/cookie_manager.dart b/flutter_inappwebview_windows/lib/src/cookie_manager.dart index bf9d5a9b..a95dc409 100644 --- a/flutter_inappwebview_windows/lib/src/cookie_manager.dart +++ b/flutter_inappwebview_windows/lib/src/cookie_manager.dart @@ -152,7 +152,7 @@ class MacOSCookieManager extends PlatformCookieManager with ChannelController { final setCookieCompleter = Completer(); final headlessWebView = - MacOSHeadlessInAppWebView(MacOSHeadlessInAppWebViewCreationParams( + WindowsHeadlessInAppWebView(WindowsHeadlessInAppWebViewCreationParams( initialUrlRequest: URLRequest(url: url), onLoadStop: (controller, url) async { await controller.evaluateJavascript( @@ -234,7 +234,7 @@ class MacOSCookieManager extends PlatformCookieManager with ChannelController { final pageLoaded = Completer(); final headlessWebView = - MacOSHeadlessInAppWebView(MacOSHeadlessInAppWebViewCreationParams( + WindowsHeadlessInAppWebView(WindowsHeadlessInAppWebViewCreationParams( initialUrlRequest: URLRequest(url: url), onLoadStop: (controller, url) async { pageLoaded.complete(); diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart new file mode 100644 index 00000000..1f41ab12 --- /dev/null +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/custom_platform_view.dart @@ -0,0 +1,414 @@ +import 'package:flutter/services.dart'; +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +const Map _cursors = { + 'none': SystemMouseCursors.none, + 'basic': SystemMouseCursors.basic, + 'click': SystemMouseCursors.click, + 'forbidden': SystemMouseCursors.forbidden, + 'wait': SystemMouseCursors.wait, + 'progress': SystemMouseCursors.progress, + 'contextMenu': SystemMouseCursors.contextMenu, + 'help': SystemMouseCursors.help, + 'text': SystemMouseCursors.text, + 'verticalText': SystemMouseCursors.verticalText, + 'cell': SystemMouseCursors.cell, + 'precise': SystemMouseCursors.precise, + 'move': SystemMouseCursors.move, + 'grab': SystemMouseCursors.grab, + 'grabbing': SystemMouseCursors.grabbing, + 'noDrop': SystemMouseCursors.noDrop, + 'alias': SystemMouseCursors.alias, + 'copy': SystemMouseCursors.copy, + 'disappearing': SystemMouseCursors.disappearing, + 'allScroll': SystemMouseCursors.allScroll, + 'resizeLeftRight': SystemMouseCursors.resizeLeftRight, + 'resizeUpDown': SystemMouseCursors.resizeUpDown, + 'resizeUpLeftDownRight': SystemMouseCursors.resizeUpLeftDownRight, + 'resizeUpRightDownLeft': SystemMouseCursors.resizeUpRightDownLeft, + 'resizeUp': SystemMouseCursors.resizeUp, + 'resizeDown': SystemMouseCursors.resizeDown, + 'resizeLeft': SystemMouseCursors.resizeLeft, + 'resizeRight': SystemMouseCursors.resizeRight, + 'resizeUpLeft': SystemMouseCursors.resizeUpLeft, + 'resizeUpRight': SystemMouseCursors.resizeUpRight, + 'resizeDownLeft': SystemMouseCursors.resizeDownLeft, + 'resizeDownRight': SystemMouseCursors.resizeDownRight, + 'resizeColumn': SystemMouseCursors.resizeColumn, + 'resizeRow': SystemMouseCursors.resizeRow, + 'zoomIn': SystemMouseCursors.zoomIn, + 'zoomOut': SystemMouseCursors.zoomOut, +}; + +SystemMouseCursor _getCursorByName(String name) => + _cursors[name] ?? SystemMouseCursors.basic; + +/// Pointer button type +// Order must match InAppWebViewPointerEventKind (see in_app_webview.h) +enum PointerButton { none, primary, secondary, tertiary } + +/// Pointer Event kind +// Order must match InAppWebViewPointerEventKind (see in_app_webview.h) +enum InAppWebViewPointerEventKind { activate, down, enter, leave, up, update } + +/// Attempts to translate a button constant such as [kPrimaryMouseButton] +/// to a [PointerButton] +PointerButton _getButton(int value) { + switch (value) { + case kPrimaryMouseButton: + return PointerButton.primary; + case kSecondaryMouseButton: + return PointerButton.secondary; + case kTertiaryButton: + return PointerButton.tertiary; + default: + return PointerButton.none; + } +} + +const String _pluginChannelPrefix = 'com.pichillilorenzo/flutter_inappwebview'; +const MethodChannel _pluginChannel = MethodChannel(_pluginChannelPrefix); + +class CustomFlutterViewControllerValue { + const CustomFlutterViewControllerValue({ + required this.isInitialized, + }); + + final bool isInitialized; + + CustomFlutterViewControllerValue copyWith({ + bool? isInitialized, + }) { + return CustomFlutterViewControllerValue( + isInitialized: isInitialized ?? this.isInitialized, + ); + } + + CustomFlutterViewControllerValue.uninitialized() + : this( + isInitialized: false, + ); +} + +/// Controls a WebView and provides streams for various change events. +class CustomPlatformViewController + extends ValueNotifier { + Completer _creatingCompleter = Completer(); + int _textureId = 0; + bool _isDisposed = false; + + Future get ready => _creatingCompleter.future; + + late MethodChannel _methodChannel; + late EventChannel _eventChannel; + StreamSubscription? _eventStreamSubscription; + + final StreamController _cursorStreamController = + StreamController.broadcast(); + + /// A stream reflecting the current cursor style. + Stream get _cursor => _cursorStreamController.stream; + + CustomPlatformViewController() + : super(CustomFlutterViewControllerValue.uninitialized()); + + /// Initializes the underlying platform view. + Future initialize( + {Function(int id)? onPlatformViewCreated, dynamic arguments}) async { + if (_isDisposed) { + return; + } + _textureId = (await _pluginChannel.invokeMethod( + 'createInAppWebView', arguments))!; + + _methodChannel = + MethodChannel('com.pichillilorenzo/custom_platform_view_$_textureId'); + _eventChannel = + EventChannel('com.pichillilorenzo/custom_platform_view_${_textureId}_events'); + _eventStreamSubscription = + _eventChannel.receiveBroadcastStream().listen((event) { + final map = event as Map; + switch (map['type']) { + case 'cursorChanged': + _cursorStreamController.add(_getCursorByName(map['value'])); + break; + } + }); + + _methodChannel.setMethodCallHandler((call) { + throw MissingPluginException('Unknown method ${call.method}'); + }); + + value = value.copyWith(isInitialized: true); + + _creatingCompleter.complete(); + + onPlatformViewCreated?.call(_textureId); + } + + @override + Future dispose() async { + await _creatingCompleter.future; + if (!_isDisposed) { + _isDisposed = true; + await _eventStreamSubscription?.cancel(); + await _pluginChannel.invokeMethod('dispose', {"id": _textureId}); + } + super.dispose(); + } + + /// Limits the number of frames per second to the given value. + Future setFpsLimit([int? maxFps = 0]) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setFpsLimit', maxFps); + } + + /// Sends a Pointer (Touch) update + Future _setPointerUpdate(InAppWebViewPointerEventKind kind, int pointer, + Offset position, double size, double pressure) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setPointerUpdate', + [pointer, kind.index, position.dx, position.dy, size, pressure]); + } + + /// Moves the virtual cursor to [position]. + Future _setCursorPos(Offset position) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel + .invokeMethod('setCursorPos', [position.dx, position.dy]); + } + + /// Indicates whether the specified [button] is currently down. + Future _setPointerButtonState(PointerButton button, bool isDown) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setPointerButton', + {'button': button.index, 'isDown': isDown}); + } + + /// Sets the horizontal and vertical scroll delta. + Future _setScrollDelta(double dx, double dy) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel.invokeMethod('setScrollDelta', [dx, dy]); + } + + /// Sets the surface size to the provided [size]. + Future _setSize(Size size, double scaleFactor) async { + if (_isDisposed) { + return; + } + assert(value.isInitialized); + return _methodChannel + .invokeMethod('setSize', [size.width, size.height, scaleFactor]); + } +} + +class CustomPlatformView extends StatefulWidget { + /// An optional scale factor. Defaults to [FlutterView.devicePixelRatio] for + /// rendering in native resolution. + /// Setting this to 1.0 will disable high-DPI support. + /// This should only be needed to mimic old behavior before high-DPI support + /// was available. + final double? scaleFactor; + + /// The [FilterQuality] used for scaling the texture's contents. + /// Defaults to [FilterQuality.none] as this renders in native resolution + /// unless specifying a [scaleFactor]. + final FilterQuality filterQuality; + + final dynamic creationParams; + + final Function(int id)? onPlatformViewCreated; + + const CustomPlatformView( + {this.creationParams, + this.onPlatformViewCreated, + this.scaleFactor, + this.filterQuality = FilterQuality.none}); + + @override + _CustomPlatformViewState createState() => _CustomPlatformViewState(); +} + +class _CustomPlatformViewState extends State { + final GlobalKey _key = GlobalKey(); + final _downButtons = {}; + + PointerDeviceKind _pointerKind = PointerDeviceKind.unknown; + + MouseCursor _cursor = SystemMouseCursors.basic; + + final _controller = CustomPlatformViewController(); + final _focusNode = FocusNode(); + + StreamSubscription? _cursorSubscription; + + @override + void initState() { + super.initState(); + + _controller.initialize( + onPlatformViewCreated: (id) { + widget.onPlatformViewCreated?.call(id); + setState(() {}); + }, + arguments: widget.creationParams); + + // Report initial surface size + WidgetsBinding.instance.addPostFrameCallback((_) => _reportSurfaceSize()); + + _cursorSubscription = _controller._cursor.listen((cursor) { + setState(() { + _cursor = cursor; + }); + }); + } + + @override + Widget build(BuildContext context) { + return Focus( + autofocus: true, + focusNode: _focusNode, + canRequestFocus: true, + debugLabel: "flutter_inappwebview_windows_custom_platform_view", + onFocusChange: (focused) { + + }, + child: SizedBox.expand(key: _key, child: _buildInner()), + ); + } + + Widget _buildInner() { + return NotificationListener( + onNotification: (notification) { + _reportSurfaceSize(); + return true; + }, + child: SizeChangedLayoutNotifier( + child: _controller.value.isInitialized + ? Listener( + onPointerHover: (ev) { + // ev.kind is for whatever reason not set to touch + // even on touch input + if (_pointerKind == PointerDeviceKind.touch) { + // Ignoring hover events on touch for now + return; + } + _controller._setCursorPos(ev.localPosition); + }, + onPointerDown: (ev) { + if (!_focusNode.hasFocus) { + _focusNode.requestFocus(); + Future.delayed(const Duration(milliseconds: 50), () { + if (!_focusNode.hasFocus) { + _focusNode.requestFocus(); + } + }); + } + + _pointerKind = ev.kind; + if (ev.kind == PointerDeviceKind.touch) { + _controller._setPointerUpdate( + InAppWebViewPointerEventKind.down, + ev.pointer, + ev.localPosition, + ev.size, + ev.pressure); + return; + } + final button = _getButton(ev.buttons); + _downButtons[ev.pointer] = button; + _controller._setPointerButtonState(button, true); + }, + onPointerUp: (ev) { + _pointerKind = ev.kind; + if (ev.kind == PointerDeviceKind.touch) { + _controller._setPointerUpdate( + InAppWebViewPointerEventKind.up, + ev.pointer, + ev.localPosition, + ev.size, + ev.pressure); + return; + } + final button = _downButtons.remove(ev.pointer); + if (button != null) { + _controller._setPointerButtonState(button, false); + } + }, + onPointerCancel: (ev) { + _pointerKind = ev.kind; + final button = _downButtons.remove(ev.pointer); + if (button != null) { + _controller._setPointerButtonState(button, false); + } + }, + onPointerMove: (ev) { + _pointerKind = ev.kind; + if (ev.kind == PointerDeviceKind.touch) { + _controller._setPointerUpdate( + InAppWebViewPointerEventKind.update, + ev.pointer, + ev.localPosition, + ev.size, + ev.pressure); + } else { + _controller._setCursorPos(ev.localPosition); + } + }, + onPointerSignal: (signal) { + if (signal is PointerScrollEvent) { + _controller._setScrollDelta( + -signal.scrollDelta.dx, -signal.scrollDelta.dy); + } + }, + onPointerPanZoomUpdate: (ev) { + _controller._setScrollDelta( + ev.panDelta.dx, ev.panDelta.dy); + }, + child: MouseRegion( + cursor: _cursor, + child: Texture( + textureId: _controller._textureId, + filterQuality: widget.filterQuality, + )), + ) + : const SizedBox())); + } + + void _reportSurfaceSize() async { + final box = _key.currentContext?.findRenderObject() as RenderBox?; + if (box != null) { + await _controller.ready; + unawaited(_controller._setSize( + box.size, widget.scaleFactor ?? window.devicePixelRatio)); + } + } + + @override + void dispose() { + super.dispose(); + _cursorSubscription?.cancel(); + _controller.dispose(); + _focusNode.dispose(); + } +} diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/headless_in_app_webview.dart index 047005db..7354b20b 100644 --- a/flutter_inappwebview_windows/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/headless_in_app_webview.dart @@ -6,16 +6,16 @@ import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_pla import '../find_interaction/find_interaction_controller.dart'; import 'in_app_webview_controller.dart'; -/// Object specifying creation parameters for creating a [MacOSHeadlessInAppWebView]. +/// Object specifying creation parameters for creating a [WindowsHeadlessInAppWebView]. /// /// When adding additional fields make sure they can be null or have a default /// value to avoid breaking changes. See [PlatformHeadlessInAppWebViewCreationParams] for /// more information. @immutable -class MacOSHeadlessInAppWebViewCreationParams +class WindowsHeadlessInAppWebViewCreationParams extends PlatformHeadlessInAppWebViewCreationParams { - /// Creates a new [MacOSHeadlessInAppWebViewCreationParams] instance. - MacOSHeadlessInAppWebViewCreationParams( + /// Creates a new [WindowsHeadlessInAppWebViewCreationParams] instance. + WindowsHeadlessInAppWebViewCreationParams( {super.controllerFromPlatform, super.initialSize, super.windowId, @@ -128,8 +128,8 @@ class MacOSHeadlessInAppWebViewCreationParams super.pullToRefreshController, this.findInteractionController}); - /// Creates a [MacOSHeadlessInAppWebViewCreationParams] instance based on [PlatformHeadlessInAppWebViewCreationParams]. - MacOSHeadlessInAppWebViewCreationParams.fromPlatformHeadlessInAppWebViewCreationParams( + /// Creates a [WindowsHeadlessInAppWebViewCreationParams] instance based on [PlatformHeadlessInAppWebViewCreationParams]. + WindowsHeadlessInAppWebViewCreationParams.fromPlatformHeadlessInAppWebViewCreationParams( PlatformHeadlessInAppWebViewCreationParams params) : this( controllerFromPlatform: params.controllerFromPlatform, @@ -245,7 +245,7 @@ class MacOSHeadlessInAppWebViewCreationParams } ///{@macro flutter_inappwebview_platform_interface.PlatformHeadlessInAppWebView} -class MacOSHeadlessInAppWebView extends PlatformHeadlessInAppWebView +class WindowsHeadlessInAppWebView extends PlatformHeadlessInAppWebView with ChannelController { @override late final String id; @@ -258,12 +258,12 @@ class MacOSHeadlessInAppWebView extends PlatformHeadlessInAppWebView WindowsInAppWebViewController? _webViewController; - /// Constructs a [MacOSHeadlessInAppWebView]. - MacOSHeadlessInAppWebView(PlatformHeadlessInAppWebViewCreationParams params) + /// Constructs a [WindowsHeadlessInAppWebView]. + WindowsHeadlessInAppWebView(PlatformHeadlessInAppWebViewCreationParams params) : super.implementation( - params is MacOSHeadlessInAppWebViewCreationParams + params is WindowsHeadlessInAppWebViewCreationParams ? params - : MacOSHeadlessInAppWebViewCreationParams + : WindowsHeadlessInAppWebViewCreationParams .fromPlatformHeadlessInAppWebViewCreationParams(params), ) { id = IdGenerator.generate(); @@ -274,8 +274,8 @@ class MacOSHeadlessInAppWebView extends PlatformHeadlessInAppWebView dynamic _controllerFromPlatform; - MacOSHeadlessInAppWebViewCreationParams get _macosParams => - params as MacOSHeadlessInAppWebViewCreationParams; + WindowsHeadlessInAppWebViewCreationParams get _macosParams => + params as WindowsHeadlessInAppWebViewCreationParams; _init() { _webViewController = WindowsInAppWebViewController( @@ -425,7 +425,7 @@ class MacOSHeadlessInAppWebView extends PlatformHeadlessInAppWebView } } -extension InternalHeadlessInAppWebView on MacOSHeadlessInAppWebView { +extension InternalHeadlessInAppWebView on WindowsHeadlessInAppWebView { Future internalDispose() async { _started = false; _running = false; diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart index c1fe8925..09fac537 100644 --- a/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; import 'headless_in_app_webview.dart'; @@ -7,13 +6,15 @@ import 'headless_in_app_webview.dart'; import '../find_interaction/find_interaction_controller.dart'; import 'in_app_webview_controller.dart'; +import 'custom_platform_view.dart'; + /// Object specifying creation parameters for creating a [PlatformInAppWebViewWidget]. /// /// Platform specific implementations can add additional fields by extending /// this class. -class MacOSInAppWebViewWidgetCreationParams +class WindowsInAppWebViewWidgetCreationParams extends PlatformInAppWebViewWidgetCreationParams { - MacOSInAppWebViewWidgetCreationParams( + WindowsInAppWebViewWidgetCreationParams( {super.controllerFromPlatform, super.key, super.layoutDirection, @@ -131,9 +132,9 @@ class MacOSInAppWebViewWidgetCreationParams super.pullToRefreshController, this.findInteractionController}); - /// Constructs a [MacOSInAppWebViewWidgetCreationParams] using a + /// Constructs a [WindowsInAppWebViewWidgetCreationParams] using a /// [PlatformInAppWebViewWidgetCreationParams]. - MacOSInAppWebViewWidgetCreationParams.fromPlatformInAppWebViewWidgetCreationParams( + WindowsInAppWebViewWidgetCreationParams.fromPlatformInAppWebViewWidgetCreationParams( PlatformInAppWebViewWidgetCreationParams params) : this( controllerFromPlatform: params.controllerFromPlatform, @@ -254,25 +255,25 @@ class MacOSInAppWebViewWidgetCreationParams } ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget} -class MacOSInAppWebViewWidget extends PlatformInAppWebViewWidget { - /// Constructs a [MacOSInAppWebViewWidget]. +class WindowsInAppWebViewWidget extends PlatformInAppWebViewWidget { + /// Constructs a [WindowsInAppWebViewWidget]. /// ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget} - MacOSInAppWebViewWidget(PlatformInAppWebViewWidgetCreationParams params) + WindowsInAppWebViewWidget(PlatformInAppWebViewWidgetCreationParams params) : super.implementation( - params is MacOSInAppWebViewWidgetCreationParams + params is WindowsInAppWebViewWidgetCreationParams ? params - : MacOSInAppWebViewWidgetCreationParams + : WindowsInAppWebViewWidgetCreationParams .fromPlatformInAppWebViewWidgetCreationParams(params), ); - MacOSInAppWebViewWidgetCreationParams get _macosParams => - params as MacOSInAppWebViewWidgetCreationParams; + WindowsInAppWebViewWidgetCreationParams get _macosParams => + params as WindowsInAppWebViewWidgetCreationParams; WindowsInAppWebViewController? _controller; - MacOSHeadlessInAppWebView? get _macosHeadlessInAppWebView => - params.headlessWebView as MacOSHeadlessInAppWebView?; + WindowsHeadlessInAppWebView? get _macosHeadlessInAppWebView => + params.headlessWebView as WindowsHeadlessInAppWebView?; @override Widget build(BuildContext context) { @@ -300,10 +301,8 @@ class MacOSInAppWebViewWidget extends PlatformInAppWebViewWidget { } } - return UiKitView( - viewType: 'com.pichillilorenzo/flutter_inappwebview', + return CustomPlatformView( onPlatformViewCreated: _onPlatformViewCreated, - gestureRecognizers: params.gestureRecognizers, creationParams: { 'initialUrlRequest': params.initialUrlRequest?.toMap(), 'initialFile': params.initialFile, @@ -320,7 +319,6 @@ class MacOSInAppWebViewWidget extends PlatformInAppWebViewWidget { 'keepAliveId': params.keepAlive?.id, 'preventGestureDelay': params.preventGestureDelay }, - creationParamsCodec: const StandardMessageCodec(), ); } diff --git a/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview_controller.dart b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview_controller.dart index 12d07af4..2878505e 100644 --- a/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/flutter_inappwebview_windows/lib/src/in_app_webview/in_app_webview_controller.dart @@ -57,7 +57,7 @@ class WindowsInAppWebViewControllerCreationParams } } -///Controls a WebView, such as an [InAppWebView] widget instance, a [MacOSHeadlessInAppWebView] instance or [WindowsInAppBrowser] WebView instance. +///Controls a WebView, such as an [InAppWebView] widget instance, a [WindowsHeadlessInAppWebView] instance or [WindowsInAppBrowser] WebView instance. /// ///If you are using the [InAppWebView] widget, an [InAppWebViewController] instance can be obtained by setting the [InAppWebView.onWebViewCreated] ///callback. Instead, if you are using an [WindowsInAppBrowser] instance, you can get it through the [WindowsInAppBrowser.webViewController] attribute. @@ -1923,6 +1923,13 @@ class WindowsInAppWebViewController extends PlatformInAppWebViewController args.putIfAbsent('source', () => source); args.putIfAbsent('contentWorld', () => contentWorld?.toMap()); var data = await channel?.invokeMethod('evaluateJavascript', args); + if (data != null) { + try { + // try to json decode the data coming from JavaScript + // otherwise return it as it is. + data = json.decode(data); + } catch (e) {} + } return data; } diff --git a/flutter_inappwebview_windows/lib/src/inappwebview_platform.dart b/flutter_inappwebview_windows/lib/src/inappwebview_platform.dart index 1cd197d0..a91ade71 100644 --- a/flutter_inappwebview_windows/lib/src/inappwebview_platform.dart +++ b/flutter_inappwebview_windows/lib/src/inappwebview_platform.dart @@ -1,6 +1,8 @@ import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; import 'in_app_browser/in_app_browser.dart'; +import 'in_app_webview/in_app_webview.dart'; +import 'in_app_webview/in_app_webview_controller.dart'; /// Implementation of [InAppWebViewPlatform] using the WebKit API. class WindowsInAppWebViewPlatform extends InAppWebViewPlatform { @@ -9,6 +11,37 @@ class WindowsInAppWebViewPlatform extends InAppWebViewPlatform { InAppWebViewPlatform.instance = WindowsInAppWebViewPlatform(); } + /// Creates a new [WindowsInAppWebViewController]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppWebViewController] in `flutter_inappwebview` instead. + @override + WindowsInAppWebViewController createPlatformInAppWebViewController( + PlatformInAppWebViewControllerCreationParams params, + ) { + return WindowsInAppWebViewController(params); + } + + /// Creates a new empty [WindowsInAppWebViewController] to access static methods. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppWebViewController] in `flutter_inappwebview` instead. + @override + WindowsInAppWebViewController createPlatformInAppWebViewControllerStatic() { + return WindowsInAppWebViewController.static(); + } + + /// Creates a new [WindowsInAppWebViewWidget]. + /// + /// This function should only be called by the app-facing package. + /// Look at using [InAppWebView] in `flutter_inappwebview` instead. + @override + WindowsInAppWebViewWidget createPlatformInAppWebViewWidget( + PlatformInAppWebViewWidgetCreationParams params, + ) { + return WindowsInAppWebViewWidget(params); + } + /// Creates a new [WindowsInAppBrowser]. /// /// This function should only be called by the app-facing package. @@ -28,5 +61,4 @@ class WindowsInAppWebViewPlatform extends InAppWebViewPlatform { WindowsInAppBrowser createPlatformInAppBrowserStatic() { return WindowsInAppBrowser.static(); } - } diff --git a/flutter_inappwebview_windows/windows/CMakeLists.txt b/flutter_inappwebview_windows/windows/CMakeLists.txt index 20c412dd..e662976f 100644 --- a/flutter_inappwebview_windows/windows/CMakeLists.txt +++ b/flutter_inappwebview_windows/windows/CMakeLists.txt @@ -21,16 +21,9 @@ cmake_policy(VERSION 3.14...3.25) # not be changed set(PLUGIN_NAME "flutter_inappwebview_windows_plugin") -set(NUGET_URL https://dist.nuget.org/win-x86-commandline/latest/nuget.exe) - find_program(NUGET nuget) if(NOT NUGET) message(NOTICE "Nuget is not installed.") - set(NUGET ${CMAKE_BINARY_DIR}/nuget.exe) - if (NOT EXISTS ${NUGET}) - message(NOTICE "Attempting to download nuget.") - file(DOWNLOAD ${NUGET_URL} ${NUGET}) - endif() endif() add_custom_target(${PROJECT_NAME}_DEPENDENCIES_DOWNLOAD ALL) @@ -61,10 +54,25 @@ list(APPEND PLUGIN_SOURCES "types/web_resource_request.h" "types/web_resource_response.cpp" "types/web_resource_response.h" + "custom_platform_view/custom_platform_view.cc" + "custom_platform_view/custom_platform_view.h" + "custom_platform_view/texture_bridge.cc" + "custom_platform_view/texture_bridge.h" + "custom_platform_view/graphics_context.cc" + "custom_platform_view/graphics_context.h" + "custom_platform_view/util/direct3d11.interop.cc" + "custom_platform_view/util/direct3d11.interop.h" + "custom_platform_view/util/rohelper.cc" + "custom_platform_view/util/rohelper.h" + "custom_platform_view/util/string_converter.cc" + "custom_platform_view/util/string_converter.h" + "custom_platform_view/util/swizzle.h" "in_app_webview/in_app_webview_settings.cpp" "in_app_webview/in_app_webview_settings.h" "in_app_webview/in_app_webview.cpp" "in_app_webview/in_app_webview.h" + "in_app_webview/in_app_webview_manager.cpp" + "in_app_webview/in_app_webview_manager.h" "in_app_webview/webview_channel_delegate.cpp" "in_app_webview/webview_channel_delegate.h" "in_app_browser/in_app_browser_settings.cpp" @@ -85,6 +93,29 @@ add_library(${PLUGIN_NAME} SHARED ${PLUGIN_SOURCES} ) +if(NOT FLUTTER_WEBVIEW_WINDOWS_USE_TEXTURE_FALLBACK) + message(STATUS "Building with D3D texture support.") + target_compile_definitions("${PLUGIN_NAME}" PRIVATE + HAVE_FLUTTER_D3D_TEXTURE + ) + target_sources("${PLUGIN_NAME}" PRIVATE + "custom_platform_view/texture_bridge_gpu.cc" + "custom_platform_view/texture_bridge_gpu.h" + ) +else() + message(STATUS "Building with fallback PixelBuffer texture.") + target_sources("${PLUGIN_NAME}" PRIVATE + "custom_platform_view/texture_bridge_fallback.cc" + "custom_platform_view/texture_bridge_fallback.h" + "custom_platform_view/util/cpuid/cpuinfo.cc" + "custom_platform_view/util/cpuid/cpuinfo.h" + ) + # Enable AVX2 for pixel buffer conversions + if(MSVC) + target_compile_options(${PLUGIN_NAME} PRIVATE "/arch:AVX2") + endif() +endif() + # Apply a standard set of build settings that are configured in the # application-level CMakeLists.txt. This can be removed for plugins that want # full control over build settings. diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.cc b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.cc new file mode 100644 index 00000000..e8de9cc4 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.cc @@ -0,0 +1,312 @@ +#include "custom_platform_view.h" + +#include +#include + +#ifdef HAVE_FLUTTER_D3D_TEXTURE +#include "texture_bridge_gpu.h" +#else +#include "texture_bridge_fallback.h" +#endif + +namespace flutter_inappwebview_plugin +{ + constexpr auto kErrorInvalidArgs = "invalidArguments"; + + constexpr auto kMethodSetSize = "setSize"; + constexpr auto kMethodSetCursorPos = "setCursorPos"; + constexpr auto kMethodSetPointerUpdate = "setPointerUpdate"; + constexpr auto kMethodSetPointerButton = "setPointerButton"; + constexpr auto kMethodSetScrollDelta = "setScrollDelta"; + constexpr auto kMethodSetFpsLimit = "setFpsLimit"; + + constexpr auto kEventType = "type"; + constexpr auto kEventValue = "value"; + + static const std::optional> GetPointFromArgs( + const flutter::EncodableValue* args) + { + const flutter::EncodableList* list = + std::get_if(args); + if (!list || list->size() != 2) { + return std::nullopt; + } + const auto x = std::get_if(&(*list)[0]); + const auto y = std::get_if(&(*list)[1]); + if (!x || !y) { + return std::nullopt; + } + return std::make_pair(*x, *y); + } + + static const std::optional> + GetPointAndScaleFactorFromArgs(const flutter::EncodableValue* args) + { + const flutter::EncodableList* list = + std::get_if(args); + if (!list || list->size() != 3) { + return std::nullopt; + } + const auto x = std::get_if(&(*list)[0]); + const auto y = std::get_if(&(*list)[1]); + const auto z = std::get_if(&(*list)[2]); + if (!x || !y || !z) { + return std::nullopt; + } + return std::make_tuple(*x, *y, *z); + } + + static const std::string& GetCursorName(const HCURSOR cursor) + { + // The cursor names correspond to the Flutter Engine names: + // in shell/platform/windows/flutter_window_win32.cc + static const std::string kDefaultCursorName = "basic"; + static const std::pair mappings[] = { + {"allScroll", IDC_SIZEALL}, + {kDefaultCursorName, IDC_ARROW}, + {"click", IDC_HAND}, + {"forbidden", IDC_NO}, + {"help", IDC_HELP}, + {"move", IDC_SIZEALL}, + {"none", nullptr}, + {"noDrop", IDC_NO}, + {"precise", IDC_CROSS}, + {"progress", IDC_APPSTARTING}, + {"text", IDC_IBEAM}, + {"resizeColumn", IDC_SIZEWE}, + {"resizeDown", IDC_SIZENS}, + {"resizeDownLeft", IDC_SIZENESW}, + {"resizeDownRight", IDC_SIZENWSE}, + {"resizeLeft", IDC_SIZEWE}, + {"resizeLeftRight", IDC_SIZEWE}, + {"resizeRight", IDC_SIZEWE}, + {"resizeRow", IDC_SIZENS}, + {"resizeUp", IDC_SIZENS}, + {"resizeUpDown", IDC_SIZENS}, + {"resizeUpLeft", IDC_SIZENWSE}, + {"resizeUpRight", IDC_SIZENESW}, + {"resizeUpLeftDownRight", IDC_SIZENWSE}, + {"resizeUpRightDownLeft", IDC_SIZENESW}, + {"wait", IDC_WAIT}, + }; + + static std::map cursors; + static bool initialized = false; + + if (!initialized) { + initialized = true; + for (const auto& pair : mappings) { + HCURSOR cursor_handle = LoadCursor(nullptr, pair.second); + if (cursor_handle) { + cursors[cursor_handle] = pair.first; + } + } + } + + const auto it = cursors.find(cursor); + if (it != cursors.end()) { + return it->second; + } + return kDefaultCursorName; + } + + CustomPlatformView::CustomPlatformView(flutter::BinaryMessenger* messenger, + flutter::TextureRegistrar* texture_registrar, + GraphicsContext* graphics_context, + HWND hwnd, + std::unique_ptr webView) + : hwnd_(hwnd), view(std::move(webView)), texture_registrar_(texture_registrar) + { +#ifdef HAVE_FLUTTER_D3D_TEXTURE + texture_bridge_ = + std::make_unique(graphics_context, view->surface()); + + flutter_texture_ = + std::make_unique(flutter::GpuSurfaceTexture( + kFlutterDesktopGpuSurfaceTypeDxgiSharedHandle, + [bridge = static_cast(texture_bridge_.get())]( + size_t width, + size_t height) -> const FlutterDesktopGpuSurfaceDescriptor* + { + return bridge->GetSurfaceDescriptor(width, height); + })); +#else + texture_bridge_ = std::make_unique( + graphics_context, webview_->surface()); + + flutter_texture_ = + std::make_unique(flutter::PixelBufferTexture( + [bridge = static_cast(texture_bridge_.get())]( + size_t width, size_t height) -> const FlutterDesktopPixelBuffer* + { + return bridge->CopyPixelBuffer(width, height); + })); +#endif + + texture_id_ = texture_registrar->RegisterTexture(flutter_texture_.get()); + texture_bridge_->SetOnFrameAvailable( + [this]() { texture_registrar_->MarkTextureFrameAvailable(texture_id_); }); + // texture_bridge_->SetOnSurfaceSizeChanged([this](Size size) { + // view->SetSurfaceSize(size.width, size.height); + //}); + + const auto method_channel_name = "com.pichillilorenzo/custom_platform_view_" + std::to_string(texture_id_); + method_channel_ = + std::make_unique>( + messenger, method_channel_name, + &flutter::StandardMethodCodec::GetInstance()); + method_channel_->SetMethodCallHandler([this](const auto& call, auto result) + { + HandleMethodCall(call, std::move(result)); + }); + + const auto event_channel_name = "com.pichillilorenzo/custom_platform_view_" + std::to_string(texture_id_) + "_events"; + event_channel_ = + std::make_unique>( + messenger, event_channel_name, + &flutter::StandardMethodCodec::GetInstance()); + + auto handler = std::make_unique< + flutter::StreamHandlerFunctions>( + [this](const flutter::EncodableValue* arguments, + std::unique_ptr>&& + events) + { + event_sink_ = std::move(events); + RegisterEventHandlers(); + return nullptr; + }, + [this](const flutter::EncodableValue* arguments) + { + event_sink_ = nullptr; + return nullptr; + }); + + event_channel_->SetStreamHandler(std::move(handler)); + } + + CustomPlatformView::~CustomPlatformView() + { + debugLog("dealloc CustomPlatformView"); + method_channel_->SetMethodCallHandler(nullptr); + texture_registrar_->UnregisterTexture(texture_id_); + } + + void CustomPlatformView::RegisterEventHandlers() + { + if (!view) { + return; + } + + view->onSurfaceSizeChanged([this](size_t width, size_t height) + { + texture_bridge_->NotifySurfaceSizeChanged(); + }); + + view->onCursorChanged([this](const HCURSOR cursor) + { + const auto& name = GetCursorName(cursor); + const auto event = flutter::EncodableValue( + flutter::EncodableMap { {flutter::EncodableValue(kEventType), + flutter::EncodableValue("cursorChanged")}, + { flutter::EncodableValue(kEventValue), name }}); + EmitEvent(event); + }); + } + + void CustomPlatformView::HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + const auto& method_name = method_call.method_name(); + + // setCursorPos: [double x, double y] + if (method_name.compare(kMethodSetCursorPos) == 0) { + const auto point = GetPointFromArgs(method_call.arguments()); + if (point && view) { + view->setCursorPos(point->first, point->second); + return result->Success(); + } + return result->Error(kErrorInvalidArgs); + } + + // setPointerUpdate: + // [int pointer, int event, double x, double y, double size, double pressure] + if (method_name.compare(kMethodSetPointerUpdate) == 0) { + const flutter::EncodableList* list = + std::get_if(method_call.arguments()); + if (!list || list->size() != 6) { + return result->Error(kErrorInvalidArgs); + } + + const auto pointer = std::get_if(&(*list)[0]); + const auto event = std::get_if(&(*list)[1]); + const auto x = std::get_if(&(*list)[2]); + const auto y = std::get_if(&(*list)[3]); + const auto size = std::get_if(&(*list)[4]); + const auto pressure = std::get_if(&(*list)[5]); + + if (pointer && event && x && y && size && pressure && view) { + view->setPointerUpdate(*pointer, + static_cast(*event), + *x, *y, *size, *pressure); + return result->Success(); + } + return result->Error(kErrorInvalidArgs); + } + + // setScrollDelta: [double dx, double dy] + if (method_name.compare(kMethodSetScrollDelta) == 0) { + const auto delta = GetPointFromArgs(method_call.arguments()); + if (delta && view) { + view->setScrollDelta(delta->first, delta->second); + return result->Success(); + } + return result->Error(kErrorInvalidArgs); + } + + // setPointerButton: {"button": int, "isDown": bool} + if (method_name.compare(kMethodSetPointerButton) == 0) { + const auto& map = std::get(*method_call.arguments()); + + const auto button = map.find(flutter::EncodableValue("button")); + const auto isDown = map.find(flutter::EncodableValue("isDown")); + if (button != map.end() && isDown != map.end()) { + const auto buttonValue = std::get_if(&button->second); + const auto isDownValue = std::get_if(&isDown->second); + if (buttonValue && isDownValue && view) { + view->setPointerButtonState( + static_cast(*buttonValue), *isDownValue); + return result->Success(); + } + } + return result->Error(kErrorInvalidArgs); + } + + // setSize: [double width, double height, double scale_factor] + if (method_name.compare(kMethodSetSize) == 0) { + auto size = GetPointAndScaleFactorFromArgs(method_call.arguments()); + if (size && view) { + const auto [width, height, scale_factor] = size.value(); + + view->setSurfaceSize(static_cast(width), + static_cast(height), + static_cast(scale_factor)); + + texture_bridge_->Start(); + return result->Success(); + } + return result->Error(kErrorInvalidArgs); + } + + if (method_name.compare(kMethodSetFpsLimit) == 0) { + if (const auto value = std::get_if(method_call.arguments())) { + texture_bridge_->SetFpsLimit(*value == 0 ? std::nullopt + : std::make_optional(*value)); + return result->Success(); + } + } + + result->NotImplemented(); + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.h b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.h new file mode 100644 index 00000000..6c4adfdb --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/custom_platform_view.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include "../in_app_webview/in_app_webview.h" +#include "graphics_context.h" +#include "texture_bridge.h" + +namespace flutter_inappwebview_plugin +{ + class CustomPlatformView { + public: + static inline const wchar_t* CLASS_NAME = L"CustomPlatformView"; + + const std::unique_ptr view; + + CustomPlatformView(flutter::BinaryMessenger* messenger, + flutter::TextureRegistrar* texture_registrar, + GraphicsContext* graphics_context, + HWND hwnd, + std::unique_ptr webView); + ~CustomPlatformView(); + + TextureBridge* texture_bridge() const { return texture_bridge_.get(); } + + int64_t texture_id() const { return texture_id_; } + private: + HWND hwnd_; + std::unique_ptr flutter_texture_; + std::unique_ptr texture_bridge_; + std::unique_ptr> event_sink_; + std::unique_ptr> + event_channel_; + std::unique_ptr> + method_channel_; + + flutter::TextureRegistrar* texture_registrar_; + int64_t texture_id_; + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + void RegisterEventHandlers(); + + template + void EmitEvent(const T& value) + { + if (event_sink_) { + event_sink_->Success(value); + } + } + }; +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.cc b/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.cc new file mode 100644 index 00000000..2bbc1e74 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.cc @@ -0,0 +1,158 @@ +#include "graphics_context.h" + +#include "util/d3dutil.h" +#include "util/direct3d11.interop.h" + +namespace flutter_inappwebview_plugin +{ + GraphicsContext::GraphicsContext(rx::RoHelper* rohelper) : rohelper_(rohelper) + { + device_ = CreateD3DDevice(); + if (!device_) { + return; + } + + device_->GetImmediateContext(device_context_.put()); + if (FAILED(CreateDirect3D11DeviceFromDXGIDevice( + device_.try_as().get(), + (IInspectable**)device_winrt_.put()))) { + return; + } + + valid_ = true; + } + + winrt::com_ptr + GraphicsContext::CreateCompositor() + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_UI_Composition_Compositor, &className, + &classNameHeader))) { + return nullptr; + } + + winrt::com_ptr af; + if (FAILED(rohelper_->GetActivationFactory( + className, __uuidof(IActivationFactory), af.put_void()))) { + return nullptr; + } + + winrt::com_ptr compositor; + if (FAILED(af->ActivateInstance( + reinterpret_cast(compositor.put())))) { + return nullptr; + } + + return compositor; + } + + winrt::com_ptr + GraphicsContext::CreateGraphicsCaptureItemFromVisual( + ABI::Windows::UI::Composition::IVisual* visual) const + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem, &className, + &classNameHeader))) { + return nullptr; + } + + ABI::Windows::Graphics::Capture::IGraphicsCaptureItemStatics* + capture_item_statics; + if (FAILED(rohelper_->GetActivationFactory( + className, + __uuidof( + ABI::Windows::Graphics::Capture::IGraphicsCaptureItemStatics), + (void**)&capture_item_statics))) { + return nullptr; + } + + winrt::com_ptr + capture_item; + if (FAILED( + capture_item_statics->CreateFromVisual(visual, capture_item.put()))) { + return nullptr; + } + + return capture_item; + } + + winrt::com_ptr + GraphicsContext::CreateCaptureFramePool( + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, + ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, + INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool, + &className, &classNameHeader))) { + return nullptr; + } + + ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePoolStatics* + capture_frame_pool_statics; + if (FAILED(rohelper_->GetActivationFactory( + className, + __uuidof(ABI::Windows::Graphics::Capture:: + IDirect3D11CaptureFramePoolStatics), + (void**)&capture_frame_pool_statics))) { + return nullptr; + } + + winrt::com_ptr + capture_frame_pool; + + if (FAILED(capture_frame_pool_statics->Create(device, pixelFormat, + numberOfBuffers, size, + capture_frame_pool.put()))) { + return nullptr; + } + + return capture_frame_pool; + } + + winrt::com_ptr + GraphicsContext::CreateFreeThreadedCaptureFramePool( + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, + ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, + INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool, + &className, &classNameHeader))) { + return nullptr; + } + + ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePoolStatics2* + capture_frame_pool_statics; + if (FAILED(rohelper_->GetActivationFactory( + className, + __uuidof(ABI::Windows::Graphics::Capture:: + IDirect3D11CaptureFramePoolStatics2), + (void**)&capture_frame_pool_statics))) { + return nullptr; + } + + winrt::com_ptr + capture_frame_pool; + + if (FAILED(capture_frame_pool_statics->CreateFreeThreaded( + device, pixelFormat, numberOfBuffers, size, + capture_frame_pool.put()))) { + return nullptr; + } + + return capture_frame_pool; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.h b/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.h new file mode 100644 index 00000000..883bffe7 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/graphics_context.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include + +#include "util/rohelper.h" + +namespace flutter_inappwebview_plugin +{ + class GraphicsContext { + public: + GraphicsContext(rx::RoHelper* rohelper); + + inline bool IsValid() const { return valid_; } + + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device() const + { + return device_winrt_.get(); + } + ID3D11Device* d3d_device() const { return device_.get(); } + ID3D11DeviceContext* d3d_device_context() const + { + return device_context_.get(); + } + + winrt::com_ptr CreateCompositor(); + + winrt::com_ptr + CreateGraphicsCaptureItemFromVisual( + ABI::Windows::UI::Composition::IVisual* visual) const; + + winrt::com_ptr + CreateCaptureFramePool( + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, + ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, + INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const; + + winrt::com_ptr + CreateFreeThreadedCaptureFramePool( + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device, + ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat, + INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const; + + private: + bool valid_ = false; + rx::RoHelper* rohelper_; + winrt::com_ptr + device_winrt_; + winrt::com_ptr device_{ nullptr }; + winrt::com_ptr device_context_{ nullptr }; + }; +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.cc b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.cc new file mode 100644 index 00000000..658df19f --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.cc @@ -0,0 +1,189 @@ +#include "texture_bridge.h" + +#include + +#include +#include +#include +#include + +#include "util/direct3d11.interop.h" + +namespace flutter_inappwebview_plugin +{ + const int kNumBuffers = 1; + + TextureBridge::TextureBridge(GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual) + : graphics_context_(graphics_context) + { + capture_item_ = + graphics_context_->CreateGraphicsCaptureItemFromVisual(visual); + assert(capture_item_); + + capture_item_->add_Closed( + Microsoft::WRL::Callback>( + [](ABI::Windows::Graphics::Capture::IGraphicsCaptureItem* item, + IInspectable* args) -> HRESULT + { + std::cerr << "Capture item was closed." << std::endl; + return S_OK; + }) + .Get(), + &on_closed_token_); + } + + TextureBridge::~TextureBridge() + { + const std::lock_guard lock(mutex_); + StopInternal(); + if (capture_item_) { + capture_item_->remove_Closed(on_closed_token_); + } + } + + bool TextureBridge::Start() + { + const std::lock_guard lock(mutex_); + if (is_running_ || !capture_item_) { + return false; + } + + ABI::Windows::Graphics::SizeInt32 size; + capture_item_->get_Size(&size); + + frame_pool_ = graphics_context_->CreateCaptureFramePool( + graphics_context_->device(), + static_cast( + kPixelFormat), + kNumBuffers, size); + assert(frame_pool_); + + frame_pool_->add_FrameArrived( + Microsoft::WRL::Callback>( + [this](ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePool* + pool, + IInspectable* args) -> HRESULT + { + OnFrameArrived(); + return S_OK; + }) + .Get(), + &on_frame_arrived_token_); + + if (FAILED(frame_pool_->CreateCaptureSession(capture_item_.get(), + capture_session_.put()))) { + std::cerr << "Creating capture session failed." << std::endl; + return false; + } + + if (SUCCEEDED(capture_session_->StartCapture())) { + is_running_ = true; + return true; + } + + return false; + } + + void TextureBridge::Stop() + { + const std::lock_guard lock(mutex_); + StopInternal(); + } + + void TextureBridge::StopInternal() + { + if (is_running_) { + is_running_ = false; + frame_pool_->remove_FrameArrived(on_frame_arrived_token_); + auto closable = + capture_session_.try_as(); + assert(closable); + closable->Close(); + capture_session_ = nullptr; + } + } + + void TextureBridge::OnFrameArrived() + { + const std::lock_guard lock(mutex_); + if (!is_running_) { + return; + } + + bool has_frame = false; + + winrt::com_ptr + frame; + auto hr = frame_pool_->TryGetNextFrame(frame.put()); + if (SUCCEEDED(hr) && frame) { + winrt::com_ptr< + ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface> + frame_surface; + + if (SUCCEEDED(frame->get_Surface(frame_surface.put()))) { + last_frame_ = + TryGetDXGIInterfaceFromObject(frame_surface); + has_frame = !ShouldDropFrame(); + } + } + + if (needs_update_) { + ABI::Windows::Graphics::SizeInt32 size; + capture_item_->get_Size(&size); + frame_pool_->Recreate( + graphics_context_->device(), + static_cast( + kPixelFormat), + kNumBuffers, size); + needs_update_ = false; + } + + if (has_frame && frame_available_) { + frame_available_(); + } + } + + bool TextureBridge::ShouldDropFrame() + { + if (!frame_duration_.has_value()) { + return false; + } + auto now = std::chrono::high_resolution_clock::now(); + + bool should_drop_frame = false; + if (last_frame_timestamp_.has_value()) { + auto diff = std::chrono::duration_cast( + now - last_frame_timestamp_.value()); + should_drop_frame = diff < frame_duration_.value(); + } + + if (!should_drop_frame) { + last_frame_timestamp_ = now; + } + return should_drop_frame; + } + + void TextureBridge::NotifySurfaceSizeChanged() + { + const std::lock_guard lock(mutex_); + needs_update_ = true; + } + + void TextureBridge::SetFpsLimit(std::optional max_fps) + { + const std::lock_guard lock(mutex_); + auto value = max_fps.value_or(0); + if (value != 0) { + frame_duration_ = FrameDuration(1000.0 / value); + } + else { + frame_duration_.reset(); + last_frame_timestamp_.reset(); + } + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.h b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.h new file mode 100644 index 00000000..122c6938 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +#include "graphics_context.h" + +namespace flutter_inappwebview_plugin +{ + typedef struct { + size_t width; + size_t height; + } Size; + + class TextureBridge { + public: + typedef std::function FrameAvailableCallback; + typedef std::function SurfaceSizeChangedCallback; + typedef std::chrono::duration FrameDuration; + + TextureBridge(GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual); + virtual ~TextureBridge(); + + bool Start(); + void Stop(); + + void SetOnFrameAvailable(FrameAvailableCallback callback) + { + frame_available_ = std::move(callback); + } + + void SetOnSurfaceSizeChanged(SurfaceSizeChangedCallback callback) + { + surface_size_changed_ = std::move(callback); + } + + void NotifySurfaceSizeChanged(); + void SetFpsLimit(std::optional max_fps); + + protected: + bool is_running_ = false; + + const GraphicsContext* graphics_context_; + std::mutex mutex_; + std::optional frame_duration_ = std::nullopt; + + FrameAvailableCallback frame_available_; + SurfaceSizeChangedCallback surface_size_changed_; + std::atomic needs_update_ = false; + winrt::com_ptr last_frame_; + std::optional + last_frame_timestamp_; + + winrt::com_ptr + capture_item_; + winrt::com_ptr + frame_pool_; + winrt::com_ptr + capture_session_; + + EventRegistrationToken on_closed_token_ = {}; + EventRegistrationToken on_frame_arrived_token_ = {}; + + virtual void StopInternal(); + void OnFrameArrived(); + bool ShouldDropFrame(); + + // corresponds to DXGI_FORMAT_B8G8R8A8_UNORM + static constexpr auto kPixelFormat = ABI::Windows::Graphics::DirectX:: + DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized; + }; +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.cc b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.cc new file mode 100644 index 00000000..6aa5d58d --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.cc @@ -0,0 +1,145 @@ +#include "texture_bridge_fallback.h" + +#include + +#include "util/direct3d11.interop.h" +#include "util/swizzle.h" + +namespace flutter_inappwebview_plugin +{ + TextureBridgeFallback::TextureBridgeFallback( + GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual) + : TextureBridge(graphics_context, visual) + {} + + TextureBridgeFallback::~TextureBridgeFallback() + { + const std::lock_guard lock(buffer_mutex_); + } + + void TextureBridgeFallback::ProcessFrame( + winrt::com_ptr src_texture) + { + D3D11_TEXTURE2D_DESC desc; + src_texture->GetDesc(&desc); + + const auto width = desc.Width; + const auto height = desc.Height; + + bool is_exact_size; + EnsureStagingTexture(width, height, is_exact_size); + + auto device_context = graphics_context_->d3d_device_context(); + auto staging_texture = staging_texture_.get(); + + if (is_exact_size) { + device_context->CopyResource(staging_texture, src_texture.get()); + } + else { + D3D11_BOX client_box; + client_box.top = 0; + client_box.left = 0; + client_box.right = width; + client_box.bottom = height; + client_box.front = 0; + client_box.back = 1; + device_context->CopySubresourceRegion(staging_texture, 0, 0, 0, 0, + src_texture.get(), 0, &client_box); + } + + D3D11_MAPPED_SUBRESOURCE mappedResource; + if (!SUCCEEDED(device_context->Map(staging_texture, 0, D3D11_MAP_READ, 0, + &mappedResource))) { + return; + } + + { + const std::lock_guard lock(buffer_mutex_); + if (!pixel_buffer_ || pixel_buffer_->width != width || + pixel_buffer_->height != height) { + if (!pixel_buffer_) { + pixel_buffer_ = std::make_unique(); + pixel_buffer_->release_context = &buffer_mutex_; + // Gets invoked after the FlutterDesktopPixelBuffer's + // backing buffer has been uploaded. + pixel_buffer_->release_callback = [](void* opaque) + { + auto mutex = reinterpret_cast(opaque); + // Gets locked just before |CopyPixelBuffer| returns. + mutex->unlock(); + }; + } + pixel_buffer_->width = width; + pixel_buffer_->height = height; + const auto size = width * height * 4; + backing_pixel_buffer_.reset(new uint8_t[size]); + pixel_buffer_->buffer = backing_pixel_buffer_.get(); + } + + const auto src_pitch_in_pixels = mappedResource.RowPitch / 4; + RGBA_to_BGRA(reinterpret_cast(backing_pixel_buffer_.get()), + static_cast(mappedResource.pData), height, + src_pitch_in_pixels, width); + } + + device_context->Unmap(staging_texture, 0); + } + + void TextureBridgeFallback::EnsureStagingTexture(uint32_t width, + uint32_t height, + bool& is_exact_size) + { + // Only recreate an existing texture if it's too small. + if (!staging_texture_ || staging_texture_size_.width < width || + staging_texture_size_.height < height) { + D3D11_TEXTURE2D_DESC dstDesc = {}; + dstDesc.ArraySize = 1; + dstDesc.MipLevels = 1; + dstDesc.BindFlags = 0; + dstDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + dstDesc.Format = static_cast(kPixelFormat); + dstDesc.Width = width; + dstDesc.Height = height; + dstDesc.MiscFlags = 0; + dstDesc.SampleDesc.Count = 1; + dstDesc.SampleDesc.Quality = 0; + dstDesc.Usage = D3D11_USAGE_STAGING; + + staging_texture_ = nullptr; + if (!SUCCEEDED(graphics_context_->d3d_device()->CreateTexture2D( + &dstDesc, nullptr, staging_texture_.put()))) { + std::cerr << "Creating dst texture failed" << std::endl; + return; + } + + staging_texture_size_ = { width, height }; + } + + is_exact_size = staging_texture_size_.width == width && + staging_texture_size_.height == height; + } + + const FlutterDesktopPixelBuffer* TextureBridgeFallback::CopyPixelBuffer( + size_t width, size_t height) + { + const std::lock_guard lock(mutex_); + + if (!is_running_) { + return nullptr; + } + + if (last_frame_) { + ProcessFrame(last_frame_); + } + + auto buffer = pixel_buffer_.get(); + // Only lock the mutex if the buffer is not null + // (to ensure the release callback gets called) + if (buffer) { + // Gets unlocked in the FlutterDesktopPixelBuffer's release callback. + buffer_mutex_.lock(); + } + return buffer; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.h b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.h new file mode 100644 index 00000000..ad07b77c --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_fallback.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +#include "texture_bridge.h" + +namespace flutter_inappwebview_plugin +{ + class TextureBridgeFallback : public TextureBridge { + public: + TextureBridgeFallback(GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual); + ~TextureBridgeFallback() override; + + const FlutterDesktopPixelBuffer* CopyPixelBuffer(size_t width, size_t height); + + private: + Size staging_texture_size_ = { 0, 0 }; + winrt::com_ptr staging_texture_{ nullptr }; + std::mutex buffer_mutex_; + std::unique_ptr backing_pixel_buffer_; + std::unique_ptr pixel_buffer_; + + void ProcessFrame(winrt::com_ptr src_texture); + void EnsureStagingTexture(uint32_t width, uint32_t height, + bool& is_exact_size); + }; +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.cc b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.cc new file mode 100644 index 00000000..c3bdee26 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.cc @@ -0,0 +1,108 @@ +#include "texture_bridge_gpu.h" + +#include + +#include "util/direct3d11.interop.h" + +namespace flutter_inappwebview_plugin +{ + TextureBridgeGpu::TextureBridgeGpu( + GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual) + : TextureBridge(graphics_context, visual) + { + surface_descriptor_.struct_size = sizeof(FlutterDesktopGpuSurfaceDescriptor); + surface_descriptor_.format = + kFlutterDesktopPixelFormatNone; // no format required for DXGI surfaces + } + + void TextureBridgeGpu::ProcessFrame( + winrt::com_ptr src_texture) + { + D3D11_TEXTURE2D_DESC desc; + src_texture->GetDesc(&desc); + + const auto width = desc.Width; + const auto height = desc.Height; + + EnsureSurface(width, height); + + auto device_context = graphics_context_->d3d_device_context(); + + device_context->CopyResource(surface_.get(), src_texture.get()); + device_context->Flush(); + } + + void TextureBridgeGpu::EnsureSurface(uint32_t width, uint32_t height) + { + if (!surface_ || surface_size_.width != width || + surface_size_.height != height) { + D3D11_TEXTURE2D_DESC dstDesc = {}; + dstDesc.ArraySize = 1; + dstDesc.MipLevels = 1; + dstDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + dstDesc.CPUAccessFlags = 0; + dstDesc.Format = static_cast(kPixelFormat); + dstDesc.Width = width; + dstDesc.Height = height; + dstDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; + dstDesc.SampleDesc.Count = 1; + dstDesc.SampleDesc.Quality = 0; + dstDesc.Usage = D3D11_USAGE_DEFAULT; + + surface_ = nullptr; + if (!SUCCEEDED(graphics_context_->d3d_device()->CreateTexture2D( + &dstDesc, nullptr, surface_.put()))) { + std::cerr << "Creating intermediate texture failed" << std::endl; + return; + } + + HANDLE shared_handle; + surface_.try_as(dxgi_surface_); + assert(dxgi_surface_); + dxgi_surface_->GetSharedHandle(&shared_handle); + + surface_descriptor_.handle = shared_handle; + surface_descriptor_.width = surface_descriptor_.visible_width = width; + surface_descriptor_.height = surface_descriptor_.visible_height = height; + surface_descriptor_.release_context = surface_.get(); + surface_descriptor_.release_callback = [](void* release_context) + { + auto texture = reinterpret_cast(release_context); + texture->Release(); + }; + + surface_size_ = { width, height }; + } + } + + const FlutterDesktopGpuSurfaceDescriptor* + TextureBridgeGpu::GetSurfaceDescriptor(size_t width, size_t height) + { + const std::lock_guard lock(mutex_); + + if (!is_running_) { + return nullptr; + } + + if (last_frame_) { + ProcessFrame(last_frame_); + } + + if (surface_) { + // Gets released in the SurfaceDescriptor's release callback. + surface_->AddRef(); + } + + return &surface_descriptor_; + } + + void TextureBridgeGpu::StopInternal() + { + TextureBridge::StopInternal(); + + // For some reason, the destination surface needs to be recreated upon + // resuming. Force |EnsureSurface| to create a new one by resetting it here. + surface_ = nullptr; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.h b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.h new file mode 100644 index 00000000..8b29a321 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/texture_bridge_gpu.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "texture_bridge.h" + +namespace flutter_inappwebview_plugin +{ + class TextureBridgeGpu : public TextureBridge { + public: + TextureBridgeGpu(GraphicsContext* graphics_context, + ABI::Windows::UI::Composition::IVisual* visual); + + const FlutterDesktopGpuSurfaceDescriptor* GetSurfaceDescriptor(size_t width, + size_t height); + + protected: + void StopInternal() override; + + private: + FlutterDesktopGpuSurfaceDescriptor surface_descriptor_ = {}; + Size surface_size_ = { 0, 0 }; + winrt::com_ptr surface_{ nullptr }; + winrt::com_ptr dxgi_surface_; + + void ProcessFrame(winrt::com_ptr src_texture); + void EnsureSurface(uint32_t width, uint32_t height); + }; +} diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/composition.desktop.interop.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/composition.desktop.interop.h new file mode 100644 index 00000000..9b9eb0d3 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/composition.desktop.interop.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +namespace util { + + winrt::com_ptr TryCreateDesktopWindowTarget( + const winrt::com_ptr& + compositor, + HWND window) + { + namespace abi = ABI::Windows::UI::Composition::Desktop; + auto interop = compositor.try_as(); + + winrt::com_ptr target; + interop->CreateDesktopWindowTarget(window, true, target.put()); + return target; + } + +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.cc b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.cc new file mode 100644 index 00000000..acee3437 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.cc @@ -0,0 +1,80 @@ +#include "cpuinfo.h" + +#include "detail/cpuinfo_impl.h" + +#if defined(_MSC_VER) && (defined(__x86_64__) || defined(_M_X64)) +#include "detail/init_msvc_x86.h" +#else +#include "detail/init_unknown.hpp" +#endif + +namespace cpuid { + +cpuinfo::cpuinfo() : impl_(new impl) { init_cpuinfo(*impl_); } + +cpuinfo::~cpuinfo() {} + +// x86 member functions +bool cpuinfo::has_fpu() const { return impl_->m_has_fpu; } + +bool cpuinfo::has_mmx() const { return impl_->m_has_mmx; } + +bool cpuinfo::has_sse() const { return impl_->m_has_sse; } + +bool cpuinfo::has_sse2() const { return impl_->m_has_sse2; } + +bool cpuinfo::has_sse3() const { return impl_->m_has_sse3; } + +bool cpuinfo::has_ssse3() const { return impl_->m_has_ssse3; } + +bool cpuinfo::has_sse4_1() const { return impl_->m_has_sse4_1; } + +bool cpuinfo::has_sse4_2() const { return impl_->m_has_sse4_2; } + +bool cpuinfo::has_pclmulqdq() const { return impl_->m_has_pclmulqdq; } + +bool cpuinfo::has_avx() const { return impl_->m_has_avx; } + +bool cpuinfo::has_avx2() const { return impl_->m_has_avx2; } + +bool cpuinfo::has_avx512_f() const { return impl_->m_has_avx512_f; } + +bool cpuinfo::has_avx512_dq() const { return impl_->m_has_avx512_dq; } + +bool cpuinfo::has_avx512_ifma() const { return impl_->m_has_avx512_ifma; } + +bool cpuinfo::has_avx512_pf() const { return impl_->m_has_avx512_pf; } + +bool cpuinfo::has_avx512_er() const { return impl_->m_has_avx512_er; } + +bool cpuinfo::has_avx512_cd() const { return impl_->m_has_avx512_cd; } + +bool cpuinfo::has_avx512_bw() const { return impl_->m_has_avx512_bw; } + +bool cpuinfo::has_avx512_vl() const { return impl_->m_has_avx512_vl; } + +bool cpuinfo::has_avx512_vbmi() const { return impl_->m_has_avx512_vbmi; } + +bool cpuinfo::has_avx512_vbmi2() const { return impl_->m_has_avx512_vbmi2; } + +bool cpuinfo::has_avx512_vnni() const { return impl_->m_has_avx512_vnni; } + +bool cpuinfo::has_avx512_bitalg() const { return impl_->m_has_avx512_bitalg; } + +bool cpuinfo::has_avx512_vpopcntdq() const { + return impl_->m_has_avx512_vpopcntdq; +} + +bool cpuinfo::has_avx512_4vnniw() const { return impl_->m_has_avx512_4vnniw; } + +bool cpuinfo::has_avx512_4fmaps() const { return impl_->m_has_avx512_4fmaps; } + +bool cpuinfo::has_avx512_vp2intersect() const { + return impl_->m_has_avx512_vp2intersect; +} + +bool cpuinfo::has_f16c() const { return impl_->m_has_f16c; } + +// ARM member functions +bool cpuinfo::has_neon() const { return impl_->m_has_neon; } +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.h new file mode 100644 index 00000000..0cee3598 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/cpuinfo.h @@ -0,0 +1,105 @@ +#pragma once + +#include + +namespace cpuid { + +class cpuinfo { + public: + struct impl; + + cpuinfo(); + ~cpuinfo(); + + // Has X87 FPU + bool has_fpu() const; + + // Return true if the CPU supports MMX + bool has_mmx() const; + + // Return true if the CPU supports SSE + bool has_sse() const; + + // Return true if the CPU supports SSE2 + bool has_sse2() const; + + // Return true if the CPU supports SSE3 + bool has_sse3() const; + + // Return true if the CPU supports SSSE3 + bool has_ssse3() const; + + // Return true if the CPU supports SSE 4.1 + bool has_sse4_1() const; + + // Return true if the CPU supports SSE 4.2 + bool has_sse4_2() const; + + // Return true if the CPU supports pclmulqdq + bool has_pclmulqdq() const; + + // Return true if the CPU supports AVX + bool has_avx() const; + + // Return true if the CPU supports AVX2 + bool has_avx2() const; + + // Return true if the CPU supports AVX512F + bool has_avx512_f() const; + + // Return true if the CPU supports AVX512DQ + bool has_avx512_dq() const; + + // Return true if the CPU supports AVX512_IFMA + bool has_avx512_ifma() const; + + // Return true if the CPU supports AVX512PF + bool has_avx512_pf() const; + + // Return true if the CPU supports AVX512ER + bool has_avx512_er() const; + + // Return true if the CPU supports AVX512CD + bool has_avx512_cd() const; + + // Return true if the CPU supports AVX512BW + bool has_avx512_bw() const; + + // Return true if the CPU supports AVX512VL + bool has_avx512_vl() const; + + // Return true if the CPU supports AVX512_VBMI + bool has_avx512_vbmi() const; + + // Return true if the CPU supports AVX512_VBMI2 + bool has_avx512_vbmi2() const; + + // Return true if the CPU supports AVX512_VNNI + bool has_avx512_vnni() const; + + // Return true if the CPU supports AVX512_BITALG + bool has_avx512_bitalg() const; + + // Return true if the CPU supports AVX512_VPOPCNTDQ + bool has_avx512_vpopcntdq() const; + + // Return true if the CPU supports AVX512_4VNNIW + bool has_avx512_4vnniw() const; + + // Return true if the CPU supports AVX512_4FMAPS + bool has_avx512_4fmaps() const; + + // Return true if the CPU supports AVX512_VP2INTERSECT + bool has_avx512_vp2intersect() const; + + // Return true if the CPU supports F16C + bool has_f16c() const; + + // Return true if the CPU supports NEON + bool has_neon() const; + + private: + // Private implementation + std::unique_ptr impl_; +}; +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/cpuinfo_impl.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/cpuinfo_impl.h new file mode 100644 index 00000000..1d2ee69f --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/cpuinfo_impl.h @@ -0,0 +1,69 @@ +#pragma once + +#include "../cpuinfo.h" + +namespace cpuid { + +struct cpuinfo::impl { + impl() + : m_has_fpu(false), + m_has_mmx(false), + m_has_sse(false), + m_has_sse2(false), + m_has_sse3(false), + m_has_ssse3(false), + m_has_sse4_1(false), + m_has_sse4_2(false), + m_has_pclmulqdq(false), + m_has_avx(false), + m_has_avx2(false), + m_has_avx512_f(false), + m_has_avx512_dq(false), + m_has_avx512_ifma(false), + m_has_avx512_pf(false), + m_has_avx512_er(false), + m_has_avx512_cd(false), + m_has_avx512_bw(false), + m_has_avx512_vl(false), + m_has_avx512_vbmi(false), + m_has_avx512_vbmi2(false), + m_has_avx512_vnni(false), + m_has_avx512_bitalg(false), + m_has_avx512_vpopcntdq(false), + m_has_avx512_4vnniw(false), + m_has_avx512_4fmaps(false), + m_has_avx512_vp2intersect(false), + m_has_f16c(false), + m_has_neon(false) {} + + bool m_has_fpu; + bool m_has_mmx; + bool m_has_sse; + bool m_has_sse2; + bool m_has_sse3; + bool m_has_ssse3; + bool m_has_sse4_1; + bool m_has_sse4_2; + bool m_has_pclmulqdq; + bool m_has_avx; + bool m_has_avx2; + bool m_has_avx512_f; + bool m_has_avx512_dq; + bool m_has_avx512_ifma; + bool m_has_avx512_pf; + bool m_has_avx512_er; + bool m_has_avx512_cd; + bool m_has_avx512_bw; + bool m_has_avx512_vl; + bool m_has_avx512_vbmi; + bool m_has_avx512_vbmi2; + bool m_has_avx512_vnni; + bool m_has_avx512_bitalg; + bool m_has_avx512_vpopcntdq; + bool m_has_avx512_4vnniw; + bool m_has_avx512_4fmaps; + bool m_has_avx512_vp2intersect; + bool m_has_f16c; + bool m_has_neon; +}; +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/extract_x86_flags.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/extract_x86_flags.h new file mode 100644 index 00000000..5bea5866 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/extract_x86_flags.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include "cpuinfo_impl.h" + +namespace cpuid { + +void extract_x86_flags(cpuinfo::impl& info, uint32_t ecx, uint32_t edx) { + info.m_has_fpu = (edx & (1 << 0)) != 0; + info.m_has_mmx = (edx & (1 << 23)) != 0; + info.m_has_sse = (edx & (1 << 25)) != 0; + info.m_has_sse2 = (edx & (1 << 26)) != 0; + info.m_has_sse3 = (ecx & (1 << 0)) != 0; + info.m_has_ssse3 = (ecx & (1 << 9)) != 0; + info.m_has_sse4_1 = (ecx & (1 << 19)) != 0; + info.m_has_sse4_2 = (ecx & (1 << 20)) != 0; + info.m_has_pclmulqdq = (ecx & (1 << 1)) != 0; + info.m_has_avx = (ecx & (1 << 28)) != 0; + info.m_has_f16c = (ecx & (1 << 29)) != 0; +} + +void extract_x86_extended_flags(cpuinfo::impl& info, uint32_t ebx, uint32_t ecx, + uint32_t edx) { + info.m_has_avx2 = (ebx & (1 << 5)) != 0; + info.m_has_avx512_f = (ebx & (1 << 16)) != 0; + info.m_has_avx512_dq = (ebx & (1 << 17)) != 0; + info.m_has_avx512_ifma = (ebx & (1 << 21)) != 0; + info.m_has_avx512_pf = (ebx & (1 << 26)) != 0; + info.m_has_avx512_er = (ebx & (1 << 27)) != 0; + info.m_has_avx512_cd = (ebx & (1 << 28)) != 0; + info.m_has_avx512_bw = (ebx & (1 << 30)) != 0; + info.m_has_avx512_vl = (ebx & (1 << 31)) != 0; + info.m_has_avx512_vbmi = (ecx & (1 << 1)) != 0; + info.m_has_avx512_vbmi2 = (ecx & (1 << 6)) != 0; + info.m_has_avx512_vnni = (ecx & (1 << 11)) != 0; + info.m_has_avx512_bitalg = (ecx & (1 << 12)) != 0; + info.m_has_avx512_vpopcntdq = (ecx & (1 << 14)) != 0; + info.m_has_avx512_4vnniw = (edx & (1 << 2)) != 0; + info.m_has_avx512_4fmaps = (edx & (1 << 3)) != 0; + info.m_has_avx512_vp2intersect = (edx & (1 << 8)) != 0; +} +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_msvc_x86.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_msvc_x86.h new file mode 100644 index 00000000..697270b1 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_msvc_x86.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "cpuinfo_impl.h" +#include "extract_x86_flags.h" + +namespace cpuid { + +void init_cpuinfo(cpuinfo::impl& info) { + int registers[4]; + + // The register information per input can be extracted from here: + // http://en.wikipedia.org/wiki/CPUID + // + // CPUID should be called with EAX=0 first, as this will return the + // maximum supported EAX input value for future calls + __cpuid(registers, 0); + uint32_t maximum_eax = registers[0]; + + // Set registers for basic flag extraction, eax=1 + // All CPUs should support index=1 + if (maximum_eax >= 1U) { + __cpuid(registers, 1); + extract_x86_flags(info, registers[2], registers[3]); + } + + // Set registers for extended flags extraction, eax=7 and ecx=0 + // This operation is not supported on older CPUs, so it should be skipped + // to avoid incorrect results + if (maximum_eax >= 7U) { + __cpuidex(registers, 7, 0); + extract_x86_extended_flags(info, registers[1], registers[2], registers[3]); + } +} +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_unknown.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_unknown.h new file mode 100644 index 00000000..3b52ef2b --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/cpuid/detail/init_unknown.h @@ -0,0 +1,9 @@ + +#pragma once + +#include "cpuinfo_impl.h" + +namespace cpuid { + +void init_cpuinfo(cpuinfo::impl& info) { (void)info; } +} // namespace cpuid diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/d3dutil.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/d3dutil.h new file mode 100644 index 00000000..a32bc157 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/d3dutil.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +inline auto CreateD3DDevice(D3D_DRIVER_TYPE const type, + winrt::com_ptr& device) { + WINRT_ASSERT(!device); + + UINT flags = + D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_VIDEO_SUPPORT; + + //#ifdef _DEBUG + // flags |= D3D11_CREATE_DEVICE_DEBUG; + //#endif + + return D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0, + D3D11_SDK_VERSION, device.put(), nullptr, nullptr); +} + +inline auto CreateD3DDevice() { + winrt::com_ptr device; + HRESULT hr = CreateD3DDevice(D3D_DRIVER_TYPE_HARDWARE, device); + + if (DXGI_ERROR_UNSUPPORTED == hr) { + CreateD3DDevice(D3D_DRIVER_TYPE_WARP, device); + } + + return device; +} diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.cc b/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.cc new file mode 100644 index 00000000..96a74ba9 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.cc @@ -0,0 +1,47 @@ +#include "direct3d11.interop.h" + +namespace flutter_inappwebview_plugin +{ + + namespace { + + typedef HRESULT(WINAPI* CreateDirect3D11DeviceFromDXGIDeviceFn)(IDXGIDevice*, + LPVOID*); + + struct D3DFuncs { + CreateDirect3D11DeviceFromDXGIDeviceFn CreateDirect3D11DeviceFromDXGIDevice = + nullptr; + + D3DFuncs() + { + auto handle = GetModuleHandle(L"d3d11.dll"); + if (!handle) { + return; + } + + CreateDirect3D11DeviceFromDXGIDevice = + reinterpret_cast( + GetProcAddress(handle, "CreateDirect3D11DeviceFromDXGIDevice")); + } + + static const D3DFuncs& instance() + { + static D3DFuncs funcs; + return funcs; + } + }; + + } // namespace + + HRESULT CreateDirect3D11DeviceFromDXGIDevice(IDXGIDevice* dxgiDevice, + IInspectable** graphicsDevice) + { + auto ptr = D3DFuncs::instance().CreateDirect3D11DeviceFromDXGIDevice; + if (ptr) { + return ptr(dxgiDevice, reinterpret_cast(graphicsDevice)); + } + + return E_NOTIMPL; + } + +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.h new file mode 100644 index 00000000..4434815d --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/direct3d11.interop.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +#include "dxgi.h" + +namespace Windows { + namespace Graphics { + namespace DirectX { + namespace Direct3D11 { + struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")) + IDirect3DDxgiInterfaceAccess : ::IUnknown { + virtual HRESULT __stdcall GetInterface(GUID const& id, void** object) = 0; + }; + + } // namespace Direct3D11 + } // namespace DirectX + } // namespace Graphics +} // namespace Windows + +namespace flutter_inappwebview_plugin +{ + + HRESULT CreateDirect3D11DeviceFromDXGIDevice(IDXGIDevice* dxgiDevice, + IInspectable** graphicsDevice); + + template + auto GetDXGIInterfaceFromObject( + winrt::Windows::Foundation::IInspectable const& object) + { + auto access = object.as< + Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>(); + winrt::com_ptr result; + winrt::check_hresult( + access->GetInterface(winrt::guid_of(), result.put_void())); + return result; + } + + template + auto TryGetDXGIInterfaceFromObject(const winrt::com_ptr& object) + { + auto access = object.try_as< + Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>(); + winrt::com_ptr result; + access->GetInterface(winrt::guid_of(), result.put_void()); + return result; + } + +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.cc b/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.cc new file mode 100644 index 00000000..6fa6ab0b --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.cc @@ -0,0 +1,244 @@ +// Based on ANGLE's RoHelper (CompositorNativeWindow11.{cpp,h}) +// - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.h +// - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.cpp +// - https://gist.github.com/clarkezone/43e984fb9bdcd2cfcd9a4f41c208a02f +// +// Copyright 2018 The ANGLE Project Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc. +// Ltd., nor the names of their contributors may be used to endorse +// or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include "rohelper.h" + +#include +#include + +namespace rx { +template +bool AssignProcAddress(HMODULE comBaseModule, const char* name, T*& outProc) { + outProc = reinterpret_cast(GetProcAddress(comBaseModule, name)); + return *outProc != nullptr; +} + +RoHelper::RoHelper(RO_INIT_TYPE init_type) + : mFpWindowsCreateStringReference(nullptr), + mFpGetActivationFactory(nullptr), + mFpWindowsCompareStringOrdinal(nullptr), + mFpCreateDispatcherQueueController(nullptr), + mFpWindowsDeleteString(nullptr), + mFpRoInitialize(nullptr), + mFpRoUninitialize(nullptr), + mWinRtAvailable(false), + mComBaseModule(nullptr), + mCoreMessagingModule(nullptr) { +#ifdef WINUWP + mFpWindowsCreateStringReference = &::WindowsCreateStringReference; + mFpRoInitialize = &::RoInitialize; + mFpRoUninitialize = &::RoUninitialize; + mFpWindowsDeleteString = &::WindowsDeleteString; + mFpGetActivationFactory = &::RoGetActivationFactory; + mFpWindowsCompareStringOrdinal = &::WindowsCompareStringOrdinal; + mFpCreateDispatcherQueueController = &::CreateDispatcherQueueController; + mWinRtAvailable = true; +#else + + mComBaseModule = LoadLibraryA("ComBase.dll"); + + if (mComBaseModule == nullptr) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "WindowsCreateStringReference", + mFpWindowsCreateStringReference)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "RoGetActivationFactory", + mFpGetActivationFactory)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "WindowsCompareStringOrdinal", + mFpWindowsCompareStringOrdinal)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "WindowsDeleteString", + mFpWindowsDeleteString)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "RoInitialize", mFpRoInitialize)) { + return; + } + + if (!AssignProcAddress(mComBaseModule, "RoUninitialize", mFpRoUninitialize)) { + return; + } + + mCoreMessagingModule = LoadLibraryA("coremessaging.dll"); + + if (mCoreMessagingModule == nullptr) { + return; + } + + if (!AssignProcAddress(mCoreMessagingModule, + "CreateDispatcherQueueController", + mFpCreateDispatcherQueueController)) { + return; + } + + auto result = RoInitialize(init_type); + + if (SUCCEEDED(result) || result == S_FALSE || result == RPC_E_CHANGED_MODE) { + mWinRtAvailable = true; + } +#endif +} + +RoHelper::~RoHelper() { +#ifndef WINUWP + if (mWinRtAvailable) { + RoUninitialize(); + } + + if (mCoreMessagingModule != nullptr) { + FreeLibrary(mCoreMessagingModule); + mCoreMessagingModule = nullptr; + } + + if (mComBaseModule != nullptr) { + FreeLibrary(mComBaseModule); + mComBaseModule = nullptr; + } +#endif +} + +bool RoHelper::WinRtAvailable() const { return mWinRtAvailable; } + +bool RoHelper::SupportedWindowsRelease() { + if (!mWinRtAvailable) { + return false; + } + + HSTRING className, contractName; + HSTRING_HEADER classNameHeader, contractNameHeader; + boolean isSupported = false; + + HRESULT hr = GetStringReference( + RuntimeClass_Windows_Foundation_Metadata_ApiInformation, &className, + &classNameHeader); + + if (FAILED(hr)) { + return !!isSupported; + } + + Microsoft::WRL::ComPtr< + ABI::Windows::Foundation::Metadata::IApiInformationStatics> + api; + + hr = GetActivationFactory( + className, + __uuidof(ABI::Windows::Foundation::Metadata::IApiInformationStatics), + &api); + + if (FAILED(hr)) { + return !!isSupported; + } + + hr = GetStringReference(L"Windows.Foundation.UniversalApiContract", + &contractName, &contractNameHeader); + if (FAILED(hr)) { + return !!isSupported; + } + + api->IsApiContractPresentByMajor(contractName, 6, &isSupported); + + return !!isSupported; +} + +HRESULT RoHelper::GetStringReference(PCWSTR source, HSTRING* act, + HSTRING_HEADER* header) { + if (!mWinRtAvailable) { + return E_FAIL; + } + + const wchar_t* str = static_cast(source); + + unsigned int length; + HRESULT hr = SizeTToUInt32(::wcslen(str), &length); + if (FAILED(hr)) { + return hr; + } + + return mFpWindowsCreateStringReference(source, length, header, act); +} + +HRESULT RoHelper::GetActivationFactory(const HSTRING act, + const IID& interfaceId, void** fac) { + if (!mWinRtAvailable) { + return E_FAIL; + } + auto hr = mFpGetActivationFactory(act, interfaceId, fac); + return hr; +} + +HRESULT RoHelper::WindowsCompareStringOrdinal(HSTRING one, HSTRING two, + int* result) { + if (!mWinRtAvailable) { + return E_FAIL; + } + return mFpWindowsCompareStringOrdinal(one, two, result); +} + +HRESULT RoHelper::CreateDispatcherQueueController( + DispatcherQueueOptions options, + ABI::Windows::System::IDispatcherQueueController** + dispatcherQueueController) { + if (!mWinRtAvailable) { + return E_FAIL; + } + return mFpCreateDispatcherQueueController(options, dispatcherQueueController); +} + +HRESULT RoHelper::WindowsDeleteString(HSTRING one) { + if (!mWinRtAvailable) { + return E_FAIL; + } + return mFpWindowsDeleteString(one); +} + +HRESULT RoHelper::RoInitialize(RO_INIT_TYPE type) { + return mFpRoInitialize(type); +} + +void RoHelper::RoUninitialize() { mFpRoUninitialize(); } +} // namespace rx diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.h new file mode 100644 index 00000000..12e7e6cc --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/rohelper.h @@ -0,0 +1,97 @@ +// Based on ANGLE's RoHelper (CompositorNativeWindow11.{cpp,h}) +// - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.h +// - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.cpp +// - https://gist.github.com/clarkezone/43e984fb9bdcd2cfcd9a4f41c208a02f +// +// Copyright 2018 The ANGLE Project Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc. +// Ltd., nor the names of their contributors may be used to endorse +// or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include + +namespace rx { +class RoHelper { + public: + RoHelper(RO_INIT_TYPE init_type); + ~RoHelper(); + bool WinRtAvailable() const; + bool SupportedWindowsRelease(); + HRESULT GetStringReference(PCWSTR source, HSTRING* act, + HSTRING_HEADER* header); + HRESULT GetActivationFactory(const HSTRING act, const IID& interfaceId, + void** fac); + HRESULT WindowsCompareStringOrdinal(HSTRING one, HSTRING two, int* result); + HRESULT CreateDispatcherQueueController( + DispatcherQueueOptions options, + ABI::Windows::System::IDispatcherQueueController** + dispatcherQueueController); + HRESULT WindowsDeleteString(HSTRING one); + HRESULT RoInitialize(RO_INIT_TYPE type); + void RoUninitialize(); + + private: + using WindowsCreateStringReference_ = HRESULT __stdcall(PCWSTR, UINT32, + HSTRING_HEADER*, + HSTRING*); + + using GetActivationFactory_ = HRESULT __stdcall(HSTRING, REFIID, void**); + + using WindowsCompareStringOrginal_ = HRESULT __stdcall(HSTRING, HSTRING, + int*); + + using WindowsDeleteString_ = HRESULT __stdcall(HSTRING); + + using CreateDispatcherQueueController_ = + HRESULT __stdcall(DispatcherQueueOptions, + ABI::Windows::System::IDispatcherQueueController**); + + using RoInitialize_ = HRESULT __stdcall(RO_INIT_TYPE); + using RoUninitialize_ = void __stdcall(); + + WindowsCreateStringReference_* mFpWindowsCreateStringReference; + GetActivationFactory_* mFpGetActivationFactory; + WindowsCompareStringOrginal_* mFpWindowsCompareStringOrdinal; + CreateDispatcherQueueController_* mFpCreateDispatcherQueueController; + WindowsDeleteString_* mFpWindowsDeleteString; + RoInitialize_* mFpRoInitialize; + RoUninitialize_* mFpRoUninitialize; + + bool mWinRtAvailable; + + HMODULE mComBaseModule; + HMODULE mCoreMessagingModule; +}; +} // namespace rx diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.cc b/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.cc new file mode 100644 index 00000000..7b2d8861 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.cc @@ -0,0 +1,54 @@ +#include "string_converter.h" + +#include + +namespace util { +std::string Utf8FromUtf16(std::wstring_view utf16_string) { + if (utf16_string.empty()) { + return std::string(); + } + + auto src_length = static_cast(utf16_string.size()); + int target_length = + ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), + src_length, nullptr, 0, nullptr, nullptr); + + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), src_length, + utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} + +std::wstring Utf16FromUtf8(std::string_view utf8_string) { + if (utf8_string.empty()) { + return std::wstring(); + } + + auto src_length = static_cast(utf8_string.size()); + int target_length = + ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), + src_length, nullptr, 0); + + std::wstring utf16_string; + if (target_length <= 0 || target_length > utf16_string.max_size()) { + return utf16_string; + } + utf16_string.resize(target_length); + int converted_length = + ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), + src_length, utf16_string.data(), target_length); + if (converted_length == 0) { + return std::wstring(); + } + return utf16_string; +} + +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.h new file mode 100644 index 00000000..0a71dbe0 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/string_converter.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace util { +std::string Utf8FromUtf16(std::wstring_view utf16_string); +std::wstring Utf16FromUtf8(std::string_view utf8_string); +} // namespace util diff --git a/flutter_inappwebview_windows/windows/custom_platform_view/util/swizzle.h b/flutter_inappwebview_windows/windows/custom_platform_view/util/swizzle.h new file mode 100644 index 00000000..67f5f7c6 --- /dev/null +++ b/flutter_inappwebview_windows/windows/custom_platform_view/util/swizzle.h @@ -0,0 +1,192 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * see skia/src/opts/SkSwizzler_opts.h + */ + +#pragma once + +#include "cpuid/cpuinfo.h" + +/** + * SK_CPU_SSE_LEVEL + * + * If defined, SK_CPU_SSE_LEVEL should be set to the highest supported level. + * On non-intel CPU this should be undefined. + */ +#define SK_CPU_SSE_LEVEL_SSE1 10 +#define SK_CPU_SSE_LEVEL_SSE2 20 +#define SK_CPU_SSE_LEVEL_SSE3 30 +#define SK_CPU_SSE_LEVEL_SSSE3 31 +#define SK_CPU_SSE_LEVEL_SSE41 41 +#define SK_CPU_SSE_LEVEL_SSE42 42 +#define SK_CPU_SSE_LEVEL_AVX 51 +#define SK_CPU_SSE_LEVEL_AVX2 52 +#define SK_CPU_SSE_LEVEL_SKX 60 + +// Are we in GCC/Clang? +#ifndef SK_CPU_SSE_LEVEL +// These checks must be done in descending order to ensure we set the highest +// available SSE level. +#if defined(__AVX512F__) && defined(__AVX512DQ__) && defined(__AVX512CD__) && \ + defined(__AVX512BW__) && defined(__AVX512VL__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SKX +#elif defined(__AVX2__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX2 +#elif defined(__AVX__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX +#elif defined(__SSE4_2__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE42 +#elif defined(__SSE4_1__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE41 +#elif defined(__SSSE3__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSSE3 +#elif defined(__SSE3__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE3 +#elif defined(__SSE2__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE2 +#endif +#endif + +// Are we in VisualStudio? +#ifndef SK_CPU_SSE_LEVEL +// These checks must be done in descending order to ensure we set the highest +// available SSE level. 64-bit intel guarantees at least SSE2 support. +#if defined(__AVX512F__) && defined(__AVX512DQ__) && defined(__AVX512CD__) && \ + defined(__AVX512BW__) && defined(__AVX512VL__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SKX +#elif defined(__AVX2__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX2 +#elif defined(__AVX__) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_AVX +#elif defined(_M_X64) || defined(_M_AMD64) +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE2 +#elif defined(_M_IX86_FP) +#if _M_IX86_FP >= 2 +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE2 +#elif _M_IX86_FP == 1 +#define SK_CPU_SSE_LEVEL SK_CPU_SSE_LEVEL_SSE1 +#endif +#endif +#endif + +inline void RGBA_to_BGRA_portable(uint32_t* dst, const uint32_t* src, + int height, int src_stride, int dst_stride) { + auto width = std::min(src_stride, dst_stride); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + uint8_t a = (src[x] >> 24) & 0xFF, b = (src[x] >> 16) & 0xFF, + g = (src[x] >> 8) & 0xFF, r = (src[x] >> 0) & 0xFF; + dst[x] = (uint32_t)a << 24 | (uint32_t)r << 16 | (uint32_t)g << 8 | + (uint32_t)b << 0; + } + + src += src_stride; + dst += dst_stride; + } +} + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SKX + +inline void RGBA_to_BGRA_SKX(uint32_t* dst, const uint32_t* src, int height, + int src_stride, int dst_stride) { + const uint8_t mask[64] = {2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, + 13, 12, 15, 2, 1, 0, 3, 6, 5, 4, 7, 10, 9, + 8, 11, 14, 13, 12, 15, 2, 1, 0, 3, 6, 5, 4, + 7, 10, 9, 8, 11, 14, 13, 12, 15, 2, 1, 0, 3, + 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15}; + const __m512i swapRB = _mm512_loadu_si512(mask); + + auto width = std::min(src_stride, dst_stride); + + for (int y = 0; y < height; y++) { + auto cw = width; + auto rptr = src; + auto dptr = dst; + while (cw >= 16) { + __m512i rgba = _mm512_loadu_si512((const __m512i*)rptr); + __m512i bgra = _mm512_shuffle_epi8(rgba, swapRB); + _mm512_storeu_si512((__m512i*)dptr, bgra); + + rptr += 16; + dptr += 16; + cw -= 16; + } + + for (auto x = 0; x < cw; x++) { + uint8_t a = (rptr[x] >> 24) & 0xFF, b = (rptr[x] >> 16) & 0xFF, + g = (rptr[x] >> 8) & 0xFF, r = (rptr[x] >> 0) & 0xFF; + dptr[x] = (uint32_t)a << 24 | (uint32_t)r << 16 | (uint32_t)g << 8 | + (uint32_t)b << 0; + } + + src += src_stride; + dst += dst_stride; + } +} + +#endif + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + +inline void RGBA_to_BGRA_AVX2(uint32_t* dst, const uint32_t* src, int height, + int src_stride, int dst_stride) { + const __m256i swapRB = + _mm256_setr_epi8(2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15, 2, + 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15); + + auto width = std::min(src_stride, dst_stride); + + for (int y = 0; y < height; y++) { + auto cw = width; + auto rptr = src; + auto dptr = dst; + while (cw >= 8) { + __m256i rgba = _mm256_loadu_si256((const __m256i*)rptr); + __m256i bgra = _mm256_shuffle_epi8(rgba, swapRB); + _mm256_storeu_si256((__m256i*)dptr, bgra); + + rptr += 8; + dptr += 8; + cw -= 8; + } + + for (auto x = 0; x < cw; x++) { + uint8_t a = (rptr[x] >> 24) & 0xFF, b = (rptr[x] >> 16) & 0xFF, + g = (rptr[x] >> 8) & 0xFF, r = (rptr[x] >> 0) & 0xFF; + dptr[x] = (uint32_t)a << 24 | (uint32_t)r << 16 | (uint32_t)g << 8 | + (uint32_t)b << 0; + } + + src += src_stride; + dst += dst_stride; + } +} + +#endif + +inline void RGBA_to_BGRA(uint32_t* dst, const uint32_t* src, int height, + int src_stride, int dst_stride) { + static cpuid::cpuinfo info; + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SKX + if (info.has_avx512_f() && info.has_avx512_dq() && info.has_avx512_cd() && + info.has_avx512_bw() && info.has_avx512_vl()) { + return RGBA_to_BGRA_SKX(dst, src, height, src_stride, dst_stride); + } +#endif + +#if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 + if (info.has_avx2()) { + return RGBA_to_BGRA_AVX2(dst, src, height, src_stride, dst_stride); + } +#endif + + RGBA_to_BGRA_portable(dst, src, height, src_stride, dst_stride); +} diff --git a/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.cpp b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.cpp index 74eb2287..dfbb46e1 100644 --- a/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.cpp +++ b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.cpp @@ -4,6 +4,11 @@ #include #include "in_app_browser/in_app_browser_manager.h" +#include "in_app_webview/in_app_webview_manager.h" + +#pragma comment(lib, "Shlwapi.lib") +#pragma comment(lib, "dxgi.lib") +#pragma comment(lib, "d3d11.lib") namespace flutter_inappwebview_plugin { @@ -18,6 +23,7 @@ namespace flutter_inappwebview_plugin FlutterInappwebviewWindowsPlugin::FlutterInappwebviewWindowsPlugin(flutter::PluginRegistrarWindows* registrar) : registrar(registrar) { + inAppWebViewManager = std::make_unique(this); inAppBrowserManager = std::make_unique(this); } diff --git a/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.h b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.h index ffa0a576..9e882ea7 100644 --- a/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.h +++ b/flutter_inappwebview_windows/windows/flutter_inappwebview_windows_plugin.h @@ -6,11 +6,13 @@ namespace flutter_inappwebview_plugin { + class InAppWebViewManager; class InAppBrowserManager; class FlutterInappwebviewWindowsPlugin : public flutter::Plugin { public: flutter::PluginRegistrarWindows* registrar; + std::unique_ptr inAppWebViewManager; std::unique_ptr inAppBrowserManager; static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.cpp b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.cpp index 219ca230..577db80c 100644 --- a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.cpp +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.cpp @@ -26,7 +26,7 @@ namespace flutter_inappwebview_plugin m_hWnd = CreateWindowEx( 0, // Optional window styles. - InAppBrowser::CLASS_NAME, // Window class + wndClass.lpszClassName, // Window class L"", // Window text WS_OVERLAPPEDWINDOW, // Window style @@ -35,25 +35,35 @@ namespace flutter_inappwebview_plugin NULL, // Parent window NULL, // Menu - m_hInstance,// Instance handle + wndClass.hInstance,// Instance handle this // Additional application data ); - ShowWindow(m_hWnd, SW_SHOW); + ShowWindow(m_hWnd, settings->hidden ? SW_HIDE : SW_SHOW); InAppWebViewCreationParams webViewParams = { id, params.initialWebViewSettings }; - webView = std::make_unique(plugin, webViewParams, m_hWnd, InAppBrowser::METHOD_CHANNEL_NAME_PREFIX + id, [this]() -> void + InAppWebView::createInAppWebViewEnv(m_hWnd, false, + [this, webViewParams](wil::com_ptr webViewEnv, wil::com_ptr webViewController, wil::com_ptr webViewCompositionController) -> void { - if (channelDelegate) { - channelDelegate->onBrowserCreated(); - } + if (webViewEnv && webViewController) { + webView = std::make_unique(this, this->plugin, webViewParams, m_hWnd, std::move(webViewEnv), std::move(webViewController), nullptr); + webView->initChannel(std::nullopt, InAppBrowser::METHOD_CHANNEL_NAME_PREFIX + id); - if (initialUrlRequest.has_value()) { - webView->loadUrl(initialUrlRequest.value()); + if (channelDelegate) { + channelDelegate->onBrowserCreated(); + } + + if (initialUrlRequest.has_value()) { + webView->loadUrl(initialUrlRequest.value()); + } + } + else { + std::cerr << "Cannot create the InAppWebView instance!" << std::endl; + close(); } }); @@ -148,6 +158,35 @@ namespace flutter_inappwebview_plugin // }).Get()); } + void InAppBrowser::close() const + { + DestroyWindow(m_hWnd); + } + + void InAppBrowser::show() const + { + ShowWindow(m_hWnd, SW_SHOW); + webView->webViewController->put_IsVisible(true); + } + + void InAppBrowser::hide() const + { + ShowWindow(m_hWnd, SW_HIDE); + webView->webViewController->put_IsVisible(false); + } + + bool InAppBrowser::isHidden() const + { + return !IsWindowVisible(m_hWnd); + } + + void InAppBrowser::didChangeTitle(const std::optional& title) const + { + if (title.has_value()) { + SetWindowText(m_hWnd, ansi_to_wide(title.value()).c_str()); + } + } + LRESULT CALLBACK InAppBrowser::WndProc( HWND window, UINT message, diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.h b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.h index 9e91cf14..7c6899a3 100644 --- a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.h +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser.h @@ -43,6 +43,13 @@ namespace flutter_inappwebview_plugin InAppBrowser(const FlutterInappwebviewWindowsPlugin* plugin, const InAppBrowserCreationParams& params); ~InAppBrowser(); + void close() const; + void show() const; + void hide() const; + bool isHidden() const; + + void didChangeTitle(const std::optional& title) const; + private: const HINSTANCE m_hInstance; HWND m_hWnd; diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.cpp b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.cpp index b07181f4..9086497f 100644 --- a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.cpp +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.cpp @@ -18,7 +18,7 @@ namespace flutter_inappwebview_plugin { if (method_call.method_name().compare("open") == 0) { auto* arguments = std::get_if(method_call.arguments()); - open(arguments); + createInAppWebView(arguments); result->Success(flutter::EncodableValue(true)); } else { @@ -26,7 +26,7 @@ namespace flutter_inappwebview_plugin } } - void InAppBrowserManager::open(const flutter::EncodableMap* arguments) + void InAppBrowserManager::createInAppWebView(const flutter::EncodableMap* arguments) { auto id = get_fl_map_value(*arguments, "id"); auto urlRequestMap = get_optional_fl_map_value(*arguments, "urlRequest"); diff --git a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.h b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.h index 31180fee..cf77e008 100644 --- a/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.h +++ b/flutter_inappwebview_windows/windows/in_app_browser/in_app_browser_manager.h @@ -27,7 +27,7 @@ namespace flutter_inappwebview_plugin const flutter::MethodCall& method_call, std::unique_ptr> result); - void open(const flutter::EncodableMap* arguments); + void createInAppWebView(const flutter::EncodableMap* arguments); }; } #endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_BROWSER_MANAGER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp index 5f1f33dd..bb66d8f9 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.cpp @@ -1,77 +1,135 @@ -#pragma comment(lib, "Shlwapi.lib") - #include #include #include #include +#include "../custom_platform_view/util/composition.desktop.interop.h" #include "../types/web_resource_error.h" #include "../types/web_resource_request.h" #include "../utils/strconv.h" #include "../utils/util.h" #include "in_app_webview.h" +#include "in_app_webview_manager.h" namespace flutter_inappwebview_plugin { using namespace Microsoft::WRL; - InAppWebView::InAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, const std::function completionHandler) - : plugin(plugin), id(params.id), settings(params.initialSettings), channelDelegate(std::make_unique(this, plugin->registrar->messenger())) + InAppWebView::InAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController) + : plugin(plugin), id(params.id), webViewEnv(std::move(webViewEnv)), webViewController(std::move(webViewController)), webViewCompositionController(std::move(webViewCompositionController)), + settings(params.initialSettings) { - createWebView(parentWindow, completionHandler); + this->webViewController->get_CoreWebView2(webView.put()); + + if (this->webViewCompositionController) { + if (!createSurface(parentWindow, plugin->inAppWebViewManager->compositor())) { + std::cerr << "Cannot create InAppWebView surface\n"; + } + registerSurfaceEventHandlers(); + } + else { + // Resize WebView to fit the bounds of the parent window + RECT bounds; + GetClientRect(parentWindow, &bounds); + this->webViewController->put_Bounds(bounds); + } + + wil::com_ptr webView2Settings; + if (SUCCEEDED(webView->get_Settings(&webView2Settings))) { + webView2Settings->put_IsScriptEnabled(settings->javaScriptEnabled); + webView2Settings->put_IsZoomControlEnabled(settings->supportZoom); + + wil::com_ptr webView2Settings2; + if (SUCCEEDED(webView2Settings->QueryInterface(IID_PPV_ARGS(&webView2Settings2)))) { + if (!settings->userAgent.empty()) { + webView2Settings2->put_UserAgent(ansi_to_wide(settings->userAgent).c_str()); + } + } + } + + registerEventHandlers(); } - InAppWebView::InAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, const std::string& channelName, const std::function completionHandler) - : plugin(plugin), id(params.id), settings(params.initialSettings), channelDelegate(std::make_unique(this, plugin->registrar->messenger(), channelName)) + InAppWebView::InAppWebView(InAppBrowser* inAppBrowser, const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController) + : InAppWebView(plugin, params, parentWindow, std::move(webViewEnv), std::move(webViewController), std::move(webViewCompositionController)) { - createWebView(parentWindow, completionHandler); + this->inAppBrowser = inAppBrowser; } - void InAppWebView::createWebView(const HWND parentWindow, const std::function completionHandler) + void InAppWebView::createInAppWebViewEnv(const HWND parentWindow, const bool willBeSurface, std::function webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController)> completionHandler) { CreateCoreWebView2EnvironmentWithOptions(nullptr, nullptr, nullptr, Callback( - [parentWindow, completionHandler, this](HRESULT result, ICoreWebView2Environment* env) -> HRESULT + [parentWindow, completionHandler, willBeSurface](HRESULT result, wil::com_ptr env) -> HRESULT { - webViewEnv = env; - // Create a CoreWebView2Controller and get the associated CoreWebView2 whose parent is the main window HWND - env->CreateCoreWebView2Controller(parentWindow, Callback( - [parentWindow, completionHandler, this](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT - { - if (controller != nullptr) { - webViewController = controller; - webViewController->get_CoreWebView2(webView.put()); - } + if (FAILED(result) || !env) { + completionHandler(nullptr, nullptr, nullptr); + debugLog(getErrorMessage(result)); + return E_FAIL; + } - wil::com_ptr webView2Settings; - if (SUCCEEDED(webView->get_Settings(&webView2Settings))) { - webView2Settings->put_IsScriptEnabled(settings->javaScriptEnabled); - webView2Settings->put_IsZoomControlEnabled(settings->supportZoom); - webView2Settings->put_IsStatusBarEnabled(true); + wil::com_ptr webViewEnv3; + if (willBeSurface && SUCCEEDED(env->QueryInterface(IID_PPV_ARGS(&webViewEnv3)))) { + webViewEnv3->CreateCoreWebView2CompositionController(parentWindow, Callback( + [completionHandler, env](HRESULT result, wil::com_ptr compositionController) -> HRESULT + { + wil::com_ptr webViewController = compositionController.try_query(); - wil::com_ptr webView2Settings2; - if (SUCCEEDED(webView2Settings->QueryInterface(IID_PPV_ARGS(&webView2Settings2)))) { - if (!settings->userAgent.empty()) { - webView2Settings2->put_UserAgent(ansi_to_wide(settings->userAgent).c_str()); - } + if (FAILED(result) || !webViewController) { + completionHandler(nullptr, nullptr, nullptr); + debugLog(getErrorMessage(result)); + return E_FAIL; } + + ICoreWebView2Controller3* webViewController3; + HRESULT hr = webViewController->QueryInterface(IID_PPV_ARGS(&webViewController3)); + if (SUCCEEDED(hr)) { + webViewController3->put_BoundsMode(COREWEBVIEW2_BOUNDS_MODE_USE_RAW_PIXELS); + webViewController3->put_ShouldDetectMonitorScaleChanges(FALSE); + webViewController3->put_RasterizationScale(1.0); + } + else { + debugLog(getErrorMessage(hr)); + } + + completionHandler(std::move(env), std::move(webViewController), std::move(compositionController)); + return S_OK; } + ).Get()); + } + else { + env->CreateCoreWebView2Controller(parentWindow, Callback( + [completionHandler, env](HRESULT result, wil::com_ptr controller) -> HRESULT + { + if (FAILED(result) || !controller) { + completionHandler(nullptr, nullptr, nullptr); + debugLog(getErrorMessage(result)); + return E_FAIL; + } - // Resize WebView to fit the bounds of the parent window - RECT bounds; - GetClientRect(parentWindow, &bounds); - webViewController->put_Bounds(bounds); - - registerEventHandlers(); - - completionHandler(); - - return S_OK; - }).Get()); + completionHandler(std::move(env), std::move(controller), nullptr); + return S_OK; + }).Get()); + } return S_OK; }).Get()); } + void InAppWebView::initChannel(const std::optional> viewId, const std::optional channelName) + { + if (viewId.has_value()) { + id = viewId.value(); + } + channelDelegate = channelName.has_value() ? std::make_unique(this, plugin->registrar->messenger(), channelName.value()) : + std::make_unique(this, plugin->registrar->messenger()); + } + void InAppWebView::registerEventHandlers() { if (!webView) { @@ -124,17 +182,17 @@ namespace flutter_inappwebview_plugin UINT64 navigationId; if (SUCCEEDED(args->get_NavigationId(&navigationId))) { - navigationActions.insert({ navigationId, navigationAction }); + navigationActions.insert({ navigationId, navigationAction }); callShouldOverrideUrlLoading_ = } - if (callShouldOverrideUrlLoading && requestMethod == nullptr) { + if (callShouldOverrideUrlLoading_ && requestMethod == nullptr) { // for some reason, we can't cancel and load an URL with other HTTP methods than GET, // so ignore the shouldOverrideUrlLoading event. auto callback = std::make_unique(); callback->nonNullSuccess = [this, urlRequest](const NavigationActionPolicy actionPolicy) { - callShouldOverrideUrlLoading = false; + callShouldOverrideUrlLoading_ = false; if (actionPolicy == allow) { loadUrl(*urlRequest); } @@ -142,7 +200,7 @@ namespace flutter_inappwebview_plugin }; auto defaultBehaviour = [this, urlRequest](const std::optional actionPolicy) { - callShouldOverrideUrlLoading = false; + callShouldOverrideUrlLoading_ = false; loadUrl(*urlRequest); }; callback->defaultBehaviour = defaultBehaviour; @@ -155,7 +213,7 @@ namespace flutter_inappwebview_plugin args->put_Cancel(true); } else { - callShouldOverrideUrlLoading = true; + callShouldOverrideUrlLoading_ = true; channelDelegate->onLoadStart(url); args->put_Cancel(false); } @@ -207,14 +265,53 @@ namespace flutter_inappwebview_plugin return S_OK; } ).Get(), nullptr); + + webView->add_DocumentTitleChanged(Callback( + [this](ICoreWebView2* sender, IUnknown* args) + { + if (channelDelegate) { + wil::unique_cotaskmem_string title; + sender->get_DocumentTitle(&title); + channelDelegate->onTitleChanged(title.is_valid() ? wide_to_ansi(title.get()) : std::optional{}); + } + return S_OK; + } + ).Get(), nullptr); + } + + void InAppWebView::registerSurfaceEventHandlers() + { + if (!webViewCompositionController) { + return; + } + + webViewCompositionController->add_CursorChanged( + Callback( + [this](ICoreWebView2CompositionController* sender, + IUnknown* args) -> HRESULT + { + HCURSOR cursor; + if (cursorChangedCallback_ && + sender->get_Cursor(&cursor) == S_OK) { + cursorChangedCallback_(cursor); + } + return S_OK; + }) + .Get(), nullptr); } std::optional InAppWebView::getUrl() const { - LPWSTR uri = nullptr; + LPWSTR uri; return SUCCEEDED(webView->get_Source(&uri)) ? wide_to_utf8(uri) : std::optional{}; } + std::optional InAppWebView::getTitle() const + { + LPWSTR title; + return SUCCEEDED(webView->get_DocumentTitle(&title)) ? wide_to_utf8(title) : std::optional{}; + } + void InAppWebView::loadUrl(const URLRequest& urlRequest) const { if (!webView || !urlRequest.url.has_value()) { @@ -261,6 +358,338 @@ namespace flutter_inappwebview_plugin } } + void InAppWebView::reload() const + { + webView->Reload(); + } + + void InAppWebView::goBack() const + { + webView->GoBack(); + } + + void InAppWebView::goForward() const + { + webView->GoForward(); + } + + void InAppWebView::evaluateJavascript(const std::string& source, std::function completionHanlder) const + { + webView->ExecuteScript(ansi_to_wide(source).c_str(), + Callback( + [completionHanlder = std::move(completionHanlder)](HRESULT error, PCWSTR result) -> HRESULT + { + if (error != S_OK) { + debugLog(getErrorMessage(error)); + } + completionHanlder(wide_to_ansi(result)); + return S_OK; + }).Get()); + } + + // flutter_view + void InAppWebView::setSurfaceSize(size_t width, size_t height, float scale_factor) + { + if (!webViewController) { + return; + } + + if (surface_ && width > 0 && height > 0) { + scaleFactor_ = scale_factor; + auto scaled_width = width * scale_factor; + auto scaled_height = height * scale_factor; + + RECT bounds; + bounds.left = 0; + bounds.top = 0; + bounds.right = static_cast(scaled_width); + bounds.bottom = static_cast(scaled_height); + + surface_->put_Size({ scaled_width, scaled_height }); + + wil::com_ptr webViewController3; + if (SUCCEEDED(webViewController->QueryInterface(IID_PPV_ARGS(&webViewController3)))) { + webViewController3->put_RasterizationScale(scale_factor); + } + + if (webViewController->put_Bounds(bounds) != S_OK) { + std::cerr << "Setting webview bounds failed." << std::endl; + } + + if (surfaceSizeChangedCallback_) { + surfaceSizeChangedCallback_(width, height); + } + } + } + + void InAppWebView::setCursorPos(double x, double y) + { + if (!webViewCompositionController) { + return; + } + + POINT point; + point.x = static_cast(x * scaleFactor_); + point.y = static_cast(y * scaleFactor_); + lastCursorPos_ = point; + + // https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.774.44 + webViewCompositionController->SendMouseInput( + COREWEBVIEW2_MOUSE_EVENT_KIND::COREWEBVIEW2_MOUSE_EVENT_KIND_MOVE, + virtualKeys_.state(), 0, point); + } + + void InAppWebView::setPointerUpdate(int32_t pointer, + InAppWebViewPointerEventKind eventKind, double x, + double y, double size, double pressure) + { + if (!webViewEnv || !webViewCompositionController) { + return; + } + + COREWEBVIEW2_POINTER_EVENT_KIND event = + COREWEBVIEW2_POINTER_EVENT_KIND_UPDATE; + UINT32 pointerFlags = POINTER_FLAG_NONE; + switch (eventKind) { + case InAppWebViewPointerEventKind::Activate: + event = COREWEBVIEW2_POINTER_EVENT_KIND_ACTIVATE; + break; + case InAppWebViewPointerEventKind::Down: + event = COREWEBVIEW2_POINTER_EVENT_KIND_DOWN; + pointerFlags = + POINTER_FLAG_DOWN | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT; + break; + case InAppWebViewPointerEventKind::Enter: + event = COREWEBVIEW2_POINTER_EVENT_KIND_ENTER; + break; + case InAppWebViewPointerEventKind::Leave: + event = COREWEBVIEW2_POINTER_EVENT_KIND_LEAVE; + break; + case InAppWebViewPointerEventKind::Up: + event = COREWEBVIEW2_POINTER_EVENT_KIND_UP; + pointerFlags = POINTER_FLAG_UP; + break; + case InAppWebViewPointerEventKind::Update: + event = COREWEBVIEW2_POINTER_EVENT_KIND_UPDATE; + pointerFlags = + POINTER_FLAG_UPDATE | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT; + break; + } + + POINT point; + point.x = static_cast(x * scaleFactor_); + point.y = static_cast(y * scaleFactor_); + + RECT rect; + rect.left = point.x - 2; + rect.right = point.x + 2; + rect.top = point.y - 2; + rect.bottom = point.y + 2; + + wil::com_ptr webViewEnv3; + if (SUCCEEDED(webViewEnv->QueryInterface(IID_PPV_ARGS(&webViewEnv3)))) { + wil::com_ptr pInfo; + if (SUCCEEDED(webViewEnv3->CreateCoreWebView2PointerInfo(&pInfo))) { + if (pInfo) { + pInfo->put_PointerId(pointer); + pInfo->put_PointerKind(PT_TOUCH); + pInfo->put_PointerFlags(pointerFlags); + pInfo->put_TouchFlags(TOUCH_FLAG_NONE); + pInfo->put_TouchMask(TOUCH_MASK_CONTACTAREA | TOUCH_MASK_PRESSURE); + pInfo->put_TouchPressure( + std::clamp((UINT32)(pressure == 0.0 ? 1024 : 1024 * pressure), + (UINT32)0, (UINT32)1024)); + pInfo->put_PixelLocationRaw(point); + pInfo->put_TouchContactRaw(rect); + webViewCompositionController->SendPointerInput(event, pInfo.get()); + } + } + } + } + + void InAppWebView::setPointerButtonState(InAppWebViewPointerButton button, bool is_down) + { + if (!webViewCompositionController) { + return; + } + + COREWEBVIEW2_MOUSE_EVENT_KIND kind; + switch (button) { + case InAppWebViewPointerButton::Primary: + virtualKeys_.setIsLeftButtonDown(is_down); + kind = is_down ? COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_DOWN + : COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_UP; + break; + case InAppWebViewPointerButton::Secondary: + virtualKeys_.setIsRightButtonDown(is_down); + kind = is_down ? COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_DOWN + : COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_UP; + break; + case InAppWebViewPointerButton::Tertiary: + virtualKeys_.setIsMiddleButtonDown(is_down); + kind = is_down ? COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_DOWN + : COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_UP; + break; + default: + kind = static_cast(0); + } + + webViewCompositionController->SendMouseInput(kind, virtualKeys_.state(), 0, + lastCursorPos_); + } + + void InAppWebView::sendScroll(double delta, bool horizontal) + { + if (!webViewCompositionController) { + return; + } + + // delta * 6 gives me a multiple of WHEEL_DELTA (120) + constexpr auto kScrollMultiplier = 6; + + auto offset = static_cast(delta * kScrollMultiplier); + + /* + // For some reason, + // setting the point other than (x: 0, y: 0) + // will not make the scroll work. + // Unfortunately, this will break the scroll event + // for nested HTML scrollable elements. + POINT point; + point.x = 0; + point.y = 0; + + + if (horizontal) { + webViewCompositionController->SendMouseInput( + COREWEBVIEW2_MOUSE_EVENT_KIND_HORIZONTAL_WHEEL, virtual_keys_.state(), + offset, point); + } + else { + webViewCompositionController->SendMouseInput(COREWEBVIEW2_MOUSE_EVENT_KIND_WHEEL, + virtual_keys_.state(), offset, + point); + } + */ + + // Workaround for scroll events + auto workaroundScrollJS = "(function(horizontal, offset, x, y) { \ + function elemCanScrollY(elem) { \ + if (elem.scrollTop > 0) { \ + return elem; \ + } else { \ + elem.scrollTop++; \ + const top = elem.scrollTop; \ + top && (elem.scrollTop = 0); \ + if (top > 0) { \ + return elem; \ + } else { \ + return elemCanScrollY(elem.parentElement); \ + } \ + } \ + } \ + function elemCanScrollX(elem) { \ + if (elem.scrollLeft > 0) { \ + return elem; \ + } else { \ + elem.scrollLeft++; \ + const left = elem.scrollLeft; \ + left && (elem.scrollLeft = 0); \ + if (left > 0) { \ + return elem; \ + } else { \ + return elemCanScrollX(elem.parentElement); \ + } \ + } \ + } \ + const elem = document.elementFromPoint(x, y); \ + const elem2 = horizontal ? elemCanScrollX(elem) : elemCanScrollY(elem); \ + const handled = elem2 != null && elem2 != document.documentElement && elem2 != document.body; \ + if (handled) { \ + elem2.scrollBy({left: horizontal ? offset : 0, top: horizontal ? 0 : offset}); \ + } \ + return handled; \ +})(" + std::to_string(horizontal) + ", " + std::to_string(offset) + ", " + std::to_string(lastCursorPos_.x) + ", " + std::to_string(lastCursorPos_.y) + ");"; + + webView->ExecuteScript(ansi_to_wide(workaroundScrollJS).c_str(), Callback( + [this, horizontal, offset](HRESULT error, PCWSTR result) -> HRESULT + { + if (webViewCompositionController && (error != S_OK || wide_to_ansi(result).compare("false") == 0)) { + // try to use native mouse wheel handler + + POINT point; + point.x = 0; + point.y = 0; + + if (horizontal) { + webViewCompositionController->SendMouseInput( + COREWEBVIEW2_MOUSE_EVENT_KIND_HORIZONTAL_WHEEL, virtualKeys_.state(), + offset, point); + } + else { + webViewCompositionController->SendMouseInput(COREWEBVIEW2_MOUSE_EVENT_KIND_WHEEL, + virtualKeys_.state(), offset, + point); + } + } + + return S_OK; + }).Get()); + } + + void InAppWebView::setScrollDelta(double delta_x, double delta_y) + { + if (!webViewCompositionController) { + return; + } + + if (delta_x != 0.0) { + sendScroll(delta_x, true); + } + if (delta_y != 0.0) { + sendScroll(delta_y, false); + } + } + + bool InAppWebView::createSurface(const HWND parentWindow, + winrt::com_ptr compositor) + { + if (!webViewCompositionController || !webViewController) { + return false; + } + + winrt::com_ptr root; + if (FAILED(compositor->CreateContainerVisual(root.put()))) { + return false; + } + surface_ = root.try_as(); + assert(surface_); + + // initial size. doesn't matter as we resize the surface anyway. + surface_->put_Size({ 1280, 720 }); + surface_->put_IsVisible(true); + + winrt::com_ptr webview_visual; + compositor->CreateContainerVisual( + reinterpret_cast( + webview_visual.put())); + + auto webview_visual2 = + webview_visual.try_as(); + if (webview_visual2) { + webview_visual2->put_RelativeSizeAdjustment({ 1.0f, 1.0f }); + } + + winrt::com_ptr children; + root->get_Children(children.put()); + children->InsertAtTop(webview_visual.get()); + webViewCompositionController->put_RootVisualTarget(webview_visual2.get()); + + webViewController->put_IsVisible(true); + + return true; + } + bool InAppWebView::isSslError(const COREWEBVIEW2_WEB_ERROR_STATUS& webErrorStatus) { return webErrorStatus >= COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_COMMON_NAME_IS_INCORRECT && webErrorStatus <= COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_IS_INVALID; @@ -276,6 +705,7 @@ namespace flutter_inappwebview_plugin webViewController->Close(); } navigationActions.clear(); + inAppBrowser = nullptr; plugin = nullptr; } } \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h index d8fa450e..61bbca31 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview.h @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #include "../flutter_inappwebview_windows_plugin.h" #include "../types/navigation_action.h" @@ -13,10 +16,59 @@ namespace flutter_inappwebview_plugin { + class InAppBrowser; + using namespace Microsoft::WRL; + // custom_platform_view + enum class InAppWebViewPointerButton { None, Primary, Secondary, Tertiary }; + enum class InAppWebViewPointerEventKind { Activate, Down, Enter, Leave, Up, Update }; + typedef std::function + SurfaceSizeChangedCallback; + typedef std::function CursorChangedCallback; + struct VirtualKeyState { + public: + inline void setIsLeftButtonDown(bool is_down) + { + set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_LEFT_BUTTON, + is_down); + } + + inline void setIsRightButtonDown(bool is_down) + { + set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_RIGHT_BUTTON, + is_down); + } + + inline void setIsMiddleButtonDown(bool is_down) + { + set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_MIDDLE_BUTTON, + is_down); + } + + inline COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS state() const { return state_; } + + private: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS state_ = + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS:: + COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_NONE; + + inline void set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS key, bool flag) + { + if (flag) { + state_ |= key; + } + else { + state_ &= ~key; + } + } + }; + struct InAppWebViewCreationParams { - const std::variant id; + const std::variant id; const std::shared_ptr initialSettings; }; @@ -26,26 +78,75 @@ namespace flutter_inappwebview_plugin static inline const std::string METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_"; const FlutterInappwebviewWindowsPlugin* plugin; - const std::variant id; + std::variant id; wil::com_ptr webViewEnv; wil::com_ptr webViewController; + wil::com_ptr webViewCompositionController; wil::com_ptr webView; - const std::unique_ptr channelDelegate; + std::unique_ptr channelDelegate; std::map> navigationActions = {}; const std::shared_ptr settings; + InAppBrowser* inAppBrowser = nullptr; - InAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, const std::function completionHandler); - InAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, const std::string& channelName, const std::function completionHandler); + InAppWebView(const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, + wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController); + InAppWebView(InAppBrowser* inAppBrowser, const FlutterInappwebviewWindowsPlugin* plugin, const InAppWebViewCreationParams& params, const HWND parentWindow, + wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController); ~InAppWebView(); + static void createInAppWebViewEnv(const HWND parentWindow, const bool willBeSurface, std::function webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController)> completionHandler); + + // custom_platform_view + ABI::Windows::UI::Composition::IVisual* const surface() + { + return surface_.get(); + } + void setSurfaceSize(size_t width, size_t height, float scale_factor); + void setCursorPos(double x, double y); + void setPointerUpdate(int32_t pointer, InAppWebViewPointerEventKind eventKind, + double x, double y, double size, double pressure); + void setPointerButtonState(InAppWebViewPointerButton button, bool isDown); + void sendScroll(double offset, bool horizontal); + void setScrollDelta(double delta_x, double delta_y); + void onSurfaceSizeChanged(SurfaceSizeChangedCallback callback) + { + surfaceSizeChangedCallback_ = std::move(callback); + } + void onCursorChanged(CursorChangedCallback callback) + { + cursorChangedCallback_ = std::move(callback); + } + bool createSurface(const HWND parentWindow, + winrt::com_ptr compositor); + + void initChannel(const std::optional> viewId, const std::optional channelName); std::optional getUrl() const; + std::optional getTitle() const; void loadUrl(const URLRequest& urlRequest) const; + void reload() const; + void goBack() const; + void goForward() const; + void evaluateJavascript(const std::string& source, std::function completionHanlder) const; static bool isSslError(const COREWEBVIEW2_WEB_ERROR_STATUS& webErrorStatus); private: - bool callShouldOverrideUrlLoading = true; - void createWebView(const HWND parentWindow, const std::function completionHandler); + // custom_platform_view + winrt::com_ptr surface_; + SurfaceSizeChangedCallback surfaceSizeChangedCallback_; + CursorChangedCallback cursorChangedCallback_; + float scaleFactor_ = 1.0; + POINT lastCursorPos_ = { 0, 0 }; + VirtualKeyState virtualKeys_; + + bool callShouldOverrideUrlLoading_ = true; void InAppWebView::registerEventHandlers(); + void InAppWebView::registerSurfaceEventHandlers(); }; } #endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.cpp b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.cpp new file mode 100644 index 00000000..408724b3 --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.cpp @@ -0,0 +1,162 @@ +#include +#include +#include +#include +#include + +#include "../in_app_webview/in_app_webview_settings.h" +#include "../types/url_request.h" +#include "../utils/flutter.h" +#include "in_app_webview_manager.h" + +namespace flutter_inappwebview_plugin +{ + InAppWebViewManager::InAppWebViewManager(const FlutterInappwebviewWindowsPlugin* plugin) + : plugin(plugin), + ChannelDelegate(plugin->registrar->messenger(), InAppWebViewManager::METHOD_CHANNEL_NAME), + rohelper_(std::make_unique(RO_INIT_SINGLETHREADED)) + { + if (rohelper_->WinRtAvailable()) { + DispatcherQueueOptions options{ sizeof(DispatcherQueueOptions), + DQTYPE_THREAD_CURRENT, DQTAT_COM_STA }; + + if (FAILED(rohelper_->CreateDispatcherQueueController( + options, dispatcher_queue_controller_.put()))) { + std::cerr << "Creating DispatcherQueueController failed." << std::endl; + return; + } + + if (!isGraphicsCaptureSessionSupported()) { + std::cerr << "Windows::Graphics::Capture::GraphicsCaptureSession is not " + "supported." + << std::endl; + return; + } + + graphics_context_ = std::make_unique(rohelper_.get()); + compositor_ = graphics_context_->CreateCompositor(); + valid_ = graphics_context_->IsValid(); + } + + windowClass_.lpszClassName = CustomPlatformView::CLASS_NAME; + windowClass_.lpfnWndProc = &DefWindowProc; + + RegisterClass(&windowClass_); + } + + void InAppWebViewManager::HandleMethodCall(const flutter::MethodCall& method_call, + std::unique_ptr> result) + { + auto* arguments = std::get_if(method_call.arguments()); + + if (method_call.method_name().compare("createInAppWebView") == 0) { + if (isSupported()) { + createInAppWebView(arguments, std::move(result)); + } + else { + result->Error("0", "Creating an InAppWebView instance is not supported! Graphics Context is not valid!"); + } + } + else if (method_call.method_name().compare("dispose") == 0) { + auto id = get_fl_map_value(*arguments, "id"); + if (map_contains(webViews, (uint64_t)id)) { + webViews.erase(id); + } + result->Success(); + } + else { + result->NotImplemented(); + } + } + + void InAppWebViewManager::createInAppWebView(const flutter::EncodableMap* arguments, std::unique_ptr> result) + { + auto result_ = std::shared_ptr>(std::move(result)); + + auto settingsMap = get_fl_map_value(*arguments, "initialSettings"); + auto urlRequestMap = get_optional_fl_map_value(*arguments, "initialUrlRequest"); + + auto hwnd = CreateWindowEx(0, windowClass_.lpszClassName, L"", 0, CW_DEFAULT, + CW_DEFAULT, 0, 0, HWND_MESSAGE, nullptr, + windowClass_.hInstance, nullptr); + + InAppWebView::createInAppWebViewEnv(hwnd, true, + [=](wil::com_ptr webViewEnv, + wil::com_ptr webViewController, + wil::com_ptr webViewCompositionController) + { + if (webViewEnv && webViewController && webViewCompositionController) { + auto initialSettings = std::make_unique(settingsMap); + + InAppWebViewCreationParams params = { + "", + std::move(initialSettings), + }; + + auto inAppWebView = std::make_unique(plugin, params, hwnd, + std::move(webViewEnv), std::move(webViewController), std::move(webViewCompositionController) + ); + + std::optional urlRequest = urlRequestMap.has_value() ? std::make_optional(urlRequestMap.value()) : std::optional{}; + if (urlRequest.has_value()) { + inAppWebView->loadUrl(urlRequest.value()); + } + + auto customPlatformView = std::make_unique(plugin->registrar->messenger(), + plugin->registrar->texture_registrar(), + graphics_context(), + hwnd, + std::move(inAppWebView)); + + auto textureId = customPlatformView->texture_id(); + + customPlatformView->view->initChannel(textureId, std::nullopt); + + webViews.insert({ textureId, std::move(customPlatformView) }); + + result_->Success(flutter::EncodableValue(textureId)); + } + else { + result_->Error("0", "Cannot create the InAppWebView instance!"); + } + } + ); + } + + bool InAppWebViewManager::isGraphicsCaptureSessionSupported() + { + HSTRING className; + HSTRING_HEADER classNameHeader; + + if (FAILED(rohelper_->GetStringReference( + RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureSession, + &className, &classNameHeader))) { + return false; + } + + ABI::Windows::Graphics::Capture::IGraphicsCaptureSessionStatics* + capture_session_statics; + if (FAILED(rohelper_->GetActivationFactory( + className, + __uuidof( + ABI::Windows::Graphics::Capture::IGraphicsCaptureSessionStatics), + (void**)&capture_session_statics))) { + return false; + } + + boolean is_supported = false; + if (FAILED(capture_session_statics->IsSupported(&is_supported))) { + return false; + } + + return !!is_supported; + } + + InAppWebViewManager::~InAppWebViewManager() + { + debugLog("dealloc InAppWebViewManager"); + webViews.clear(); + UnregisterClass(windowClass_.lpszClassName, nullptr); + plugin = nullptr; + } +} \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.h b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.h new file mode 100644 index 00000000..8630ceeb --- /dev/null +++ b/flutter_inappwebview_windows/windows/in_app_webview/in_app_webview_manager.h @@ -0,0 +1,58 @@ +#ifndef FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_MANAGER_H_ +#define FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_MANAGER_H_ + +#include +#include +#include +#include +#include +#include + +#include "../custom_platform_view/custom_platform_view.h" +#include "../custom_platform_view/graphics_context.h" +#include "../custom_platform_view/util/rohelper.h" +#include "../flutter_inappwebview_windows_plugin.h" +#include "../types/channel_delegate.h" +#include "windows.ui.composition.h" + +namespace flutter_inappwebview_plugin +{ + class InAppWebViewManager : public ChannelDelegate + { + public: + static inline const std::string METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview"; + + const FlutterInappwebviewWindowsPlugin* plugin; + std::map> webViews; + + bool isSupported() const { return valid_; } + bool isGraphicsCaptureSessionSupported(); + GraphicsContext* graphics_context() const + { + return graphics_context_.get(); + }; + rx::RoHelper* rohelper() const { return rohelper_.get(); } + winrt::com_ptr compositor() const + { + return compositor_; + } + + InAppWebViewManager(const FlutterInappwebviewWindowsPlugin* plugin); + ~InAppWebViewManager(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + void createInAppWebView(const flutter::EncodableMap* arguments, std::unique_ptr> result); + private: + std::unique_ptr rohelper_; + winrt::com_ptr + dispatcher_queue_controller_; + std::unique_ptr graphics_context_; + winrt::com_ptr compositor_; + WNDCLASS windowClass_ = {}; + bool valid_ = false; + }; +} +#endif //FLUTTER_INAPPWEBVIEW_PLUGIN_IN_APP_WEBVIEW_MANAGER_H_ \ No newline at end of file diff --git a/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp index c6da13e6..6d267177 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp +++ b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.cpp @@ -1,3 +1,4 @@ +#include "../in_app_browser/in_app_browser.h" #include "../types/base_callback_result.h" #include "../utils/flutter.h" #include "../utils/strconv.h" @@ -34,15 +35,53 @@ namespace flutter_inappwebview_plugin return; } + auto& arguments = std::get(*method_call.arguments()); + if (method_call.method_name().compare("getUrl") == 0) { result->Success(make_fl_value(webView->getUrl())); } + else if (method_call.method_name().compare("getTitle") == 0) { + result->Success(make_fl_value(webView->getUrl())); + } else if (method_call.method_name().compare("loadUrl") == 0) { - auto& arguments = std::get(*method_call.arguments()); auto urlRequest = std::make_unique(get_fl_map_value(arguments, "urlRequest")); webView->loadUrl(*urlRequest); result->Success(make_fl_value(true)); } + else if (method_call.method_name().compare("reload") == 0) { + webView->reload(); + result->Success(make_fl_value(true)); + } + else if (method_call.method_name().compare("goBack") == 0) { + webView->goBack(); + result->Success(make_fl_value(true)); + } + else if (method_call.method_name().compare("goForward") == 0) { + webView->goForward(); + result->Success(make_fl_value(true)); + } + else if (method_call.method_name().compare("evaluateJavascript") == 0) { + auto result_ = std::shared_ptr>(std::move(result)); + + auto source = get_fl_map_value(arguments, "source"); + webView->evaluateJavascript(source, [result_ = std::move(result_)](const std::string& value) + { + result_->Success(make_fl_value(value)); + }); + } + // for inAppBrowser + else if (webView->inAppBrowser && method_call.method_name().compare("show") == 0) { + webView->inAppBrowser->show(); + result->Success(make_fl_value(true)); + } + else if (webView->inAppBrowser && method_call.method_name().compare("hide") == 0) { + webView->inAppBrowser->hide(); + result->Success(make_fl_value(true)); + } + else if (webView->inAppBrowser && method_call.method_name().compare("close") == 0) { + webView->inAppBrowser->close(); + result->Success(make_fl_value(true)); + } else { result->NotImplemented(); } @@ -108,6 +147,22 @@ namespace flutter_inappwebview_plugin channel->InvokeMethod("onReceivedHttpError", std::move(arguments)); } + void WebViewChannelDelegate::onTitleChanged(const std::optional& title) const + { + if (!channel) { + return; + } + + auto arguments = std::make_unique(flutter::EncodableMap{ + {"title", make_fl_value(title)} + }); + channel->InvokeMethod("onTitleChanged", std::move(arguments)); + + if (webView && webView->inAppBrowser) { + webView->inAppBrowser->didChangeTitle(title); + } + } + WebViewChannelDelegate::~WebViewChannelDelegate() { debugLog("dealloc WebViewChannelDelegate"); diff --git a/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.h b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.h index 7f950d66..f511a3c6 100644 --- a/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.h +++ b/flutter_inappwebview_windows/windows/in_app_webview/webview_channel_delegate.h @@ -39,8 +39,9 @@ namespace flutter_inappwebview_plugin void onLoadStart(const std::optional& url) const; void onLoadStop(const std::optional& url) const; void shouldOverrideUrlLoading(std::shared_ptr navigationAction, std::unique_ptr callback) const; - void WebViewChannelDelegate::onReceivedError(std::shared_ptr request, std::shared_ptr error) const; - void WebViewChannelDelegate::onReceivedHttpError(std::shared_ptr request, std::shared_ptr error) const; + void onReceivedError(std::shared_ptr request, std::shared_ptr error) const; + void onReceivedHttpError(std::shared_ptr request, std::shared_ptr error) const; + void onTitleChanged(const std::optional& title) const; }; } diff --git a/flutter_inappwebview_windows/windows/utils/util.h b/flutter_inappwebview_windows/windows/utils/util.h index 2513ddd2..88822691 100644 --- a/flutter_inappwebview_windows/windows/utils/util.h +++ b/flutter_inappwebview_windows/windows/utils/util.h @@ -2,6 +2,7 @@ #define FLUTTER_INAPPWEBVIEW_PLUGIN_UTIL_H_ #include +#include #include #include #include @@ -48,13 +49,19 @@ namespace flutter_inappwebview_plugin #endif } + static inline std::string getErrorMessage(const HRESULT& error) + { + _com_error err(error); + return wide_to_ansi(err.ErrorMessage()); + } + template static inline std::optional make_pointer_optional(const T* value) { return value == nullptr ? std::nullopt : std::make_optional(*value); } - static inline std::string variant_to_string(const std::variant& var) + static inline std::string variant_to_string(const std::variant& var) { return std::visit([](auto&& arg) {