├── .clang-format
├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .gitmodules
├── .idea
├── libraries
│ └── Dart_SDK.xml
├── modules.xml
├── runConfigurations
│ └── example_lib_main_dart.xml
└── workspace.xml
├── .metadata
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── example
├── .gitignore
├── .metadata
├── README.md
├── lib
│ └── main.dart
├── pubspec.yaml
├── test
│ └── widget_test.dart
└── windows
│ ├── .gitignore
│ ├── CMakeLists.txt
│ ├── flutter
│ └── CMakeLists.txt
│ └── runner
│ ├── CMakeLists.txt
│ ├── Runner.rc
│ ├── flutter_window.cpp
│ ├── flutter_window.h
│ ├── main.cpp
│ ├── resource.h
│ ├── resources
│ └── app_icon.ico
│ ├── runner.exe.manifest
│ ├── utils.cpp
│ ├── utils.h
│ ├── win32_window.cpp
│ └── win32_window.h
├── lib
├── src
│ ├── cursor.dart
│ ├── enums.dart
│ └── webview.dart
└── webview_windows.dart
├── pubspec.yaml
├── test
└── webview_windows_test.dart
├── webview_windows.iml
└── windows
├── .gitignore
├── CMakeLists.txt
├── graphics_context.cc
├── graphics_context.h
├── include
└── webview_windows
│ └── webview_windows_plugin.h
├── texture_bridge.cc
├── texture_bridge.h
├── texture_bridge_gpu.cc
├── texture_bridge_gpu.h
├── util
├── composition.desktop.interop.h
├── d3dutil.h
├── direct3d11.interop.cc
├── direct3d11.interop.h
├── rohelper.cc
├── rohelper.h
├── string_converter.cc
└── string_converter.h
├── webview.cc
├── webview.h
├── webview_bridge.cc
├── webview_bridge.h
├── webview_host.cc
├── webview_host.h
├── webview_platform.cc
├── webview_platform.h
└── webview_windows_plugin.cc
/.clang-format:
--------------------------------------------------------------------------------
1 | BasedOnStyle: Google
2 | DerivePointerAlignment: false
3 | PointerAlignment: Left
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: ['https://www.buymeacoffee.com/jnschulze']
2 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push, pull_request]
3 |
4 | defaults:
5 | run:
6 | working-directory: example
7 |
8 | jobs:
9 | build_windows:
10 | name: Build on Windows
11 | runs-on: windows-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | with:
15 | submodules: true
16 | - uses: subosito/flutter-action@v1
17 | with:
18 | channel: "beta"
19 | - run: flutter config --enable-windows-desktop
20 | - run: flutter pub get
21 | - run: flutter build windows
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dart_tool/
3 |
4 | .packages
5 | .pub/
6 |
7 | build/
8 |
9 | pubspec.lock
10 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jnschulze/flutter-webview-windows/5e156e4d616ab901ef5b386f01b4a57f03449634/.gitmodules
--------------------------------------------------------------------------------
/.idea/libraries/Dart_SDK.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/example_lib_main_dart.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 3955ef0d0204a9db4c35019e601c88cf7cc63582
8 | channel: master
9 |
10 | project_type: plugin
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.4.0
2 |
3 | * Enable MSVC coroutine support ([#278](https://github.com/jnschulze/flutter-webview-windows/pull/278))
4 | * Enable scrolling with trackpad ([#274](https://github.com/jnschulze/flutter-webview-windows/pull/274))
5 |
6 | ## 0.3.0
7 |
8 | * Add full-screen support ([#189](https://github.com/jnschulze/flutter-webview-windows/pull/189))
9 | * Make `loadingState` a broadcast stream ([#193](https://github.com/jnschulze/flutter-webview-windows/pull/193))
10 | * Add `getWebViewVersion()` method ([#197](https://github.com/jnschulze/flutter-webview-windows/pull/197))
11 | * Fix string casting of paths that may contain Unicode characters ([#199](https://github.com/jnschulze/flutter-webview-windows/pull/199))
12 | * Add high-DPI screen support ([#203](https://github.com/jnschulze/flutter-webview-windows/pull/203))
13 | * Add `setZoomFactor` ([#214](https://github.com/jnschulze/flutter-webview-windows/pull/214))
14 | * Fix example ([#215](https://github.com/jnschulze/flutter-webview-windows/pull/215))
15 | * Fix Visual Studio 17.6 builds ([#252](https://github.com/jnschulze/flutter-webview-windows/pull/252))
16 |
17 | ## 0.2.2
18 |
19 | * Remove `libfmt` dependency in favor of C++20 `std::format`
20 | * Enable D3D texture bridge by default
21 | * Make `executeScript` return the script's result
22 |
23 | ## 0.2.1
24 |
25 | * Add `WebviewController.addScriptToExecuteOnDocumentCreated` and `WebviewController.removeScriptToExecuteOnDocumentCreated`
26 | * Add `WebviewController.onLoadError` stream
27 | * Change `WebviewController.webMessage` stream type from `Map` to `dynamic`
28 | * Add virtual hostname mapping support
29 | * Add multi-touch support
30 |
31 | ## 0.2.0
32 |
33 | * Fix Flutter 3.0 null safety warning in example
34 | * Bump WebView2 SDK version to `1.0.1210.3`
35 | * Add an option for limiting the FPS
36 | * Change data directory base path from `RoamingAppData` to `LocalAppData`
37 |
38 | ## 0.1.9
39 |
40 | * Fix Flutter 3.0 compatibility
41 |
42 | ## 0.1.8
43 |
44 | * Prefix CMake build target names to prevent collisions with other plugins
45 |
46 | ## 0.1.7
47 |
48 | * Add method for opening DevTools
49 | * Update `TextureBridgeGpu`
50 | * Update `libfmt` dependency
51 |
52 | ## 0.1.7-dev.2
53 |
54 | * Ensure Flutter apps referencing `webview_windows` still work on Windows 8.
55 |
56 | ## 0.1.7-dev.1
57 |
58 | * Remove windowsapp.lib dependency
59 |
60 | ## 0.1.6
61 |
62 | * Improve WebView creation error handling
63 |
64 | ## 0.1.5
65 |
66 | * Fix a potential crash during WebView creation
67 |
68 | ## 0.1.4
69 |
70 | * Improve error handling for Webview environment creation
71 |
72 | ## 0.1.3
73 |
74 | * Stability fixes
75 |
76 | ## 0.1.2
77 |
78 | * Unregister method channel handlers upon WebView destruction
79 |
80 | ## 0.1.1
81 |
82 | * Fix unicode string conversion in ExecuteScript and LoadStringContent
83 | * Load CoreMessaging.dll on demand
84 |
85 | ## 0.1.0
86 |
87 | * Fix a string conversion issue
88 | * Add an option for controlling popup window behavior
89 | * Update Microsoft.Web.WebView2 and Microsoft.Windows.ImplementationLibrary
90 |
91 | ## 0.0.9
92 |
93 | * Fix resizing issues
94 | * Add preliminary GpuSurfaceTexture support
95 |
96 | ## 0.0.8
97 |
98 | * Don't rely on AVX2 support
99 | * Add history controls
100 | * Add suspend/resume support
101 | * Add support for disabling cache, clearing cookies etc.
102 |
103 | ## 0.0.7
104 |
105 | * Add support for handling permission requests
106 | * Allow setting the background color
107 | * Automatically download nuget
108 |
109 | ## 0.0.6
110 |
111 | * Fix mousewheel event handling
112 | * Make text selection work
113 | * Add method for setting the user agent
114 | * Add support for JavaScript injection
115 | * Add support for JSON message passing between Dart and JS
116 | * Fix WebView disposal
117 |
118 | ## 0.0.5
119 |
120 | * Fix input field focus issue
121 |
122 | ## 0.0.4
123 |
124 | * Minor cleanup
125 |
126 | ## 0.0.3
127 |
128 | * Add support for additional cursor types
129 |
130 | ## 0.0.2
131 |
132 | * Add support for loading string content
133 |
134 | ## 0.0.1
135 |
136 | * Initial release
137 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2021 Niklas Schulze
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # webview_windows
2 |
3 | [](https://github.com/jnschulze/flutter-webview-windows/actions/workflows/ci.yml)
4 | [](https://pub.dartlang.org/packages/webview_windows)
5 |
6 | A [Flutter](https://flutter.dev/) WebView plugin for Windows built on [Microsoft Edge WebView2](https://docs.microsoft.com/en-us/microsoft-edge/webview2/).
7 |
8 |
9 | ### Target platform requirements
10 | - [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)
11 | Before initializing the webview, call `getWebViewVersion()` to check whether the required **WebView2 Runtime** is installed or not on the current system. If `getWebViewVersion()` returns null, guide your user to install **WebView2 Runtime** from this [page](https://developer.microsoft.com/en-us/microsoft-edge/webview2/).
12 | - Windows 10 1809+
13 |
14 | ### Development platform requirements
15 | - Visual Studio 2019 or higher
16 | - Windows 11 SDK (10.0.22000.194 or higher)
17 | - (recommended) nuget.exe in your $PATH *(The makefile attempts to download nuget if it's not installed, however, this fallback might not work in China)*
18 |
19 | ## Demo
20 | 
21 |
22 | https://user-images.githubusercontent.com/720469/116716747-66f08180-a9d8-11eb-86ca-63ad5c24f07b.mp4
23 |
24 |
25 |
26 | ## Limitations
27 | This plugin provides seamless composition of web-based contents with other Flutter widgets by rendering off-screen.
28 |
29 | Unfortunately, [Microsoft Edge WebView2](https://docs.microsoft.com/en-us/microsoft-edge/webview2/) doesn't currently have an explicit API for offscreen rendering.
30 | In order to still be able to obtain a pixel buffer upon rendering a new frame, this plugin currently relies on the `Windows.Graphics.Capture` API provided by Windows 10.
31 | The downside is that older Windows versions aren't currently supported.
32 |
33 | Older Windows versions might still be targeted by using `BitBlt` for the time being.
34 |
35 | See:
36 | - https://github.com/MicrosoftEdge/WebView2Feedback/issues/20
37 | - https://github.com/MicrosoftEdge/WebView2Feedback/issues/526
38 | - https://github.com/MicrosoftEdge/WebView2Feedback/issues/547
39 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2 | # for details. All rights reserved. Use of this source code is governed by a
3 | # BSD-style license that can be found in the LICENSE file.
4 | #
5 | # Google internally enforced rules. See README.md for more information,
6 | # including a list of lints that are intentionally _not_ enforced.
7 |
8 | linter:
9 | rules:
10 | - always_declare_return_types
11 | - always_require_non_null_named_parameters
12 | - annotate_overrides
13 | - avoid_init_to_null
14 | - avoid_null_checks_in_equality_operators
15 | - avoid_relative_lib_imports
16 | - avoid_return_types_on_setters
17 | - avoid_shadowing_type_parameters
18 | - avoid_single_cascade_in_expression_statements
19 | - avoid_types_as_parameter_names
20 | - await_only_futures
21 | - camel_case_extensions
22 | - curly_braces_in_flow_control_structures
23 | - empty_catches
24 | - empty_constructor_bodies
25 | - library_names
26 | - library_prefixes
27 | - no_duplicate_case_values
28 | - null_closures
29 | - omit_local_variable_types
30 | - prefer_adjacent_string_concatenation
31 | - prefer_collection_literals
32 | - prefer_conditional_assignment
33 | - prefer_contains
34 | - prefer_equal_for_default_values
35 | - prefer_final_fields
36 | - prefer_for_elements_to_map_fromIterable
37 | - prefer_generic_function_type_aliases
38 | - prefer_if_null_operators
39 | - prefer_inlined_adds
40 | - prefer_is_empty
41 | - prefer_is_not_empty
42 | - prefer_iterable_whereType
43 | - prefer_single_quotes
44 | - prefer_spread_collections
45 | - recursive_getters
46 | - slash_for_doc_comments
47 | - sort_child_properties_last
48 | - type_init_formals
49 | - unawaited_futures
50 | - unnecessary_brace_in_string_interps
51 | - unnecessary_const
52 | - unnecessary_getters_setters
53 | - unnecessary_new
54 | - unnecessary_null_in_if_null_operators
55 | - unnecessary_this
56 | - unrelated_type_equality_checks
57 | - unsafe_html
58 | - use_full_hex_values_for_flutter_colors
59 | - use_function_type_syntax_for_parameters
60 | - use_rethrow_when_possible
61 | - valid_regexps
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | **/ios/Flutter/.last_build_id
26 | .dart_tool/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Web related
35 | lib/generated_plugin_registrant.dart
36 |
37 | # Symbolication related
38 | app.*.symbols
39 |
40 | # Obfuscation related
41 | app.*.map.json
42 |
43 | # Android Studio will place build artifacts here
44 | /android/app/debug
45 | /android/app/profile
46 | /android/app/release
47 |
48 | generated_plugin_registrant.*
49 | GeneratedPluginRegistrant.*
50 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 3955ef0d0204a9db4c35019e601c88cf7cc63582
8 | channel: master
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # webview_windows_example
2 |
3 | Demonstrates how to use the webview_windows plugin.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
13 |
14 | For help getting started with Flutter, view our
15 | [online documentation](https://flutter.dev/docs), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'dart:async';
4 |
5 | import 'package:webview_windows/webview_windows.dart';
6 | import 'package:window_manager/window_manager.dart';
7 |
8 | final navigatorKey = GlobalKey();
9 |
10 | void main() async {
11 | // For full-screen example
12 | WidgetsFlutterBinding.ensureInitialized();
13 | await windowManager.ensureInitialized();
14 |
15 | runApp(MyApp());
16 | }
17 |
18 | class MyApp extends StatelessWidget {
19 | @override
20 | Widget build(BuildContext context) {
21 | return MaterialApp(navigatorKey: navigatorKey, home: ExampleBrowser());
22 | }
23 | }
24 |
25 | class ExampleBrowser extends StatefulWidget {
26 | @override
27 | State createState() => _ExampleBrowser();
28 | }
29 |
30 | class _ExampleBrowser extends State {
31 | final _controller = WebviewController();
32 | final _textController = TextEditingController();
33 | final List _subscriptions = [];
34 | bool _isWebviewSuspended = false;
35 |
36 | @override
37 | void initState() {
38 | super.initState();
39 | initPlatformState();
40 | }
41 |
42 | Future initPlatformState() async {
43 | // Optionally initialize the webview environment using
44 | // a custom user data directory
45 | // and/or a custom browser executable directory
46 | // and/or custom chromium command line flags
47 | //await WebviewController.initializeEnvironment(
48 | // additionalArguments: '--show-fps-counter');
49 |
50 | try {
51 | await _controller.initialize();
52 | _subscriptions.add(_controller.url.listen((url) {
53 | _textController.text = url;
54 | }));
55 |
56 | _subscriptions
57 | .add(_controller.containsFullScreenElementChanged.listen((flag) {
58 | debugPrint('Contains fullscreen element: $flag');
59 | windowManager.setFullScreen(flag);
60 | }));
61 |
62 | await _controller.setBackgroundColor(Colors.transparent);
63 | await _controller.setPopupWindowPolicy(WebviewPopupWindowPolicy.deny);
64 | await _controller.loadUrl('https://flutter.dev');
65 |
66 | if (!mounted) return;
67 | setState(() {});
68 | } on PlatformException catch (e) {
69 | WidgetsBinding.instance.addPostFrameCallback((_) {
70 | showDialog(
71 | context: context,
72 | builder: (_) => AlertDialog(
73 | title: Text('Error'),
74 | content: Column(
75 | mainAxisSize: MainAxisSize.min,
76 | crossAxisAlignment: CrossAxisAlignment.start,
77 | children: [
78 | Text('Code: ${e.code}'),
79 | Text('Message: ${e.message}'),
80 | ],
81 | ),
82 | actions: [
83 | TextButton(
84 | child: Text('Continue'),
85 | onPressed: () {
86 | Navigator.of(context).pop();
87 | },
88 | )
89 | ],
90 | ));
91 | });
92 | }
93 | }
94 |
95 | Widget compositeView() {
96 | if (!_controller.value.isInitialized) {
97 | return const Text(
98 | 'Not Initialized',
99 | style: TextStyle(
100 | fontSize: 24.0,
101 | fontWeight: FontWeight.w900,
102 | ),
103 | );
104 | } else {
105 | return Padding(
106 | padding: EdgeInsets.all(20),
107 | child: Column(
108 | children: [
109 | Card(
110 | elevation: 0,
111 | child: Row(children: [
112 | Expanded(
113 | child: TextField(
114 | decoration: InputDecoration(
115 | hintText: 'URL',
116 | contentPadding: EdgeInsets.all(10.0),
117 | ),
118 | textAlignVertical: TextAlignVertical.center,
119 | controller: _textController,
120 | onSubmitted: (val) {
121 | _controller.loadUrl(val);
122 | },
123 | ),
124 | ),
125 | IconButton(
126 | icon: Icon(Icons.refresh),
127 | splashRadius: 20,
128 | onPressed: () {
129 | _controller.reload();
130 | },
131 | ),
132 | IconButton(
133 | icon: Icon(Icons.developer_mode),
134 | tooltip: 'Open DevTools',
135 | splashRadius: 20,
136 | onPressed: () {
137 | _controller.openDevTools();
138 | },
139 | )
140 | ]),
141 | ),
142 | Expanded(
143 | child: Card(
144 | color: Colors.transparent,
145 | elevation: 0,
146 | clipBehavior: Clip.antiAliasWithSaveLayer,
147 | child: Stack(
148 | children: [
149 | Webview(
150 | _controller,
151 | permissionRequested: _onPermissionRequested,
152 | ),
153 | StreamBuilder(
154 | stream: _controller.loadingState,
155 | builder: (context, snapshot) {
156 | if (snapshot.hasData &&
157 | snapshot.data == LoadingState.loading) {
158 | return LinearProgressIndicator();
159 | } else {
160 | return SizedBox();
161 | }
162 | }),
163 | ],
164 | ))),
165 | ],
166 | ),
167 | );
168 | }
169 | }
170 |
171 | @override
172 | Widget build(BuildContext context) {
173 | return Scaffold(
174 | floatingActionButton: FloatingActionButton(
175 | tooltip: _isWebviewSuspended ? 'Resume webview' : 'Suspend webview',
176 | onPressed: () async {
177 | if (_isWebviewSuspended) {
178 | await _controller.resume();
179 | } else {
180 | await _controller.suspend();
181 | }
182 | setState(() {
183 | _isWebviewSuspended = !_isWebviewSuspended;
184 | });
185 | },
186 | child: Icon(_isWebviewSuspended ? Icons.play_arrow : Icons.pause),
187 | ),
188 | appBar: AppBar(
189 | title: StreamBuilder(
190 | stream: _controller.title,
191 | builder: (context, snapshot) {
192 | return Text(
193 | snapshot.hasData ? snapshot.data! : 'WebView (Windows) Example');
194 | },
195 | )),
196 | body: Center(
197 | child: compositeView(),
198 | ),
199 | );
200 | }
201 |
202 | Future _onPermissionRequested(
203 | String url, WebviewPermissionKind kind, bool isUserInitiated) async {
204 | final decision = await showDialog(
205 | context: navigatorKey.currentContext!,
206 | builder: (BuildContext context) => AlertDialog(
207 | title: const Text('WebView permission requested'),
208 | content: Text('WebView has requested permission \'$kind\''),
209 | actions: [
210 | TextButton(
211 | onPressed: () =>
212 | Navigator.pop(context, WebviewPermissionDecision.deny),
213 | child: const Text('Deny'),
214 | ),
215 | TextButton(
216 | onPressed: () =>
217 | Navigator.pop(context, WebviewPermissionDecision.allow),
218 | child: const Text('Allow'),
219 | ),
220 | ],
221 | ),
222 | );
223 |
224 | return decision ?? WebviewPermissionDecision.none;
225 | }
226 |
227 | @override
228 | void dispose() {
229 | _subscriptions.forEach((s) => s.cancel());
230 | _controller.dispose();
231 | super.dispose();
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: webview_windows_example
2 | description: Demonstrates how to use the webview_windows plugin.
3 |
4 | publish_to: 'none'
5 |
6 | environment:
7 | sdk: ">=2.12.0 <3.0.0"
8 |
9 | dependencies:
10 | flutter:
11 | sdk: flutter
12 |
13 | webview_windows:
14 | path: ../
15 | window_manager: ^0.2.7
16 |
17 | dev_dependencies:
18 | flutter_test:
19 | sdk: flutter
20 |
21 | flutter:
22 | uses-material-design: true
23 |
--------------------------------------------------------------------------------
/example/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | void main() {}
2 |
--------------------------------------------------------------------------------
/example/windows/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral/
2 |
3 | # Visual Studio user-specific files.
4 | *.suo
5 | *.user
6 | *.userosscache
7 | *.sln.docstates
8 |
9 | # Visual Studio build-related files.
10 | x64/
11 | x86/
12 |
13 | # Visual Studio cache files
14 | # files ending in .cache can be ignored
15 | *.[Cc]ache
16 | # but keep track of directories ending in .cache
17 | !*.[Cc]ache/
18 |
--------------------------------------------------------------------------------
/example/windows/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.15)
2 |
3 | set(CMAKE_SYSTEM_VERSION 10.0.22000 CACHE STRING INTERNAL FORCE)
4 |
5 | project(webview_windows_example LANGUAGES CXX)
6 |
7 | set(BINARY_NAME "webview_windows_example")
8 |
9 | cmake_policy(SET CMP0063 NEW)
10 |
11 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
12 |
13 | # Configure build options.
14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
15 | if(IS_MULTICONFIG)
16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
17 | CACHE STRING "" FORCE)
18 | else()
19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
20 | set(CMAKE_BUILD_TYPE "Debug" CACHE
21 | STRING "Flutter build mode" FORCE)
22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
23 | "Debug" "Profile" "Release")
24 | endif()
25 | endif()
26 |
27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
31 |
32 | # Use Unicode for all projects.
33 | add_definitions(-DUNICODE -D_UNICODE)
34 |
35 | # Compilation settings that should be applied to most targets.
36 | function(APPLY_STANDARD_SETTINGS TARGET)
37 | target_compile_features(${TARGET} PUBLIC cxx_std_17)
38 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
39 | target_compile_options(${TARGET} PRIVATE /EHsc)
40 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
41 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>")
42 | endfunction()
43 |
44 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
45 |
46 | # Flutter library and tool build rules.
47 | add_subdirectory(${FLUTTER_MANAGED_DIR})
48 |
49 | # Application build
50 | add_subdirectory("runner")
51 |
52 | # Generated plugin build rules, which manage building the plugins and adding
53 | # them to the application.
54 | include(flutter/generated_plugins.cmake)
55 |
56 |
57 | # === Installation ===
58 | # Support files are copied into place next to the executable, so that it can
59 | # run in place. This is done instead of making a separate bundle (as on Linux)
60 | # so that building and running from within Visual Studio will work.
61 | set(BUILD_BUNDLE_DIR "$")
62 | # Make the "install" step default, as it's required to run.
63 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
64 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
65 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
66 | endif()
67 |
68 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
69 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
70 |
71 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
72 | COMPONENT Runtime)
73 |
74 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
75 | COMPONENT Runtime)
76 |
77 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
78 | COMPONENT Runtime)
79 |
80 | if(PLUGIN_BUNDLED_LIBRARIES)
81 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
82 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
83 | COMPONENT Runtime)
84 | endif()
85 |
86 | # Fully re-copy the assets directory on each build to avoid having stale files
87 | # from a previous install.
88 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
89 | install(CODE "
90 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
91 | " COMPONENT Runtime)
92 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
93 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
94 |
95 | # Install the AOT library on non-Debug builds only.
96 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
97 | CONFIGURATIONS Profile;Release
98 | COMPONENT Runtime)
99 |
--------------------------------------------------------------------------------
/example/windows/flutter/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.15)
2 |
3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
4 |
5 | # Configuration provided via flutter tool.
6 | include(${EPHEMERAL_DIR}/generated_config.cmake)
7 |
8 | # TODO: Move the rest of this into files in ephemeral. See
9 | # https://github.com/flutter/flutter/issues/57146.
10 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
11 |
12 | # === Flutter Library ===
13 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
14 |
15 | # Published to parent scope for install step.
16 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
17 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
18 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
19 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
20 |
21 | list(APPEND FLUTTER_LIBRARY_HEADERS
22 | "flutter_export.h"
23 | "flutter_windows.h"
24 | "flutter_messenger.h"
25 | "flutter_plugin_registrar.h"
26 | "flutter_texture_registrar.h"
27 | )
28 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
29 | add_library(flutter INTERFACE)
30 | target_include_directories(flutter INTERFACE
31 | "${EPHEMERAL_DIR}"
32 | )
33 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
34 | add_dependencies(flutter flutter_assemble)
35 |
36 | # === Wrapper ===
37 | list(APPEND CPP_WRAPPER_SOURCES_CORE
38 | "core_implementations.cc"
39 | "standard_codec.cc"
40 | )
41 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
42 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
43 | "plugin_registrar.cc"
44 | )
45 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
46 | list(APPEND CPP_WRAPPER_SOURCES_APP
47 | "flutter_engine.cc"
48 | "flutter_view_controller.cc"
49 | )
50 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
51 |
52 | # Wrapper sources needed for a plugin.
53 | add_library(flutter_wrapper_plugin STATIC
54 | ${CPP_WRAPPER_SOURCES_CORE}
55 | ${CPP_WRAPPER_SOURCES_PLUGIN}
56 | )
57 | apply_standard_settings(flutter_wrapper_plugin)
58 | set_target_properties(flutter_wrapper_plugin PROPERTIES
59 | POSITION_INDEPENDENT_CODE ON)
60 | set_target_properties(flutter_wrapper_plugin PROPERTIES
61 | CXX_VISIBILITY_PRESET hidden)
62 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
63 | target_include_directories(flutter_wrapper_plugin PUBLIC
64 | "${WRAPPER_ROOT}/include"
65 | )
66 | add_dependencies(flutter_wrapper_plugin flutter_assemble)
67 |
68 | # Wrapper sources needed for the runner.
69 | add_library(flutter_wrapper_app STATIC
70 | ${CPP_WRAPPER_SOURCES_CORE}
71 | ${CPP_WRAPPER_SOURCES_APP}
72 | )
73 | apply_standard_settings(flutter_wrapper_app)
74 | target_link_libraries(flutter_wrapper_app PUBLIC flutter)
75 | target_include_directories(flutter_wrapper_app PUBLIC
76 | "${WRAPPER_ROOT}/include"
77 | )
78 | add_dependencies(flutter_wrapper_app flutter_assemble)
79 |
80 | # === Flutter tool backend ===
81 | # _phony_ is a non-existent file to force this command to run every time,
82 | # since currently there's no way to get a full input/output list from the
83 | # flutter tool.
84 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
85 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
86 | add_custom_command(
87 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
88 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
89 | ${CPP_WRAPPER_SOURCES_APP}
90 | ${PHONY_OUTPUT}
91 | COMMAND ${CMAKE_COMMAND} -E env
92 | ${FLUTTER_TOOL_ENVIRONMENT}
93 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
94 | windows-x64 $
95 | VERBATIM
96 | )
97 | add_custom_target(flutter_assemble DEPENDS
98 | "${FLUTTER_LIBRARY}"
99 | ${FLUTTER_LIBRARY_HEADERS}
100 | ${CPP_WRAPPER_SOURCES_CORE}
101 | ${CPP_WRAPPER_SOURCES_PLUGIN}
102 | ${CPP_WRAPPER_SOURCES_APP}
103 | )
104 |
--------------------------------------------------------------------------------
/example/windows/runner/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.15)
2 |
3 | project(runner LANGUAGES CXX)
4 |
5 | add_executable(${BINARY_NAME} WIN32
6 | "flutter_window.cpp"
7 | "main.cpp"
8 | "utils.cpp"
9 | "win32_window.cpp"
10 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
11 | "Runner.rc"
12 | "runner.exe.manifest"
13 | )
14 | apply_standard_settings(${BINARY_NAME})
15 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
16 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
17 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
18 | add_dependencies(${BINARY_NAME} flutter_assemble)
19 |
--------------------------------------------------------------------------------
/example/windows/runner/Runner.rc:
--------------------------------------------------------------------------------
1 | // Microsoft Visual C++ generated resource script.
2 | //
3 | #pragma code_page(65001)
4 | #include "resource.h"
5 |
6 | #define APSTUDIO_READONLY_SYMBOLS
7 | /////////////////////////////////////////////////////////////////////////////
8 | //
9 | // Generated from the TEXTINCLUDE 2 resource.
10 | //
11 | #include "winres.h"
12 |
13 | /////////////////////////////////////////////////////////////////////////////
14 | #undef APSTUDIO_READONLY_SYMBOLS
15 |
16 | /////////////////////////////////////////////////////////////////////////////
17 | // English (United States) resources
18 |
19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
21 |
22 | #ifdef APSTUDIO_INVOKED
23 | /////////////////////////////////////////////////////////////////////////////
24 | //
25 | // TEXTINCLUDE
26 | //
27 |
28 | 1 TEXTINCLUDE
29 | BEGIN
30 | "resource.h\0"
31 | END
32 |
33 | 2 TEXTINCLUDE
34 | BEGIN
35 | "#include ""winres.h""\r\n"
36 | "\0"
37 | END
38 |
39 | 3 TEXTINCLUDE
40 | BEGIN
41 | "\r\n"
42 | "\0"
43 | END
44 |
45 | #endif // APSTUDIO_INVOKED
46 |
47 |
48 | /////////////////////////////////////////////////////////////////////////////
49 | //
50 | // Icon
51 | //
52 |
53 | // Icon with lowest ID value placed first to ensure application icon
54 | // remains consistent on all systems.
55 | IDI_APP_ICON ICON "resources\\app_icon.ico"
56 |
57 |
58 | /////////////////////////////////////////////////////////////////////////////
59 | //
60 | // Version
61 | //
62 |
63 | #ifdef FLUTTER_BUILD_NUMBER
64 | #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
65 | #else
66 | #define VERSION_AS_NUMBER 1,0,0
67 | #endif
68 |
69 | #ifdef FLUTTER_BUILD_NAME
70 | #define VERSION_AS_STRING #FLUTTER_BUILD_NAME
71 | #else
72 | #define VERSION_AS_STRING "1.0.0"
73 | #endif
74 |
75 | VS_VERSION_INFO VERSIONINFO
76 | FILEVERSION VERSION_AS_NUMBER
77 | PRODUCTVERSION VERSION_AS_NUMBER
78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
79 | #ifdef _DEBUG
80 | FILEFLAGS VS_FF_DEBUG
81 | #else
82 | FILEFLAGS 0x0L
83 | #endif
84 | FILEOS VOS__WINDOWS32
85 | FILETYPE VFT_APP
86 | FILESUBTYPE 0x0L
87 | BEGIN
88 | BLOCK "StringFileInfo"
89 | BEGIN
90 | BLOCK "040904e4"
91 | BEGIN
92 | VALUE "CompanyName", "com.example" "\0"
93 | VALUE "FileDescription", "Demonstrates how to use the webview_windows plugin." "\0"
94 | VALUE "FileVersion", VERSION_AS_STRING "\0"
95 | VALUE "InternalName", "webview_windows_example" "\0"
96 | VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0"
97 | VALUE "OriginalFilename", "webview_windows_example.exe" "\0"
98 | VALUE "ProductName", "webview_windows_example" "\0"
99 | VALUE "ProductVersion", VERSION_AS_STRING "\0"
100 | END
101 | END
102 | BLOCK "VarFileInfo"
103 | BEGIN
104 | VALUE "Translation", 0x409, 1252
105 | END
106 | END
107 |
108 | #endif // English (United States) resources
109 | /////////////////////////////////////////////////////////////////////////////
110 |
111 |
112 |
113 | #ifndef APSTUDIO_INVOKED
114 | /////////////////////////////////////////////////////////////////////////////
115 | //
116 | // Generated from the TEXTINCLUDE 3 resource.
117 | //
118 |
119 |
120 | /////////////////////////////////////////////////////////////////////////////
121 | #endif // not APSTUDIO_INVOKED
122 |
--------------------------------------------------------------------------------
/example/windows/runner/flutter_window.cpp:
--------------------------------------------------------------------------------
1 | #include "flutter_window.h"
2 |
3 | #include
4 |
5 | #include "flutter/generated_plugin_registrant.h"
6 |
7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project)
8 | : project_(project) {}
9 |
10 | FlutterWindow::~FlutterWindow() {}
11 |
12 | bool FlutterWindow::OnCreate() {
13 | if (!Win32Window::OnCreate()) {
14 | return false;
15 | }
16 |
17 | RECT frame = GetClientArea();
18 |
19 | // The size here must match the window dimensions to avoid unnecessary surface
20 | // creation / destruction in the startup path.
21 | flutter_controller_ = std::make_unique(
22 | frame.right - frame.left, frame.bottom - frame.top, project_);
23 | // Ensure that basic setup of the controller was successful.
24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) {
25 | return false;
26 | }
27 | RegisterPlugins(flutter_controller_->engine());
28 | SetChildContent(flutter_controller_->view()->GetNativeWindow());
29 | return true;
30 | }
31 |
32 | void FlutterWindow::OnDestroy() {
33 | if (flutter_controller_) {
34 | flutter_controller_ = nullptr;
35 | }
36 |
37 | Win32Window::OnDestroy();
38 | }
39 |
40 | LRESULT
41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
42 | WPARAM const wparam,
43 | LPARAM const lparam) noexcept {
44 | // Give Flutter, including plugins, an opportunity to handle window messages.
45 | if (flutter_controller_) {
46 | std::optional result =
47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
48 | lparam);
49 | if (result) {
50 | return *result;
51 | }
52 | }
53 |
54 | switch (message) {
55 | case WM_FONTCHANGE:
56 | flutter_controller_->engine()->ReloadSystemFonts();
57 | break;
58 | }
59 |
60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
61 | }
62 |
--------------------------------------------------------------------------------
/example/windows/runner/flutter_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_FLUTTER_WINDOW_H_
2 | #define RUNNER_FLUTTER_WINDOW_H_
3 |
4 | #include
5 | #include
6 |
7 | #include
8 |
9 | #include "win32_window.h"
10 |
11 | // A window that does nothing but host a Flutter view.
12 | class FlutterWindow : public Win32Window {
13 | public:
14 | // Creates a new FlutterWindow hosting a Flutter view running |project|.
15 | explicit FlutterWindow(const flutter::DartProject& project);
16 | virtual ~FlutterWindow();
17 |
18 | protected:
19 | // Win32Window:
20 | bool OnCreate() override;
21 | void OnDestroy() override;
22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
23 | LPARAM const lparam) noexcept override;
24 |
25 | private:
26 | // The project to run.
27 | flutter::DartProject project_;
28 |
29 | // The Flutter instance hosted by this window.
30 | std::unique_ptr flutter_controller_;
31 | };
32 |
33 | #endif // RUNNER_FLUTTER_WINDOW_H_
34 |
--------------------------------------------------------------------------------
/example/windows/runner/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "flutter_window.h"
6 | #include "utils.h"
7 |
8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
9 | _In_ wchar_t *command_line, _In_ int show_command) {
10 | // Attach to console when present (e.g., 'flutter run') or create a
11 | // new console when running with a debugger.
12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
13 | CreateAndAttachConsole();
14 | }
15 |
16 | // Initialize COM, so that it is available for use in the library and/or
17 | // plugins.
18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
19 |
20 | flutter::DartProject project(L"data");
21 |
22 | std::vector command_line_arguments =
23 | GetCommandLineArguments();
24 |
25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
26 |
27 | FlutterWindow window(project);
28 | Win32Window::Point origin(10, 10);
29 | Win32Window::Size size(1280, 720);
30 | if (!window.CreateAndShow(L"webview_windows_example", origin, size)) {
31 | return EXIT_FAILURE;
32 | }
33 | window.SetQuitOnClose(true);
34 |
35 | ::MSG msg;
36 | while (::GetMessage(&msg, nullptr, 0, 0)) {
37 | ::TranslateMessage(&msg);
38 | ::DispatchMessage(&msg);
39 | }
40 |
41 | ::CoUninitialize();
42 | return EXIT_SUCCESS;
43 | }
44 |
--------------------------------------------------------------------------------
/example/windows/runner/resource.h:
--------------------------------------------------------------------------------
1 | //{{NO_DEPENDENCIES}}
2 | // Microsoft Visual C++ generated include file.
3 | // Used by Runner.rc
4 | //
5 | #define IDI_APP_ICON 101
6 |
7 | // Next default values for new objects
8 | //
9 | #ifdef APSTUDIO_INVOKED
10 | #ifndef APSTUDIO_READONLY_SYMBOLS
11 | #define _APS_NEXT_RESOURCE_VALUE 102
12 | #define _APS_NEXT_COMMAND_VALUE 40001
13 | #define _APS_NEXT_CONTROL_VALUE 1001
14 | #define _APS_NEXT_SYMED_VALUE 101
15 | #endif
16 | #endif
17 |
--------------------------------------------------------------------------------
/example/windows/runner/resources/app_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jnschulze/flutter-webview-windows/5e156e4d616ab901ef5b386f01b4a57f03449634/example/windows/runner/resources/app_icon.ico
--------------------------------------------------------------------------------
/example/windows/runner/runner.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PerMonitorV2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/example/windows/runner/utils.cpp:
--------------------------------------------------------------------------------
1 | #include "utils.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 |
10 | void CreateAndAttachConsole() {
11 | if (::AllocConsole()) {
12 | FILE *unused;
13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
14 | _dup2(_fileno(stdout), 1);
15 | }
16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
17 | _dup2(_fileno(stdout), 2);
18 | }
19 | std::ios::sync_with_stdio();
20 | FlutterDesktopResyncOutputStreams();
21 | }
22 | }
23 |
24 | std::vector GetCommandLineArguments() {
25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
26 | int argc;
27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
28 | if (argv == nullptr) {
29 | return std::vector();
30 | }
31 |
32 | std::vector command_line_arguments;
33 |
34 | // Skip the first argument as it's the binary name.
35 | for (int i = 1; i < argc; i++) {
36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
37 | }
38 |
39 | ::LocalFree(argv);
40 |
41 | return command_line_arguments;
42 | }
43 |
44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) {
45 | if (utf16_string == nullptr) {
46 | return std::string();
47 | }
48 | int target_length = ::WideCharToMultiByte(
49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
50 | -1, nullptr, 0, nullptr, nullptr);
51 | if (target_length == 0) {
52 | return std::string();
53 | }
54 | std::string utf8_string;
55 | utf8_string.resize(target_length);
56 | int converted_length = ::WideCharToMultiByte(
57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
58 | -1, utf8_string.data(),
59 | target_length, nullptr, nullptr);
60 | if (converted_length == 0) {
61 | return std::string();
62 | }
63 | return utf8_string;
64 | }
65 |
--------------------------------------------------------------------------------
/example/windows/runner/utils.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_UTILS_H_
2 | #define RUNNER_UTILS_H_
3 |
4 | #include
5 | #include
6 |
7 | // Creates a console for the process, and redirects stdout and stderr to
8 | // it for both the runner and the Flutter library.
9 | void CreateAndAttachConsole();
10 |
11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
12 | // encoded in UTF-8. Returns an empty std::string on failure.
13 | std::string Utf8FromUtf16(const wchar_t* utf16_string);
14 |
15 | // Gets the command line arguments passed in as a std::vector,
16 | // encoded in UTF-8. Returns an empty std::vector on failure.
17 | std::vector GetCommandLineArguments();
18 |
19 | #endif // RUNNER_UTILS_H_
20 |
--------------------------------------------------------------------------------
/example/windows/runner/win32_window.cpp:
--------------------------------------------------------------------------------
1 | #include "win32_window.h"
2 |
3 | #include
4 |
5 | #include "resource.h"
6 |
7 | namespace {
8 |
9 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
10 |
11 | // The number of Win32Window objects that currently exist.
12 | static int g_active_window_count = 0;
13 |
14 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
15 |
16 | // Scale helper to convert logical scaler values to physical using passed in
17 | // scale factor
18 | int Scale(int source, double scale_factor) {
19 | return static_cast(source * scale_factor);
20 | }
21 |
22 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
23 | // This API is only needed for PerMonitor V1 awareness mode.
24 | void EnableFullDpiSupportIfAvailable(HWND hwnd) {
25 | HMODULE user32_module = LoadLibraryA("User32.dll");
26 | if (!user32_module) {
27 | return;
28 | }
29 | auto enable_non_client_dpi_scaling =
30 | reinterpret_cast(
31 | GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
32 | if (enable_non_client_dpi_scaling != nullptr) {
33 | enable_non_client_dpi_scaling(hwnd);
34 | FreeLibrary(user32_module);
35 | }
36 | }
37 |
38 | } // namespace
39 |
40 | // Manages the Win32Window's window class registration.
41 | class WindowClassRegistrar {
42 | public:
43 | ~WindowClassRegistrar() = default;
44 |
45 | // Returns the singleton registar instance.
46 | static WindowClassRegistrar* GetInstance() {
47 | if (!instance_) {
48 | instance_ = new WindowClassRegistrar();
49 | }
50 | return instance_;
51 | }
52 |
53 | // Returns the name of the window class, registering the class if it hasn't
54 | // previously been registered.
55 | const wchar_t* GetWindowClass();
56 |
57 | // Unregisters the window class. Should only be called if there are no
58 | // instances of the window.
59 | void UnregisterWindowClass();
60 |
61 | private:
62 | WindowClassRegistrar() = default;
63 |
64 | static WindowClassRegistrar* instance_;
65 |
66 | bool class_registered_ = false;
67 | };
68 |
69 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
70 |
71 | const wchar_t* WindowClassRegistrar::GetWindowClass() {
72 | if (!class_registered_) {
73 | WNDCLASS window_class{};
74 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
75 | window_class.lpszClassName = kWindowClassName;
76 | window_class.style = CS_HREDRAW | CS_VREDRAW;
77 | window_class.cbClsExtra = 0;
78 | window_class.cbWndExtra = 0;
79 | window_class.hInstance = GetModuleHandle(nullptr);
80 | window_class.hIcon =
81 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
82 | window_class.hbrBackground = 0;
83 | window_class.lpszMenuName = nullptr;
84 | window_class.lpfnWndProc = Win32Window::WndProc;
85 | RegisterClass(&window_class);
86 | class_registered_ = true;
87 | }
88 | return kWindowClassName;
89 | }
90 |
91 | void WindowClassRegistrar::UnregisterWindowClass() {
92 | UnregisterClass(kWindowClassName, nullptr);
93 | class_registered_ = false;
94 | }
95 |
96 | Win32Window::Win32Window() {
97 | ++g_active_window_count;
98 | }
99 |
100 | Win32Window::~Win32Window() {
101 | --g_active_window_count;
102 | Destroy();
103 | }
104 |
105 | bool Win32Window::CreateAndShow(const std::wstring& title,
106 | const Point& origin,
107 | const Size& size) {
108 | Destroy();
109 |
110 | const wchar_t* window_class =
111 | WindowClassRegistrar::GetInstance()->GetWindowClass();
112 |
113 | const POINT target_point = {static_cast(origin.x),
114 | static_cast(origin.y)};
115 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
116 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
117 | double scale_factor = dpi / 96.0;
118 |
119 | HWND window = CreateWindow(
120 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
121 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
122 | Scale(size.width, scale_factor), Scale(size.height, scale_factor),
123 | nullptr, nullptr, GetModuleHandle(nullptr), this);
124 |
125 | if (!window) {
126 | return false;
127 | }
128 |
129 | return OnCreate();
130 | }
131 |
132 | // static
133 | LRESULT CALLBACK Win32Window::WndProc(HWND const window,
134 | UINT const message,
135 | WPARAM const wparam,
136 | LPARAM const lparam) noexcept {
137 | if (message == WM_NCCREATE) {
138 | auto window_struct = reinterpret_cast(lparam);
139 | SetWindowLongPtr(window, GWLP_USERDATA,
140 | reinterpret_cast(window_struct->lpCreateParams));
141 |
142 | auto that = static_cast(window_struct->lpCreateParams);
143 | EnableFullDpiSupportIfAvailable(window);
144 | that->window_handle_ = window;
145 | } else if (Win32Window* that = GetThisFromHandle(window)) {
146 | return that->MessageHandler(window, message, wparam, lparam);
147 | }
148 |
149 | return DefWindowProc(window, message, wparam, lparam);
150 | }
151 |
152 | LRESULT
153 | Win32Window::MessageHandler(HWND hwnd,
154 | UINT const message,
155 | WPARAM const wparam,
156 | LPARAM const lparam) noexcept {
157 | switch (message) {
158 | case WM_DESTROY:
159 | window_handle_ = nullptr;
160 | Destroy();
161 | if (quit_on_close_) {
162 | PostQuitMessage(0);
163 | }
164 | return 0;
165 |
166 | case WM_DPICHANGED: {
167 | auto newRectSize = reinterpret_cast(lparam);
168 | LONG newWidth = newRectSize->right - newRectSize->left;
169 | LONG newHeight = newRectSize->bottom - newRectSize->top;
170 |
171 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
172 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
173 |
174 | return 0;
175 | }
176 | case WM_SIZE: {
177 | RECT rect = GetClientArea();
178 | if (child_content_ != nullptr) {
179 | // Size and position the child window.
180 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
181 | rect.bottom - rect.top, TRUE);
182 | }
183 | return 0;
184 | }
185 |
186 | case WM_ACTIVATE:
187 | if (child_content_ != nullptr) {
188 | SetFocus(child_content_);
189 | }
190 | return 0;
191 | }
192 |
193 | return DefWindowProc(window_handle_, message, wparam, lparam);
194 | }
195 |
196 | void Win32Window::Destroy() {
197 | OnDestroy();
198 |
199 | if (window_handle_) {
200 | DestroyWindow(window_handle_);
201 | window_handle_ = nullptr;
202 | }
203 | if (g_active_window_count == 0) {
204 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
205 | }
206 | }
207 |
208 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
209 | return reinterpret_cast(
210 | GetWindowLongPtr(window, GWLP_USERDATA));
211 | }
212 |
213 | void Win32Window::SetChildContent(HWND content) {
214 | child_content_ = content;
215 | SetParent(content, window_handle_);
216 | RECT frame = GetClientArea();
217 |
218 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
219 | frame.bottom - frame.top, true);
220 |
221 | SetFocus(child_content_);
222 | }
223 |
224 | RECT Win32Window::GetClientArea() {
225 | RECT frame;
226 | GetClientRect(window_handle_, &frame);
227 | return frame;
228 | }
229 |
230 | HWND Win32Window::GetHandle() {
231 | return window_handle_;
232 | }
233 |
234 | void Win32Window::SetQuitOnClose(bool quit_on_close) {
235 | quit_on_close_ = quit_on_close;
236 | }
237 |
238 | bool Win32Window::OnCreate() {
239 | // No-op; provided for subclasses.
240 | return true;
241 | }
242 |
243 | void Win32Window::OnDestroy() {
244 | // No-op; provided for subclasses.
245 | }
246 |
--------------------------------------------------------------------------------
/example/windows/runner/win32_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_WIN32_WINDOW_H_
2 | #define RUNNER_WIN32_WINDOW_H_
3 |
4 | #include
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be
11 | // inherited from by classes that wish to specialize with custom
12 | // rendering and input handling
13 | class Win32Window {
14 | public:
15 | struct Point {
16 | unsigned int x;
17 | unsigned int y;
18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {}
19 | };
20 |
21 | struct Size {
22 | unsigned int width;
23 | unsigned int height;
24 | Size(unsigned int width, unsigned int height)
25 | : width(width), height(height) {}
26 | };
27 |
28 | Win32Window();
29 | virtual ~Win32Window();
30 |
31 | // Creates and shows a win32 window with |title| and position and size using
32 | // |origin| and |size|. New windows are created on the default monitor. Window
33 | // sizes are specified to the OS in physical pixels, hence to ensure a
34 | // consistent size to will treat the width height passed in to this function
35 | // as logical pixels and scale to appropriate for the default monitor. Returns
36 | // true if the window was created successfully.
37 | bool CreateAndShow(const std::wstring& title,
38 | const Point& origin,
39 | const Size& size);
40 |
41 | // Release OS resources associated with window.
42 | void Destroy();
43 |
44 | // Inserts |content| into the window tree.
45 | void SetChildContent(HWND content);
46 |
47 | // Returns the backing Window handle to enable clients to set icon and other
48 | // window properties. Returns nullptr if the window has been destroyed.
49 | HWND GetHandle();
50 |
51 | // If true, closing this window will quit the application.
52 | void SetQuitOnClose(bool quit_on_close);
53 |
54 | // Return a RECT representing the bounds of the current client area.
55 | RECT GetClientArea();
56 |
57 | protected:
58 | // Processes and route salient window messages for mouse handling,
59 | // size change and DPI. Delegates handling of these to member overloads that
60 | // inheriting classes can handle.
61 | virtual LRESULT MessageHandler(HWND window,
62 | UINT const message,
63 | WPARAM const wparam,
64 | LPARAM const lparam) noexcept;
65 |
66 | // Called when CreateAndShow is called, allowing subclass window-related
67 | // setup. Subclasses should return false if setup fails.
68 | virtual bool OnCreate();
69 |
70 | // Called when Destroy is called.
71 | virtual void OnDestroy();
72 |
73 | private:
74 | friend class WindowClassRegistrar;
75 |
76 | // OS callback called by message pump. Handles the WM_NCCREATE message which
77 | // is passed when the non-client area is being created and enables automatic
78 | // non-client DPI scaling so that the non-client area automatically
79 | // responsponds to changes in DPI. All other messages are handled by
80 | // MessageHandler.
81 | static LRESULT CALLBACK WndProc(HWND const window,
82 | UINT const message,
83 | WPARAM const wparam,
84 | LPARAM const lparam) noexcept;
85 |
86 | // Retrieves a class instance pointer for |window|
87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept;
88 |
89 | bool quit_on_close_ = false;
90 |
91 | // window handle for top level window.
92 | HWND window_handle_ = nullptr;
93 |
94 | // window handle for hosted content.
95 | HWND child_content_ = nullptr;
96 | };
97 |
98 | #endif // RUNNER_WIN32_WINDOW_H_
99 |
--------------------------------------------------------------------------------
/lib/src/cursor.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/services.dart';
2 |
3 | const Map _cursors = {
4 | 'none': SystemMouseCursors.none,
5 | 'basic': SystemMouseCursors.basic,
6 | 'click': SystemMouseCursors.click,
7 | 'forbidden': SystemMouseCursors.forbidden,
8 | 'wait': SystemMouseCursors.wait,
9 | 'progress': SystemMouseCursors.progress,
10 | 'contextMenu': SystemMouseCursors.contextMenu,
11 | 'help': SystemMouseCursors.help,
12 | 'text': SystemMouseCursors.text,
13 | 'verticalText': SystemMouseCursors.verticalText,
14 | 'cell': SystemMouseCursors.cell,
15 | 'precise': SystemMouseCursors.precise,
16 | 'move': SystemMouseCursors.move,
17 | 'grab': SystemMouseCursors.grab,
18 | 'grabbing': SystemMouseCursors.grabbing,
19 | 'noDrop': SystemMouseCursors.noDrop,
20 | 'alias': SystemMouseCursors.alias,
21 | 'copy': SystemMouseCursors.copy,
22 | 'disappearing': SystemMouseCursors.disappearing,
23 | 'allScroll': SystemMouseCursors.allScroll,
24 | 'resizeLeftRight': SystemMouseCursors.resizeLeftRight,
25 | 'resizeUpDown': SystemMouseCursors.resizeUpDown,
26 | 'resizeUpLeftDownRight': SystemMouseCursors.resizeUpLeftDownRight,
27 | 'resizeUpRightDownLeft': SystemMouseCursors.resizeUpRightDownLeft,
28 | 'resizeUp': SystemMouseCursors.resizeUp,
29 | 'resizeDown': SystemMouseCursors.resizeDown,
30 | 'resizeLeft': SystemMouseCursors.resizeLeft,
31 | 'resizeRight': SystemMouseCursors.resizeRight,
32 | 'resizeUpLeft': SystemMouseCursors.resizeUpLeft,
33 | 'resizeUpRight': SystemMouseCursors.resizeUpRight,
34 | 'resizeDownLeft': SystemMouseCursors.resizeDownLeft,
35 | 'resizeDownRight': SystemMouseCursors.resizeDownRight,
36 | 'resizeColumn': SystemMouseCursors.resizeColumn,
37 | 'resizeRow': SystemMouseCursors.resizeRow,
38 | 'zoomIn': SystemMouseCursors.zoomIn,
39 | 'zoomOut': SystemMouseCursors.zoomOut,
40 | };
41 |
42 | SystemMouseCursor getCursorByName(String name) =>
43 | _cursors[name] ?? SystemMouseCursors.basic;
44 |
--------------------------------------------------------------------------------
/lib/src/enums.dart:
--------------------------------------------------------------------------------
1 | /// Loading state
2 | // Order must match WebviewLoadingState (see webview.h)
3 | enum LoadingState { none, loading, navigationCompleted }
4 |
5 | /// Pointer button type
6 | // Order must match WebviewPointerButton (see webview.h)
7 | enum PointerButton { none, primary, secondary, tertiary }
8 |
9 | enum WebviewDownloadEventKind {
10 | downloadStarted,
11 | downloadCompleted,
12 | downloadProgress
13 | }
14 |
15 | /// Pointer Event kind
16 | // Order must match WebviewPointerEventKind (see webview.h)
17 | enum WebviewPointerEventKind { activate, down, enter, leave, up, update }
18 |
19 | /// Permission kind
20 | // Order must match WebviewPermissionKind (see webview.h)
21 | enum WebviewPermissionKind {
22 | unknown,
23 | microphone,
24 | camera,
25 | geoLocation,
26 | notifications,
27 | otherSensors,
28 | clipboardRead
29 | }
30 |
31 | enum WebviewPermissionDecision { none, allow, deny }
32 |
33 | /// The policy for popup requests.
34 | ///
35 | /// [allow] allows popups and will create new windows.
36 | /// [deny] suppresses popups.
37 | /// [sameWindow] displays popup contents in the current WebView.
38 | enum WebviewPopupWindowPolicy { allow, deny, sameWindow }
39 |
40 | /// The kind of cross origin resource access for virtual hosts
41 | ///
42 | /// [deny] all cross origin requests are denied.
43 | /// [allow] all cross origin requests are allowed.
44 | /// [denyCors] sub resource cross origin requests are allowed, otherwise denied.
45 | ///
46 | /// For more detailed information, please refer to
47 | /// [Microsofts](https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2#corewebview2_host_resource_access_kind)
48 | /// documentation.
49 | // Order must match WebviewHostResourceAccessKind (see webview.h)
50 | enum WebviewHostResourceAccessKind { deny, allow, denyCors }
51 |
52 | enum WebErrorStatus {
53 | WebErrorStatusUnknown,
54 | WebErrorStatusCertificateCommonNameIsIncorrect,
55 | WebErrorStatusCertificateExpired,
56 | WebErrorStatusClientCertificateContainsErrors,
57 | WebErrorStatusCertificateRevoked,
58 | WebErrorStatusCertificateIsInvalid,
59 | WebErrorStatusServerUnreachable,
60 | WebErrorStatusTimeout,
61 | WebErrorStatusErrorHTTPInvalidServerResponse,
62 | WebErrorStatusConnectionAborted,
63 | WebErrorStatusConnectionReset,
64 | WebErrorStatusDisconnected,
65 | WebErrorStatusCannotConnect,
66 | WebErrorStatusHostNameNotResolved,
67 | WebErrorStatusOperationCanceled,
68 | WebErrorStatusRedirectFailed,
69 | WebErrorStatusUnexpectedError,
70 | WebErrorStatusValidAuthenticationCredentialsRequired,
71 | WebErrorStatusValidProxyAuthenticationRequired,
72 | }
73 |
--------------------------------------------------------------------------------
/lib/src/webview.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 | import 'dart:ui';
4 |
5 | import 'package:flutter/gestures.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:flutter/services.dart';
8 | import 'package:flutter/widgets.dart';
9 |
10 | import 'cursor.dart';
11 | import 'enums.dart';
12 |
13 | class HistoryChanged {
14 | final bool canGoBack;
15 | final bool canGoForward;
16 | const HistoryChanged(this.canGoBack, this.canGoForward);
17 | }
18 |
19 | class WebviewDownloadEvent {
20 | final WebviewDownloadEventKind kind;
21 | final String url;
22 | final String resultFilePath;
23 | final int bytesReceived;
24 | final int totalBytesToReceive;
25 | const WebviewDownloadEvent(
26 | this.kind,
27 | this.url,
28 | this.resultFilePath,
29 | this.bytesReceived,
30 | this.totalBytesToReceive,
31 | );
32 | }
33 |
34 | typedef PermissionRequestedDelegate
35 | = FutureOr Function(
36 | String url, WebviewPermissionKind permissionKind, bool isUserInitiated);
37 |
38 | typedef ScriptID = String;
39 |
40 | /// Attempts to translate a button constant such as [kPrimaryMouseButton]
41 | /// to a [PointerButton]
42 | PointerButton getButton(int value) {
43 | switch (value) {
44 | case kPrimaryMouseButton:
45 | return PointerButton.primary;
46 | case kSecondaryMouseButton:
47 | return PointerButton.secondary;
48 | case kTertiaryButton:
49 | return PointerButton.tertiary;
50 | default:
51 | return PointerButton.none;
52 | }
53 | }
54 |
55 | const String _pluginChannelPrefix = 'io.jns.webview.win';
56 | const MethodChannel _pluginChannel = MethodChannel(_pluginChannelPrefix);
57 |
58 | class WebviewValue {
59 | const WebviewValue({
60 | required this.isInitialized,
61 | });
62 |
63 | final bool isInitialized;
64 |
65 | WebviewValue copyWith({
66 | bool? isInitialized,
67 | }) {
68 | return WebviewValue(
69 | isInitialized: isInitialized ?? this.isInitialized,
70 | );
71 | }
72 |
73 | WebviewValue.uninitialized()
74 | : this(
75 | isInitialized: false,
76 | );
77 | }
78 |
79 | /// Controls a WebView and provides streams for various change events.
80 | class WebviewController extends ValueNotifier {
81 | /// Explicitly initializes the underlying WebView environment
82 | /// using an optional [browserExePath], an optional [userDataPath]
83 | /// and optional Chromium command line arguments [additionalArguments].
84 | ///
85 | /// The environment is shared between all WebviewController instances and
86 | /// can be initialized only once. Initialization must take place before any
87 | /// WebviewController is created/initialized.
88 | ///
89 | /// Throws [PlatformException] if the environment was initialized before.
90 | static Future initializeEnvironment(
91 | {String? userDataPath,
92 | String? browserExePath,
93 | String? additionalArguments}) async {
94 | return _pluginChannel
95 | .invokeMethod('initializeEnvironment', {
96 | 'userDataPath': userDataPath,
97 | 'browserExePath': browserExePath,
98 | 'additionalArguments': additionalArguments
99 | });
100 | }
101 |
102 | /// Get the browser version info including channel name if it is not the
103 | /// WebView2 Runtime.
104 | /// Returns [null] if the webview2 runtime is not installed.
105 | static Future getWebViewVersion() async {
106 | return _pluginChannel.invokeMethod('getWebViewVersion');
107 | }
108 |
109 | late Completer _creatingCompleter;
110 | int _textureId = 0;
111 | bool _isDisposed = false;
112 |
113 | Future get ready => _creatingCompleter.future;
114 |
115 | PermissionRequestedDelegate? _permissionRequested;
116 |
117 | late MethodChannel _methodChannel;
118 | late EventChannel _eventChannel;
119 | StreamSubscription? _eventStreamSubscription;
120 |
121 | final StreamController _urlStreamController =
122 | StreamController();
123 |
124 | /// A stream reflecting the current URL.
125 | Stream get url => _urlStreamController.stream;
126 |
127 | final StreamController _loadingStateStreamController =
128 | StreamController.broadcast();
129 |
130 | final StreamController _downloadEventStreamController =
131 | StreamController.broadcast();
132 |
133 | final StreamController _onLoadErrorStreamController =
134 | StreamController();
135 |
136 | /// A stream reflecting the current loading state.
137 | Stream get loadingState => _loadingStateStreamController.stream;
138 |
139 | Stream get onDownloadEvent =>
140 | _downloadEventStreamController.stream;
141 |
142 | /// A stream reflecting the navigation error when navigation completed with an error.
143 | Stream get onLoadError => _onLoadErrorStreamController.stream;
144 |
145 | final StreamController _historyChangedStreamController =
146 | StreamController();
147 |
148 | /// A stream reflecting the current history state.
149 | Stream get historyChanged =>
150 | _historyChangedStreamController.stream;
151 |
152 | final StreamController _securityStateChangedStreamController =
153 | StreamController();
154 |
155 | /// A stream reflecting the current security state.
156 | Stream get securityStateChanged =>
157 | _securityStateChangedStreamController.stream;
158 |
159 | final StreamController _titleStreamController =
160 | StreamController();
161 |
162 | /// A stream reflecting the current document title.
163 | Stream get title => _titleStreamController.stream;
164 |
165 | final StreamController _cursorStreamController =
166 | StreamController.broadcast();
167 |
168 | /// A stream reflecting the current cursor style.
169 | Stream get _cursor => _cursorStreamController.stream;
170 |
171 | final StreamController _webMessageStreamController =
172 | StreamController();
173 |
174 | Stream get webMessage => _webMessageStreamController.stream;
175 |
176 | final StreamController
177 | _containsFullScreenElementChangedStreamController =
178 | StreamController.broadcast();
179 |
180 | /// A stream reflecting whether the document currently contains full-screen elements.
181 | Stream get containsFullScreenElementChanged =>
182 | _containsFullScreenElementChangedStreamController.stream;
183 |
184 | WebviewController() : super(WebviewValue.uninitialized());
185 |
186 | /// Initializes the underlying platform view.
187 | Future initialize() async {
188 | if (_isDisposed) {
189 | return Future.value();
190 | }
191 | _creatingCompleter = Completer();
192 | try {
193 | final reply =
194 | await _pluginChannel.invokeMapMethod('initialize');
195 |
196 | _textureId = reply!['textureId'];
197 | _methodChannel = MethodChannel('$_pluginChannelPrefix/$_textureId');
198 | _eventChannel = EventChannel('$_pluginChannelPrefix/$_textureId/events');
199 | _eventStreamSubscription =
200 | _eventChannel.receiveBroadcastStream().listen((event) {
201 | final map = event as Map;
202 | switch (map['type']) {
203 | case 'urlChanged':
204 | _urlStreamController.add(map['value']);
205 | break;
206 | case 'onLoadError':
207 | final value = WebErrorStatus.values[map['value']];
208 | _onLoadErrorStreamController.add(value);
209 | break;
210 | case 'loadingStateChanged':
211 | final value = LoadingState.values[map['value']];
212 | _loadingStateStreamController.add(value);
213 | break;
214 | case 'downloadEvent':
215 | final value = WebviewDownloadEvent(
216 | WebviewDownloadEventKind.values[map['value']['kind']],
217 | map['value']['url'],
218 | map['value']['resultFilePath'],
219 | map['value']['bytesReceived'],
220 | map['value']['totalBytesToReceive'],
221 | );
222 | _downloadEventStreamController.add(value);
223 | break;
224 | case 'historyChanged':
225 | final value = HistoryChanged(
226 | map['value']['canGoBack'], map['value']['canGoForward']);
227 | _historyChangedStreamController.add(value);
228 | break;
229 | case 'securityStateChanged':
230 | _securityStateChangedStreamController.add(map['value']);
231 | break;
232 | case 'titleChanged':
233 | _titleStreamController.add(map['value']);
234 | break;
235 | case 'cursorChanged':
236 | _cursorStreamController.add(getCursorByName(map['value']));
237 | break;
238 | case 'webMessageReceived':
239 | try {
240 | final message = json.decode(map['value']);
241 | _webMessageStreamController.add(message);
242 | } catch (ex) {
243 | _webMessageStreamController.addError(ex);
244 | }
245 | break;
246 | case 'containsFullScreenElementChanged':
247 | _containsFullScreenElementChangedStreamController.add(map['value']);
248 | break;
249 | }
250 | });
251 |
252 | _methodChannel.setMethodCallHandler((call) {
253 | if (call.method == 'permissionRequested') {
254 | return _onPermissionRequested(
255 | call.arguments as Map);
256 | }
257 |
258 | throw MissingPluginException('Unknown method ${call.method}');
259 | });
260 |
261 | value = value.copyWith(isInitialized: true);
262 | _creatingCompleter.complete();
263 | } on PlatformException catch (e) {
264 | _creatingCompleter.completeError(e);
265 | }
266 |
267 | return _creatingCompleter.future;
268 | }
269 |
270 | Future _onPermissionRequested(Map args) async {
271 | if (_permissionRequested == null) {
272 | return null;
273 | }
274 |
275 | final url = args['url'] as String?;
276 | final permissionKindIndex = args['permissionKind'] as int?;
277 | final isUserInitiated = args['isUserInitiated'] as bool?;
278 |
279 | if (url != null && permissionKindIndex != null && isUserInitiated != null) {
280 | final permissionKind = WebviewPermissionKind.values[permissionKindIndex];
281 | final decision =
282 | await _permissionRequested!(url, permissionKind, isUserInitiated);
283 |
284 | switch (decision) {
285 | case WebviewPermissionDecision.allow:
286 | return true;
287 | case WebviewPermissionDecision.deny:
288 | return false;
289 | default:
290 | return null;
291 | }
292 | }
293 |
294 | return null;
295 | }
296 |
297 | @override
298 | Future dispose() async {
299 | await _creatingCompleter.future;
300 | if (!_isDisposed) {
301 | _isDisposed = true;
302 | await _eventStreamSubscription?.cancel();
303 | await _pluginChannel.invokeMethod('dispose', _textureId);
304 | }
305 | super.dispose();
306 | }
307 |
308 | /// Loads the given [url].
309 | Future loadUrl(String url) async {
310 | if (_isDisposed) {
311 | return;
312 | }
313 | assert(value.isInitialized);
314 | return _methodChannel.invokeMethod('loadUrl', url);
315 | }
316 |
317 | /// Loads a document from the given string.
318 | Future loadStringContent(String content) async {
319 | if (_isDisposed) {
320 | return;
321 | }
322 | assert(value.isInitialized);
323 | return _methodChannel.invokeMethod('loadStringContent', content);
324 | }
325 |
326 | /// Reloads the current document.
327 | Future reload() async {
328 | if (_isDisposed) {
329 | return;
330 | }
331 | assert(value.isInitialized);
332 | return _methodChannel.invokeMethod('reload');
333 | }
334 |
335 | /// Stops all navigations and pending resource fetches.
336 | Future stop() async {
337 | if (_isDisposed) {
338 | return;
339 | }
340 | assert(value.isInitialized);
341 | return _methodChannel.invokeMethod('stop');
342 | }
343 |
344 | /// Navigates the WebView to the previous page in the navigation history.
345 | Future goBack() async {
346 | if (_isDisposed) {
347 | return;
348 | }
349 | assert(value.isInitialized);
350 | return _methodChannel.invokeMethod('goBack');
351 | }
352 |
353 | /// Navigates the WebView to the next page in the navigation history.
354 | Future goForward() async {
355 | if (_isDisposed) {
356 | return;
357 | }
358 | assert(value.isInitialized);
359 | return _methodChannel.invokeMethod('goForward');
360 | }
361 |
362 | /// Adds the provided JavaScript [script] to a list of scripts that should be run after the global
363 | /// object has been created, but before the HTML document has been parsed and before any
364 | /// other script included by the HTML document is run.
365 | ///
366 | /// Returns a [ScriptID] on success which can be used for [removeScriptToExecuteOnDocumentCreated].
367 | ///
368 | /// see https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.1264.42#addscripttoexecuteondocumentcreated
369 | Future addScriptToExecuteOnDocumentCreated(String script) async {
370 | if (_isDisposed) {
371 | return null;
372 | }
373 | assert(value.isInitialized);
374 | return _methodChannel.invokeMethod(
375 | 'addScriptToExecuteOnDocumentCreated', script);
376 | }
377 |
378 | /// Removes the script identified by [scriptId] from the list of registered scripts.
379 | ///
380 | /// see https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.1264.42#removescripttoexecuteondocumentcreated
381 | Future removeScriptToExecuteOnDocumentCreated(ScriptID scriptId) async {
382 | if (_isDisposed) {
383 | return null;
384 | }
385 | assert(value.isInitialized);
386 | return _methodChannel.invokeMethod(
387 | 'removeScriptToExecuteOnDocumentCreated', scriptId);
388 | }
389 |
390 | /// Runs the JavaScript [script] in the current top-level document rendered in
391 | /// the WebView and returns its result.
392 | ///
393 | /// see https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2?view=webview2-1.0.1264.42#executescript
394 | Future executeScript(String script) async {
395 | if (_isDisposed) {
396 | return;
397 | }
398 | assert(value.isInitialized);
399 |
400 | final data = await _methodChannel.invokeMethod('executeScript', script);
401 | if (data == null) return null;
402 | return jsonDecode(data as String);
403 | }
404 |
405 | /// Posts the given JSON-formatted message to the current document.
406 | Future postWebMessage(String message) async {
407 | if (_isDisposed) {
408 | return;
409 | }
410 | assert(value.isInitialized);
411 | return _methodChannel.invokeMethod('postWebMessage', message);
412 | }
413 |
414 | /// Sets the user agent value.
415 | Future setUserAgent(String userAgent) async {
416 | if (_isDisposed) {
417 | return;
418 | }
419 | assert(value.isInitialized);
420 | return _methodChannel.invokeMethod('setUserAgent', userAgent);
421 | }
422 |
423 | /// Clears browser cookies.
424 | Future clearCookies() async {
425 | if (_isDisposed) {
426 | return;
427 | }
428 | assert(value.isInitialized);
429 | return _methodChannel.invokeMethod('clearCookies');
430 | }
431 |
432 | /// Clears browser cache.
433 | Future clearCache() async {
434 | if (_isDisposed) {
435 | return;
436 | }
437 | assert(value.isInitialized);
438 | return _methodChannel.invokeMethod('clearCache');
439 | }
440 |
441 | /// Toggles ignoring cache for each request. If true, cache will not be used.
442 | Future setCacheDisabled(bool disabled) async {
443 | if (_isDisposed) {
444 | return;
445 | }
446 | assert(value.isInitialized);
447 | return _methodChannel.invokeMethod('setCacheDisabled', disabled);
448 | }
449 |
450 | /// Opens the Browser DevTools in a separate window
451 | Future openDevTools() async {
452 | if (_isDisposed) {
453 | return;
454 | }
455 | assert(value.isInitialized);
456 | return _methodChannel.invokeMethod('openDevTools');
457 | }
458 |
459 | /// Sets the background color to the provided [color].
460 | ///
461 | /// Due to a limitation of the underlying WebView implementation,
462 | /// semi-transparent values are not supported.
463 | /// Any non-zero alpha value will be considered as opaque (0xff).
464 | Future setBackgroundColor(Color color) async {
465 | if (_isDisposed) {
466 | return;
467 | }
468 | assert(value.isInitialized);
469 | return _methodChannel.invokeMethod(
470 | 'setBackgroundColor', color.value.toSigned(32));
471 | }
472 |
473 | /// Sets the zoom factor.
474 | Future setZoomFactor(double zoomFactor) async {
475 | if (_isDisposed) {
476 | return;
477 | }
478 | assert(value.isInitialized);
479 | return _methodChannel.invokeMethod('setZoomFactor', zoomFactor);
480 | }
481 |
482 | /// Sets the [WebviewPopupWindowPolicy].
483 | Future setPopupWindowPolicy(
484 | WebviewPopupWindowPolicy popupPolicy) async {
485 | if (_isDisposed) {
486 | return;
487 | }
488 | assert(value.isInitialized);
489 | return _methodChannel.invokeMethod(
490 | 'setPopupWindowPolicy', popupPolicy.index);
491 | }
492 |
493 | /// Suspends the web view.
494 | Future suspend() async {
495 | if (_isDisposed) {
496 | return;
497 | }
498 | assert(value.isInitialized);
499 | return _methodChannel.invokeMethod('suspend');
500 | }
501 |
502 | /// Resumes the web view.
503 | Future resume() async {
504 | if (_isDisposed) {
505 | return;
506 | }
507 | assert(value.isInitialized);
508 | return _methodChannel.invokeMethod('resume');
509 | }
510 |
511 | /// Adds a Virtual Host Name Mapping.
512 | ///
513 | /// Please refer to
514 | /// [Microsofts](https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_3#setvirtualhostnametofoldermapping)
515 | /// documentation for more details.
516 | Future addVirtualHostNameMapping(String hostName, String folderPath,
517 | WebviewHostResourceAccessKind accessKind) async {
518 | if (_isDisposed) {
519 | return;
520 | }
521 |
522 | return _methodChannel.invokeMethod(
523 | 'setVirtualHostNameMapping', [hostName, folderPath, accessKind.index]);
524 | }
525 |
526 | /// Removes a Virtual Host Name Mapping.
527 | ///
528 | /// Please refer to
529 | /// [Microsofts](https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_3#clearvirtualhostnametofoldermapping)
530 | /// documentation for more details.
531 | Future removeVirtualHostNameMapping(String hostName) async {
532 | if (_isDisposed) {
533 | return;
534 | }
535 | return _methodChannel.invokeMethod('clearVirtualHostNameMapping', hostName);
536 | }
537 |
538 | /// Limits the number of frames per second to the given value.
539 | Future setFpsLimit([int? maxFps = 0]) async {
540 | if (_isDisposed) {
541 | return;
542 | }
543 | assert(value.isInitialized);
544 | return _methodChannel.invokeMethod('setFpsLimit', maxFps);
545 | }
546 |
547 | /// Sends a Pointer (Touch) update
548 | Future _setPointerUpdate(WebviewPointerEventKind kind, int pointer,
549 | Offset position, double size, double pressure) async {
550 | if (_isDisposed) {
551 | return;
552 | }
553 | assert(value.isInitialized);
554 | return _methodChannel.invokeMethod('setPointerUpdate',
555 | [pointer, kind.index, position.dx, position.dy, size, pressure]);
556 | }
557 |
558 | /// Moves the virtual cursor to [position].
559 | Future _setCursorPos(Offset position) async {
560 | if (_isDisposed) {
561 | return;
562 | }
563 | assert(value.isInitialized);
564 | return _methodChannel
565 | .invokeMethod('setCursorPos', [position.dx, position.dy]);
566 | }
567 |
568 | /// Indicates whether the specified [button] is currently down.
569 | Future _setPointerButtonState(PointerButton button, bool isDown) async {
570 | if (_isDisposed) {
571 | return;
572 | }
573 | assert(value.isInitialized);
574 | return _methodChannel.invokeMethod('setPointerButton',
575 | {'button': button.index, 'isDown': isDown});
576 | }
577 |
578 | /// Sets the horizontal and vertical scroll delta.
579 | Future _setScrollDelta(double dx, double dy) async {
580 | if (_isDisposed) {
581 | return;
582 | }
583 | assert(value.isInitialized);
584 | return _methodChannel.invokeMethod('setScrollDelta', [dx, dy]);
585 | }
586 |
587 | /// Sets the surface size to the provided [size].
588 | Future _setSize(Size size, double scaleFactor) async {
589 | if (_isDisposed) {
590 | return;
591 | }
592 | assert(value.isInitialized);
593 | return _methodChannel
594 | .invokeMethod('setSize', [size.width, size.height, scaleFactor]);
595 | }
596 | }
597 |
598 | class Webview extends StatefulWidget {
599 | final WebviewController controller;
600 | final PermissionRequestedDelegate? permissionRequested;
601 | final double? width;
602 | final double? height;
603 |
604 | /// An optional scale factor. Defaults to [FlutterView.devicePixelRatio] for
605 | /// rendering in native resolution.
606 | /// Setting this to 1.0 will disable high-DPI support.
607 | /// This should only be needed to mimic old behavior before high-DPI support
608 | /// was available.
609 | final double? scaleFactor;
610 |
611 | /// The [FilterQuality] used for scaling the texture's contents.
612 | /// Defaults to [FilterQuality.none] as this renders in native resolution
613 | /// unless specifying a [scaleFactor].
614 | final FilterQuality filterQuality;
615 |
616 | const Webview(this.controller,
617 | {this.width,
618 | this.height,
619 | this.permissionRequested,
620 | this.scaleFactor,
621 | this.filterQuality = FilterQuality.none});
622 |
623 | @override
624 | _WebviewState createState() => _WebviewState();
625 | }
626 |
627 | class _WebviewState extends State {
628 | final GlobalKey _key = GlobalKey();
629 | final _downButtons = {};
630 |
631 | PointerDeviceKind _pointerKind = PointerDeviceKind.unknown;
632 |
633 | MouseCursor _cursor = SystemMouseCursors.basic;
634 |
635 | WebviewController get _controller => widget.controller;
636 |
637 | StreamSubscription? _cursorSubscription;
638 |
639 | @override
640 | void initState() {
641 | super.initState();
642 |
643 | // TODO: Refactor callback and event handling and
644 | // remove this line
645 | _controller._permissionRequested = widget.permissionRequested;
646 |
647 | // Report initial surface size
648 | WidgetsBinding.instance.addPostFrameCallback((_) => _reportSurfaceSize());
649 |
650 | _cursorSubscription = _controller._cursor.listen((cursor) {
651 | setState(() {
652 | _cursor = cursor;
653 | });
654 | });
655 | }
656 |
657 | @override
658 | Widget build(BuildContext context) {
659 | return (widget.height != null && widget.width != null)
660 | ? SizedBox(
661 | key: _key,
662 | width: widget.width,
663 | height: widget.height,
664 | child: _buildInner())
665 | : SizedBox.expand(key: _key, child: _buildInner());
666 | }
667 |
668 | Widget _buildInner() {
669 | return NotificationListener(
670 | onNotification: (notification) {
671 | _reportSurfaceSize();
672 | return true;
673 | },
674 | child: SizeChangedLayoutNotifier(
675 | child: _controller.value.isInitialized
676 | ? Listener(
677 | onPointerHover: (ev) {
678 | // ev.kind is for whatever reason not set to touch
679 | // even on touch input
680 | if (_pointerKind == PointerDeviceKind.touch) {
681 | // Ignoring hover events on touch for now
682 | return;
683 | }
684 | _controller._setCursorPos(ev.localPosition);
685 | },
686 | onPointerDown: (ev) {
687 | _pointerKind = ev.kind;
688 | if (ev.kind == PointerDeviceKind.touch) {
689 | _controller._setPointerUpdate(
690 | WebviewPointerEventKind.down,
691 | ev.pointer,
692 | ev.localPosition,
693 | ev.size,
694 | ev.pressure);
695 | return;
696 | }
697 | final button = getButton(ev.buttons);
698 | _downButtons[ev.pointer] = button;
699 | _controller._setPointerButtonState(button, true);
700 | },
701 | onPointerUp: (ev) {
702 | _pointerKind = ev.kind;
703 | if (ev.kind == PointerDeviceKind.touch) {
704 | _controller._setPointerUpdate(
705 | WebviewPointerEventKind.up,
706 | ev.pointer,
707 | ev.localPosition,
708 | ev.size,
709 | ev.pressure);
710 | return;
711 | }
712 | final button = _downButtons.remove(ev.pointer);
713 | if (button != null) {
714 | _controller._setPointerButtonState(button, false);
715 | }
716 | },
717 | onPointerCancel: (ev) {
718 | _pointerKind = ev.kind;
719 | final button = _downButtons.remove(ev.pointer);
720 | if (button != null) {
721 | _controller._setPointerButtonState(button, false);
722 | }
723 | },
724 | onPointerMove: (ev) {
725 | _pointerKind = ev.kind;
726 | if (ev.kind == PointerDeviceKind.touch) {
727 | _controller._setPointerUpdate(
728 | WebviewPointerEventKind.update,
729 | ev.pointer,
730 | ev.localPosition,
731 | ev.size,
732 | ev.pressure);
733 | } else {
734 | _controller._setCursorPos(ev.localPosition);
735 | }
736 | },
737 | onPointerSignal: (signal) {
738 | if (signal is PointerScrollEvent) {
739 | _controller._setScrollDelta(
740 | -signal.scrollDelta.dx, -signal.scrollDelta.dy);
741 | }
742 | },
743 | onPointerPanZoomUpdate: (signal) {
744 | if (signal.panDelta.dx.abs() > signal.panDelta.dy.abs()) {
745 | _controller._setScrollDelta(-signal.panDelta.dx, 0);
746 | } else {
747 | _controller._setScrollDelta(0, signal.panDelta.dy);
748 | }
749 | },
750 | child: MouseRegion(
751 | cursor: _cursor,
752 | child: Texture(
753 | textureId: _controller._textureId,
754 | filterQuality: widget.filterQuality,
755 | )),
756 | )
757 | : const SizedBox()));
758 | }
759 |
760 | void _reportSurfaceSize() async {
761 | final box = _key.currentContext?.findRenderObject() as RenderBox?;
762 | if (box != null) {
763 | await _controller.ready;
764 | unawaited(_controller._setSize(
765 | box.size, widget.scaleFactor ?? window.devicePixelRatio));
766 | }
767 | }
768 |
769 | @override
770 | void dispose() {
771 | super.dispose();
772 | _cursorSubscription?.cancel();
773 | }
774 | }
775 |
--------------------------------------------------------------------------------
/lib/webview_windows.dart:
--------------------------------------------------------------------------------
1 | export 'src/enums.dart';
2 | export 'src/webview.dart';
3 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: webview_windows
2 | description: A WebView2-powered webview implementation for the Windows platform.
3 | version: 0.4.0
4 | repository: https://github.com/jnschulze/flutter-webview-windows
5 | homepage: https://jns.io
6 |
7 | environment:
8 | sdk: ">=2.13.0 <3.0.0"
9 | flutter: ">=3.3.0"
10 |
11 | dependencies:
12 | flutter:
13 | sdk: flutter
14 |
15 | dev_dependencies:
16 | flutter_test:
17 | sdk: flutter
18 |
19 | flutter:
20 | plugin:
21 | platforms:
22 | windows:
23 | pluginClass: WebviewWindowsPlugin
24 |
--------------------------------------------------------------------------------
/test/webview_windows_test.dart:
--------------------------------------------------------------------------------
1 | void main() {}
2 |
--------------------------------------------------------------------------------
/webview_windows.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/windows/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/
2 |
3 | # Visual Studio user-specific files.
4 | *.suo
5 | *.user
6 | *.userosscache
7 | *.sln.docstates
8 |
9 | # Visual Studio build-related files.
10 | x64/
11 | x86/
12 |
13 | # Visual Studio cache files
14 | # files ending in .cache can be ignored
15 | *.[Cc]ache
16 | # but keep track of directories ending in .cache
17 | !*.[Cc]ache/
18 |
--------------------------------------------------------------------------------
/windows/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.15)
2 | set(PROJECT_NAME "webview_windows")
3 |
4 | set(WIL_VERSION "1.0.220914.1")
5 | set(WEBVIEW_VERSION "1.0.1210.39")
6 |
7 | message(VERBOSE "CMake system version is ${CMAKE_SYSTEM_VERSION} (using SDK ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION})")
8 |
9 | project(${PROJECT_NAME} LANGUAGES CXX)
10 |
11 | # This value is used when generating builds using this plugin, so it must
12 | # not be changed
13 | set(PLUGIN_NAME "webview_windows_plugin")
14 |
15 | set(NUGET_URL https://dist.nuget.org/win-x86-commandline/v5.10.0/nuget.exe)
16 | set(NUGET_SHA256 852b71cc8c8c2d40d09ea49d321ff56fd2397b9d6ea9f96e532530307bbbafd3)
17 |
18 | find_program(NUGET nuget)
19 | if(NOT NUGET)
20 | message(NOTICE "Nuget is not installed.")
21 | set(NUGET ${CMAKE_BINARY_DIR}/nuget.exe)
22 | if (NOT EXISTS ${NUGET})
23 | message(NOTICE "Attempting to download nuget.")
24 | file(DOWNLOAD ${NUGET_URL} ${NUGET})
25 | endif()
26 |
27 | file(SHA256 ${NUGET} NUGET_DL_HASH)
28 | if (NOT NUGET_DL_HASH STREQUAL NUGET_SHA256)
29 | message(FATAL_ERROR "Integrity check for ${NUGET} failed.")
30 | endif()
31 | endif()
32 |
33 | add_custom_target(${PROJECT_NAME}_DEPENDENCIES_DOWNLOAD ALL)
34 | add_custom_command(
35 | TARGET ${PROJECT_NAME}_DEPENDENCIES_DOWNLOAD PRE_BUILD
36 | COMMAND ${NUGET} install Microsoft.Windows.ImplementationLibrary -Version ${WIL_VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages
37 | COMMAND ${NUGET} install Microsoft.Web.WebView2 -Version ${WEBVIEW_VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/packages
38 | DEPENDS ${NUGET}
39 | )
40 |
41 | add_library(${PLUGIN_NAME} SHARED
42 | "webview_windows_plugin.cc"
43 | "webview_platform.cc"
44 | "webview.cc"
45 | "webview_host.cc"
46 | "webview_bridge.cc"
47 | "texture_bridge.cc"
48 | "texture_bridge_gpu.cc"
49 | "graphics_context.cc"
50 | "util/direct3d11.interop.cc"
51 | "util/rohelper.cc"
52 | "util/string_converter.cc"
53 | )
54 |
55 | if(MSVC)
56 | target_compile_options(${PLUGIN_NAME} PRIVATE "/await")
57 | endif()
58 |
59 | apply_standard_settings(${PLUGIN_NAME})
60 | target_compile_features(${PLUGIN_NAME} PUBLIC cxx_std_20) # For std::format support
61 |
62 | set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden)
63 |
64 | target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Web.WebView2/build/native/Microsoft.Web.WebView2.targets)
65 | target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.ImplementationLibrary/build/native/Microsoft.Windows.ImplementationLibrary.targets)
66 |
67 | target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
68 | target_include_directories(${PLUGIN_NAME} INTERFACE
69 | "${CMAKE_CURRENT_SOURCE_DIR}/include"
70 | )
71 |
72 | target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin)
73 |
74 | set(webview_windows_bundled_libraries
75 | PARENT_SCOPE
76 | )
77 |
--------------------------------------------------------------------------------
/windows/graphics_context.cc:
--------------------------------------------------------------------------------
1 | #include "graphics_context.h"
2 |
3 | #include "util/d3dutil.h"
4 | #include "util/direct3d11.interop.h"
5 |
6 | GraphicsContext::GraphicsContext(rx::RoHelper* rohelper) : rohelper_(rohelper) {
7 | device_ = CreateD3DDevice();
8 | if (!device_) {
9 | return;
10 | }
11 |
12 | device_->GetImmediateContext(device_context_.put());
13 | if (FAILED(util::CreateDirect3D11DeviceFromDXGIDevice(
14 | device_.try_as().get(),
15 | (IInspectable**)device_winrt_.put()))) {
16 | return;
17 | }
18 |
19 | valid_ = true;
20 | }
21 |
22 | winrt::com_ptr
23 | GraphicsContext::CreateCompositor() {
24 | HSTRING className;
25 | HSTRING_HEADER classNameHeader;
26 |
27 | if (FAILED(rohelper_->GetStringReference(
28 | RuntimeClass_Windows_UI_Composition_Compositor, &className,
29 | &classNameHeader))) {
30 | return nullptr;
31 | }
32 |
33 | winrt::com_ptr af;
34 | if (FAILED(rohelper_->GetActivationFactory(
35 | className, __uuidof(IActivationFactory), af.put_void()))) {
36 | return nullptr;
37 | }
38 |
39 | winrt::com_ptr compositor;
40 | if (FAILED(af->ActivateInstance(
41 | reinterpret_cast(compositor.put())))) {
42 | return nullptr;
43 | }
44 |
45 | return compositor;
46 | }
47 |
48 | winrt::com_ptr
49 | GraphicsContext::CreateGraphicsCaptureItemFromVisual(
50 | ABI::Windows::UI::Composition::IVisual* visual) const {
51 | HSTRING className;
52 | HSTRING_HEADER classNameHeader;
53 |
54 | if (FAILED(rohelper_->GetStringReference(
55 | RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem, &className,
56 | &classNameHeader))) {
57 | return nullptr;
58 | }
59 |
60 | ABI::Windows::Graphics::Capture::IGraphicsCaptureItemStatics*
61 | capture_item_statics;
62 | if (FAILED(rohelper_->GetActivationFactory(
63 | className,
64 | __uuidof(
65 | ABI::Windows::Graphics::Capture::IGraphicsCaptureItemStatics),
66 | (void**)&capture_item_statics))) {
67 | return nullptr;
68 | }
69 |
70 | winrt::com_ptr
71 | capture_item;
72 | if (FAILED(
73 | capture_item_statics->CreateFromVisual(visual, capture_item.put()))) {
74 | return nullptr;
75 | }
76 |
77 | return capture_item;
78 | }
79 |
80 | winrt::com_ptr
81 | GraphicsContext::CreateCaptureFramePool(
82 | ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device,
83 | ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat,
84 | INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const {
85 | HSTRING className;
86 | HSTRING_HEADER classNameHeader;
87 |
88 | if (FAILED(rohelper_->GetStringReference(
89 | RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool,
90 | &className, &classNameHeader))) {
91 | return nullptr;
92 | }
93 |
94 | ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePoolStatics*
95 | capture_frame_pool_statics;
96 | if (FAILED(rohelper_->GetActivationFactory(
97 | className,
98 | __uuidof(ABI::Windows::Graphics::Capture::
99 | IDirect3D11CaptureFramePoolStatics),
100 | (void**)&capture_frame_pool_statics))) {
101 | return nullptr;
102 | }
103 |
104 | winrt::com_ptr
105 | capture_frame_pool;
106 |
107 | if (FAILED(capture_frame_pool_statics->Create(device, pixelFormat,
108 | numberOfBuffers, size,
109 | capture_frame_pool.put()))) {
110 | return nullptr;
111 | }
112 |
113 | return capture_frame_pool;
114 | }
115 |
116 | winrt::com_ptr
117 | GraphicsContext::CreateFreeThreadedCaptureFramePool(
118 | ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device,
119 | ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat,
120 | INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const {
121 | HSTRING className;
122 | HSTRING_HEADER classNameHeader;
123 |
124 | if (FAILED(rohelper_->GetStringReference(
125 | RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool,
126 | &className, &classNameHeader))) {
127 | return nullptr;
128 | }
129 |
130 | ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePoolStatics2*
131 | capture_frame_pool_statics;
132 | if (FAILED(rohelper_->GetActivationFactory(
133 | className,
134 | __uuidof(ABI::Windows::Graphics::Capture::
135 | IDirect3D11CaptureFramePoolStatics2),
136 | (void**)&capture_frame_pool_statics))) {
137 | return nullptr;
138 | }
139 |
140 | winrt::com_ptr
141 | capture_frame_pool;
142 |
143 | if (FAILED(capture_frame_pool_statics->CreateFreeThreaded(
144 | device, pixelFormat, numberOfBuffers, size,
145 | capture_frame_pool.put()))) {
146 | return nullptr;
147 | }
148 |
149 | return capture_frame_pool;
150 | }
151 |
--------------------------------------------------------------------------------
/windows/graphics_context.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include "util/rohelper.h"
9 |
10 | class GraphicsContext {
11 | public:
12 | GraphicsContext(rx::RoHelper* rohelper);
13 |
14 | inline bool IsValid() const { return valid_; }
15 |
16 | ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device() const {
17 | return device_winrt_.get();
18 | }
19 | ID3D11Device* d3d_device() const { return device_.get(); }
20 | ID3D11DeviceContext* d3d_device_context() const {
21 | return device_context_.get();
22 | }
23 |
24 | winrt::com_ptr CreateCompositor();
25 |
26 | winrt::com_ptr
27 | CreateGraphicsCaptureItemFromVisual(
28 | ABI::Windows::UI::Composition::IVisual* visual) const;
29 |
30 | winrt::com_ptr
31 | CreateCaptureFramePool(
32 | ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device,
33 | ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat,
34 | INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const;
35 |
36 | winrt::com_ptr
37 | CreateFreeThreadedCaptureFramePool(
38 | ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice* device,
39 | ABI::Windows::Graphics::DirectX::DirectXPixelFormat pixelFormat,
40 | INT32 numberOfBuffers, ABI::Windows::Graphics::SizeInt32 size) const;
41 |
42 | private:
43 | bool valid_ = false;
44 | rx::RoHelper* rohelper_;
45 | winrt::com_ptr
46 | device_winrt_;
47 | winrt::com_ptr device_{nullptr};
48 | winrt::com_ptr device_context_{nullptr};
49 | };
50 |
--------------------------------------------------------------------------------
/windows/include/webview_windows/webview_windows_plugin.h:
--------------------------------------------------------------------------------
1 | #ifndef FLUTTER_PLUGIN_WEBVIEW_WINDOWS_PLUGIN_H_
2 | #define FLUTTER_PLUGIN_WEBVIEW_WINDOWS_PLUGIN_H_
3 |
4 | #include
5 |
6 | #ifdef FLUTTER_PLUGIN_IMPL
7 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllexport)
8 | #else
9 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllimport)
10 | #endif
11 |
12 | #if defined(__cplusplus)
13 | extern "C" {
14 | #endif
15 |
16 | FLUTTER_PLUGIN_EXPORT void WebviewWindowsPluginRegisterWithRegistrar(
17 | FlutterDesktopPluginRegistrarRef registrar);
18 |
19 | #if defined(__cplusplus)
20 | } // extern "C"
21 | #endif
22 |
23 | #endif // FLUTTER_PLUGIN_WEBVIEW_WINDOWS_PLUGIN_H_
24 |
--------------------------------------------------------------------------------
/windows/texture_bridge.cc:
--------------------------------------------------------------------------------
1 | #include "texture_bridge.h"
2 |
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include "util/direct3d11.interop.h"
11 |
12 | namespace {
13 | const int kNumBuffers = 1;
14 | } // namespace
15 |
16 | TextureBridge::TextureBridge(GraphicsContext* graphics_context,
17 | ABI::Windows::UI::Composition::IVisual* visual)
18 | : graphics_context_(graphics_context) {
19 | capture_item_ =
20 | graphics_context_->CreateGraphicsCaptureItemFromVisual(visual);
21 | assert(capture_item_);
22 |
23 | capture_item_->add_Closed(
24 | Microsoft::WRL::Callback>(
27 | [](ABI::Windows::Graphics::Capture::IGraphicsCaptureItem* item,
28 | IInspectable* args) -> HRESULT {
29 | std::cerr << "Capture item was closed." << std::endl;
30 | return S_OK;
31 | })
32 | .Get(),
33 | &on_closed_token_);
34 | }
35 |
36 | TextureBridge::~TextureBridge() {
37 | const std::lock_guard lock(mutex_);
38 | StopInternal();
39 | if (capture_item_) {
40 | capture_item_->remove_Closed(on_closed_token_);
41 | }
42 | }
43 |
44 | bool TextureBridge::Start() {
45 | const std::lock_guard lock(mutex_);
46 | if (is_running_ || !capture_item_) {
47 | return false;
48 | }
49 |
50 | ABI::Windows::Graphics::SizeInt32 size;
51 | capture_item_->get_Size(&size);
52 |
53 | frame_pool_ = graphics_context_->CreateCaptureFramePool(
54 | graphics_context_->device(),
55 | static_cast(
56 | kPixelFormat),
57 | kNumBuffers, size);
58 | assert(frame_pool_);
59 |
60 | frame_pool_->add_FrameArrived(
61 | Microsoft::WRL::Callback>(
64 | [this](ABI::Windows::Graphics::Capture::IDirect3D11CaptureFramePool*
65 | pool,
66 | IInspectable* args) -> HRESULT {
67 | OnFrameArrived();
68 | return S_OK;
69 | })
70 | .Get(),
71 | &on_frame_arrived_token_);
72 |
73 | if (FAILED(frame_pool_->CreateCaptureSession(capture_item_.get(),
74 | capture_session_.put()))) {
75 | std::cerr << "Creating capture session failed." << std::endl;
76 | return false;
77 | }
78 |
79 | if (SUCCEEDED(capture_session_->StartCapture())) {
80 | is_running_ = true;
81 | return true;
82 | }
83 |
84 | return false;
85 | }
86 |
87 | void TextureBridge::Stop() {
88 | const std::lock_guard lock(mutex_);
89 | StopInternal();
90 | }
91 |
92 | void TextureBridge::StopInternal() {
93 | if (is_running_) {
94 | is_running_ = false;
95 | frame_pool_->remove_FrameArrived(on_frame_arrived_token_);
96 | auto closable =
97 | capture_session_.try_as();
98 | assert(closable);
99 | closable->Close();
100 | capture_session_ = nullptr;
101 | }
102 | }
103 |
104 | void TextureBridge::OnFrameArrived() {
105 | const std::lock_guard lock(mutex_);
106 | if (!is_running_) {
107 | return;
108 | }
109 |
110 | bool has_frame = false;
111 |
112 | winrt::com_ptr
113 | frame;
114 | auto hr = frame_pool_->TryGetNextFrame(frame.put());
115 | if (SUCCEEDED(hr) && frame) {
116 | winrt::com_ptr<
117 | ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface>
118 | frame_surface;
119 |
120 | if (SUCCEEDED(frame->get_Surface(frame_surface.put()))) {
121 | last_frame_ =
122 | util::TryGetDXGIInterfaceFromObject(frame_surface);
123 | has_frame = !ShouldDropFrame();
124 | }
125 | }
126 |
127 | if (needs_update_) {
128 | ABI::Windows::Graphics::SizeInt32 size;
129 | capture_item_->get_Size(&size);
130 | frame_pool_->Recreate(
131 | graphics_context_->device(),
132 | static_cast(
133 | kPixelFormat),
134 | kNumBuffers, size);
135 | needs_update_ = false;
136 | }
137 |
138 | if (has_frame && frame_available_) {
139 | frame_available_();
140 | }
141 | }
142 |
143 | bool TextureBridge::ShouldDropFrame() {
144 | if (!frame_duration_.has_value()) {
145 | return false;
146 | }
147 | auto now = std::chrono::high_resolution_clock::now();
148 |
149 | bool should_drop_frame = false;
150 | if (last_frame_timestamp_.has_value()) {
151 | auto diff = std::chrono::duration_cast(
152 | now - last_frame_timestamp_.value());
153 | should_drop_frame = diff < frame_duration_.value();
154 | }
155 |
156 | if (!should_drop_frame) {
157 | last_frame_timestamp_ = now;
158 | }
159 | return should_drop_frame;
160 | }
161 |
162 | void TextureBridge::NotifySurfaceSizeChanged() {
163 | const std::lock_guard lock(mutex_);
164 | needs_update_ = true;
165 | }
166 |
167 | void TextureBridge::SetFpsLimit(std::optional max_fps) {
168 | const std::lock_guard lock(mutex_);
169 | auto value = max_fps.value_or(0);
170 | if (value != 0) {
171 | frame_duration_ = FrameDuration(1000.0 / value);
172 | } else {
173 | frame_duration_.reset();
174 | last_frame_timestamp_.reset();
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/windows/texture_bridge.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | #include "graphics_context.h"
13 |
14 | typedef struct {
15 | size_t width;
16 | size_t height;
17 | } Size;
18 |
19 | class TextureBridge {
20 | public:
21 | typedef std::function FrameAvailableCallback;
22 | typedef std::function SurfaceSizeChangedCallback;
23 | typedef std::chrono::duration FrameDuration;
24 |
25 | TextureBridge(GraphicsContext* graphics_context,
26 | ABI::Windows::UI::Composition::IVisual* visual);
27 | virtual ~TextureBridge();
28 |
29 | bool Start();
30 | void Stop();
31 |
32 | void SetOnFrameAvailable(FrameAvailableCallback callback) {
33 | frame_available_ = std::move(callback);
34 | }
35 |
36 | void SetOnSurfaceSizeChanged(SurfaceSizeChangedCallback callback) {
37 | surface_size_changed_ = std::move(callback);
38 | }
39 |
40 | void NotifySurfaceSizeChanged();
41 | void SetFpsLimit(std::optional max_fps);
42 |
43 | protected:
44 | bool is_running_ = false;
45 |
46 | const GraphicsContext* graphics_context_;
47 | std::mutex mutex_;
48 | std::optional frame_duration_ = std::nullopt;
49 |
50 | FrameAvailableCallback frame_available_;
51 | SurfaceSizeChangedCallback surface_size_changed_;
52 | std::atomic needs_update_ = false;
53 | winrt::com_ptr last_frame_;
54 | std::optional
55 | last_frame_timestamp_;
56 |
57 | winrt::com_ptr
58 | capture_item_;
59 | winrt::com_ptr
60 | frame_pool_;
61 | winrt::com_ptr
62 | capture_session_;
63 |
64 | EventRegistrationToken on_closed_token_ = {};
65 | EventRegistrationToken on_frame_arrived_token_ = {};
66 |
67 | virtual void StopInternal();
68 | void OnFrameArrived();
69 | bool ShouldDropFrame();
70 |
71 | // corresponds to DXGI_FORMAT_B8G8R8A8_UNORM
72 | static constexpr auto kPixelFormat = ABI::Windows::Graphics::DirectX::
73 | DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized;
74 | };
75 |
--------------------------------------------------------------------------------
/windows/texture_bridge_gpu.cc:
--------------------------------------------------------------------------------
1 | #include "texture_bridge_gpu.h"
2 |
3 | #include
4 |
5 | #include "util/direct3d11.interop.h"
6 |
7 | TextureBridgeGpu::TextureBridgeGpu(
8 | GraphicsContext* graphics_context,
9 | ABI::Windows::UI::Composition::IVisual* visual)
10 | : TextureBridge(graphics_context, visual) {
11 | surface_descriptor_.struct_size = sizeof(FlutterDesktopGpuSurfaceDescriptor);
12 | surface_descriptor_.format =
13 | kFlutterDesktopPixelFormatNone; // no format required for DXGI surfaces
14 | }
15 |
16 | void TextureBridgeGpu::ProcessFrame(
17 | winrt::com_ptr src_texture) {
18 | D3D11_TEXTURE2D_DESC desc;
19 | src_texture->GetDesc(&desc);
20 |
21 | const auto width = desc.Width;
22 | const auto height = desc.Height;
23 |
24 | EnsureSurface(width, height);
25 |
26 | auto device_context = graphics_context_->d3d_device_context();
27 |
28 | device_context->CopyResource(surface_.get(), src_texture.get());
29 | device_context->Flush();
30 | }
31 |
32 | void TextureBridgeGpu::EnsureSurface(uint32_t width, uint32_t height) {
33 | if (!surface_ || surface_size_.width != width ||
34 | surface_size_.height != height) {
35 | D3D11_TEXTURE2D_DESC dstDesc = {};
36 | dstDesc.ArraySize = 1;
37 | dstDesc.MipLevels = 1;
38 | dstDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
39 | dstDesc.CPUAccessFlags = 0;
40 | dstDesc.Format = static_cast(kPixelFormat);
41 | dstDesc.Width = width;
42 | dstDesc.Height = height;
43 | dstDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
44 | dstDesc.SampleDesc.Count = 1;
45 | dstDesc.SampleDesc.Quality = 0;
46 | dstDesc.Usage = D3D11_USAGE_DEFAULT;
47 |
48 | surface_ = nullptr;
49 | if (!SUCCEEDED(graphics_context_->d3d_device()->CreateTexture2D(
50 | &dstDesc, nullptr, surface_.put()))) {
51 | std::cerr << "Creating intermediate texture failed" << std::endl;
52 | return;
53 | }
54 |
55 | HANDLE shared_handle;
56 | surface_.try_as(dxgi_surface_);
57 | assert(dxgi_surface_);
58 | dxgi_surface_->GetSharedHandle(&shared_handle);
59 |
60 | surface_descriptor_.handle = shared_handle;
61 | surface_descriptor_.width = surface_descriptor_.visible_width = width;
62 | surface_descriptor_.height = surface_descriptor_.visible_height = height;
63 | surface_descriptor_.release_context = surface_.get();
64 | surface_descriptor_.release_callback = [](void* release_context) {
65 | auto texture = reinterpret_cast(release_context);
66 | texture->Release();
67 | };
68 |
69 | surface_size_ = {width, height};
70 | }
71 | }
72 |
73 | const FlutterDesktopGpuSurfaceDescriptor*
74 | TextureBridgeGpu::GetSurfaceDescriptor(size_t width, size_t height) {
75 | const std::lock_guard lock(mutex_);
76 |
77 | if (!is_running_) {
78 | return nullptr;
79 | }
80 |
81 | if (last_frame_) {
82 | ProcessFrame(last_frame_);
83 | }
84 |
85 | if (surface_) {
86 | // Gets released in the SurfaceDescriptor's release callback.
87 | surface_->AddRef();
88 | }
89 |
90 | return &surface_descriptor_;
91 | }
92 |
93 | void TextureBridgeGpu::StopInternal() {
94 | TextureBridge::StopInternal();
95 |
96 | // For some reason, the destination surface needs to be recreated upon
97 | // resuming. Force |EnsureSurface| to create a new one by resetting it here.
98 | surface_ = nullptr;
99 | }
100 |
--------------------------------------------------------------------------------
/windows/texture_bridge_gpu.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "texture_bridge.h"
6 |
7 | class TextureBridgeGpu : public TextureBridge {
8 | public:
9 | TextureBridgeGpu(GraphicsContext* graphics_context,
10 | ABI::Windows::UI::Composition::IVisual* visual);
11 |
12 | const FlutterDesktopGpuSurfaceDescriptor* GetSurfaceDescriptor(size_t width,
13 | size_t height);
14 |
15 | protected:
16 | void StopInternal() override;
17 |
18 | private:
19 | FlutterDesktopGpuSurfaceDescriptor surface_descriptor_ = {};
20 | Size surface_size_ = {0, 0};
21 | winrt::com_ptr surface_{nullptr};
22 | winrt::com_ptr dxgi_surface_;
23 |
24 | void ProcessFrame(winrt::com_ptr src_texture);
25 | void EnsureSurface(uint32_t width, uint32_t height);
26 | };
27 |
--------------------------------------------------------------------------------
/windows/util/composition.desktop.interop.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace util {
6 |
7 | winrt::com_ptr
8 | TryCreateDesktopWindowTarget(
9 | const winrt::com_ptr&
10 | compositor,
11 | HWND window) {
12 | namespace abi = ABI::Windows::UI::Composition::Desktop;
13 | auto interop = compositor.try_as();
14 |
15 | winrt::com_ptr target;
16 | interop->CreateDesktopWindowTarget(window, true, target.put());
17 | return target;
18 | }
19 |
20 | } // namespace util
21 |
--------------------------------------------------------------------------------
/windows/util/d3dutil.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | inline auto CreateD3DDevice(D3D_DRIVER_TYPE const type,
8 | winrt::com_ptr& device) {
9 | WINRT_ASSERT(!device);
10 |
11 | UINT flags =
12 | D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
13 |
14 | //#ifdef _DEBUG
15 | // flags |= D3D11_CREATE_DEVICE_DEBUG;
16 | //#endif
17 |
18 | return D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0,
19 | D3D11_SDK_VERSION, device.put(), nullptr, nullptr);
20 | }
21 |
22 | inline auto CreateD3DDevice() {
23 | winrt::com_ptr device;
24 | HRESULT hr = CreateD3DDevice(D3D_DRIVER_TYPE_HARDWARE, device);
25 |
26 | if (DXGI_ERROR_UNSUPPORTED == hr) {
27 | CreateD3DDevice(D3D_DRIVER_TYPE_WARP, device);
28 | }
29 |
30 | return device;
31 | }
32 |
--------------------------------------------------------------------------------
/windows/util/direct3d11.interop.cc:
--------------------------------------------------------------------------------
1 | #include "direct3d11.interop.h"
2 |
3 | namespace util {
4 |
5 | namespace {
6 |
7 | typedef HRESULT(WINAPI* CreateDirect3D11DeviceFromDXGIDeviceFn)(IDXGIDevice*,
8 | LPVOID*);
9 |
10 | struct D3DFuncs {
11 | CreateDirect3D11DeviceFromDXGIDeviceFn CreateDirect3D11DeviceFromDXGIDevice =
12 | nullptr;
13 |
14 | D3DFuncs() {
15 | auto handle = GetModuleHandle(L"d3d11.dll");
16 | if (!handle) {
17 | return;
18 | }
19 |
20 | CreateDirect3D11DeviceFromDXGIDevice =
21 | reinterpret_cast(
22 | GetProcAddress(handle, "CreateDirect3D11DeviceFromDXGIDevice"));
23 | }
24 |
25 | static const D3DFuncs& instance() {
26 | static D3DFuncs funcs;
27 | return funcs;
28 | }
29 | };
30 |
31 | } // namespace
32 |
33 | HRESULT CreateDirect3D11DeviceFromDXGIDevice(IDXGIDevice* dxgiDevice,
34 | IInspectable** graphicsDevice) {
35 | auto ptr = D3DFuncs::instance().CreateDirect3D11DeviceFromDXGIDevice;
36 | if (ptr) {
37 | return ptr(dxgiDevice, reinterpret_cast(graphicsDevice));
38 | }
39 |
40 | return E_NOTIMPL;
41 | }
42 |
43 | } // namespace util
44 |
--------------------------------------------------------------------------------
/windows/util/direct3d11.interop.h:
--------------------------------------------------------------------------------
1 |
2 | #pragma once
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include "dxgi.h"
9 |
10 | namespace Windows {
11 | namespace Graphics {
12 | namespace DirectX {
13 | namespace Direct3D11 {
14 | struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1"))
15 | IDirect3DDxgiInterfaceAccess : ::IUnknown {
16 | virtual HRESULT __stdcall GetInterface(GUID const& id, void** object) = 0;
17 | };
18 |
19 | } // namespace Direct3D11
20 | } // namespace DirectX
21 | } // namespace Graphics
22 | } // namespace Windows
23 |
24 | namespace util {
25 |
26 | HRESULT CreateDirect3D11DeviceFromDXGIDevice(IDXGIDevice* dxgiDevice,
27 | IInspectable** graphicsDevice);
28 |
29 | template
30 | auto GetDXGIInterfaceFromObject(
31 | winrt::Windows::Foundation::IInspectable const& object) {
32 | auto access = object.as<
33 | Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>();
34 | winrt::com_ptr result;
35 | winrt::check_hresult(
36 | access->GetInterface(winrt::guid_of(), result.put_void()));
37 | return result;
38 | }
39 |
40 | template
41 | auto TryGetDXGIInterfaceFromObject(const winrt::com_ptr& object) {
42 | auto access = object.try_as<
43 | Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>();
44 | winrt::com_ptr result;
45 | access->GetInterface(winrt::guid_of(), result.put_void());
46 | return result;
47 | }
48 |
49 | } // namespace util
50 |
--------------------------------------------------------------------------------
/windows/util/rohelper.cc:
--------------------------------------------------------------------------------
1 | // Based on ANGLE's RoHelper (CompositorNativeWindow11.{cpp,h})
2 | // - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.h
3 | // - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.cpp
4 | // - https://gist.github.com/clarkezone/43e984fb9bdcd2cfcd9a4f41c208a02f
5 | //
6 | // Copyright 2018 The ANGLE Project Authors.
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions
11 | // are met:
12 | //
13 | // Redistributions of source code must retain the above copyright
14 | // notice, this list of conditions and the following disclaimer.
15 | //
16 | // Redistributions in binary form must reproduce the above
17 | // copyright notice, this list of conditions and the following
18 | // disclaimer in the documentation and/or other materials provided
19 | // with the distribution.
20 | //
21 | // Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc.
22 | // Ltd., nor the names of their contributors may be used to endorse
23 | // or promote products derived from this software without specific
24 | // prior written permission.
25 | //
26 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
29 | // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
30 | // COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
32 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
36 | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 | // POSSIBILITY OF SUCH DAMAGE.
38 |
39 | #include "rohelper.h"
40 |
41 | #include
42 | #include
43 |
44 | namespace rx {
45 | template
46 | bool AssignProcAddress(HMODULE comBaseModule, const char* name, T*& outProc) {
47 | outProc = reinterpret_cast(GetProcAddress(comBaseModule, name));
48 | return *outProc != nullptr;
49 | }
50 |
51 | RoHelper::RoHelper(RO_INIT_TYPE init_type)
52 | : mFpWindowsCreateStringReference(nullptr),
53 | mFpGetActivationFactory(nullptr),
54 | mFpWindowsCompareStringOrdinal(nullptr),
55 | mFpCreateDispatcherQueueController(nullptr),
56 | mFpWindowsDeleteString(nullptr),
57 | mFpRoInitialize(nullptr),
58 | mFpRoUninitialize(nullptr),
59 | mWinRtAvailable(false),
60 | mComBaseModule(nullptr),
61 | mCoreMessagingModule(nullptr) {
62 | #ifdef WINUWP
63 | mFpWindowsCreateStringReference = &::WindowsCreateStringReference;
64 | mFpRoInitialize = &::RoInitialize;
65 | mFpRoUninitialize = &::RoUninitialize;
66 | mFpWindowsDeleteString = &::WindowsDeleteString;
67 | mFpGetActivationFactory = &::RoGetActivationFactory;
68 | mFpWindowsCompareStringOrdinal = &::WindowsCompareStringOrdinal;
69 | mFpCreateDispatcherQueueController = &::CreateDispatcherQueueController;
70 | mWinRtAvailable = true;
71 | #else
72 |
73 | mComBaseModule = LoadLibraryA("ComBase.dll");
74 |
75 | if (mComBaseModule == nullptr) {
76 | return;
77 | }
78 |
79 | if (!AssignProcAddress(mComBaseModule, "WindowsCreateStringReference",
80 | mFpWindowsCreateStringReference)) {
81 | return;
82 | }
83 |
84 | if (!AssignProcAddress(mComBaseModule, "RoGetActivationFactory",
85 | mFpGetActivationFactory)) {
86 | return;
87 | }
88 |
89 | if (!AssignProcAddress(mComBaseModule, "WindowsCompareStringOrdinal",
90 | mFpWindowsCompareStringOrdinal)) {
91 | return;
92 | }
93 |
94 | if (!AssignProcAddress(mComBaseModule, "WindowsDeleteString",
95 | mFpWindowsDeleteString)) {
96 | return;
97 | }
98 |
99 | if (!AssignProcAddress(mComBaseModule, "RoInitialize", mFpRoInitialize)) {
100 | return;
101 | }
102 |
103 | if (!AssignProcAddress(mComBaseModule, "RoUninitialize", mFpRoUninitialize)) {
104 | return;
105 | }
106 |
107 | mCoreMessagingModule = LoadLibraryA("coremessaging.dll");
108 |
109 | if (mCoreMessagingModule == nullptr) {
110 | return;
111 | }
112 |
113 | if (!AssignProcAddress(mCoreMessagingModule,
114 | "CreateDispatcherQueueController",
115 | mFpCreateDispatcherQueueController)) {
116 | return;
117 | }
118 |
119 | auto result = RoInitialize(init_type);
120 |
121 | if (SUCCEEDED(result) || result == S_FALSE || result == RPC_E_CHANGED_MODE) {
122 | mWinRtAvailable = true;
123 | }
124 | #endif
125 | }
126 |
127 | RoHelper::~RoHelper() {
128 | #ifndef WINUWP
129 | if (mWinRtAvailable) {
130 | RoUninitialize();
131 | }
132 |
133 | if (mCoreMessagingModule != nullptr) {
134 | FreeLibrary(mCoreMessagingModule);
135 | mCoreMessagingModule = nullptr;
136 | }
137 |
138 | if (mComBaseModule != nullptr) {
139 | FreeLibrary(mComBaseModule);
140 | mComBaseModule = nullptr;
141 | }
142 | #endif
143 | }
144 |
145 | bool RoHelper::WinRtAvailable() const { return mWinRtAvailable; }
146 |
147 | bool RoHelper::SupportedWindowsRelease() {
148 | if (!mWinRtAvailable) {
149 | return false;
150 | }
151 |
152 | HSTRING className, contractName;
153 | HSTRING_HEADER classNameHeader, contractNameHeader;
154 | boolean isSupported = false;
155 |
156 | HRESULT hr = GetStringReference(
157 | RuntimeClass_Windows_Foundation_Metadata_ApiInformation, &className,
158 | &classNameHeader);
159 |
160 | if (FAILED(hr)) {
161 | return !!isSupported;
162 | }
163 |
164 | Microsoft::WRL::ComPtr<
165 | ABI::Windows::Foundation::Metadata::IApiInformationStatics>
166 | api;
167 |
168 | hr = GetActivationFactory(
169 | className,
170 | __uuidof(ABI::Windows::Foundation::Metadata::IApiInformationStatics),
171 | &api);
172 |
173 | if (FAILED(hr)) {
174 | return !!isSupported;
175 | }
176 |
177 | hr = GetStringReference(L"Windows.Foundation.UniversalApiContract",
178 | &contractName, &contractNameHeader);
179 | if (FAILED(hr)) {
180 | return !!isSupported;
181 | }
182 |
183 | api->IsApiContractPresentByMajor(contractName, 6, &isSupported);
184 |
185 | return !!isSupported;
186 | }
187 |
188 | HRESULT RoHelper::GetStringReference(PCWSTR source, HSTRING* act,
189 | HSTRING_HEADER* header) {
190 | if (!mWinRtAvailable) {
191 | return E_FAIL;
192 | }
193 |
194 | const wchar_t* str = static_cast(source);
195 |
196 | unsigned int length;
197 | HRESULT hr = SizeTToUInt32(::wcslen(str), &length);
198 | if (FAILED(hr)) {
199 | return hr;
200 | }
201 |
202 | return mFpWindowsCreateStringReference(source, length, header, act);
203 | }
204 |
205 | HRESULT RoHelper::GetActivationFactory(const HSTRING act,
206 | const IID& interfaceId, void** fac) {
207 | if (!mWinRtAvailable) {
208 | return E_FAIL;
209 | }
210 | auto hr = mFpGetActivationFactory(act, interfaceId, fac);
211 | return hr;
212 | }
213 |
214 | HRESULT RoHelper::WindowsCompareStringOrdinal(HSTRING one, HSTRING two,
215 | int* result) {
216 | if (!mWinRtAvailable) {
217 | return E_FAIL;
218 | }
219 | return mFpWindowsCompareStringOrdinal(one, two, result);
220 | }
221 |
222 | HRESULT RoHelper::CreateDispatcherQueueController(
223 | DispatcherQueueOptions options,
224 | ABI::Windows::System::IDispatcherQueueController**
225 | dispatcherQueueController) {
226 | if (!mWinRtAvailable) {
227 | return E_FAIL;
228 | }
229 | return mFpCreateDispatcherQueueController(options, dispatcherQueueController);
230 | }
231 |
232 | HRESULT RoHelper::WindowsDeleteString(HSTRING one) {
233 | if (!mWinRtAvailable) {
234 | return E_FAIL;
235 | }
236 | return mFpWindowsDeleteString(one);
237 | }
238 |
239 | HRESULT RoHelper::RoInitialize(RO_INIT_TYPE type) {
240 | return mFpRoInitialize(type);
241 | }
242 |
243 | void RoHelper::RoUninitialize() { mFpRoUninitialize(); }
244 | } // namespace rx
245 |
--------------------------------------------------------------------------------
/windows/util/rohelper.h:
--------------------------------------------------------------------------------
1 | // Based on ANGLE's RoHelper (CompositorNativeWindow11.{cpp,h})
2 | // - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.h
3 | // - https://github.com/google/angle/blob/main/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.cpp
4 | // - https://gist.github.com/clarkezone/43e984fb9bdcd2cfcd9a4f41c208a02f
5 | //
6 | // Copyright 2018 The ANGLE Project Authors.
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions
11 | // are met:
12 | //
13 | // Redistributions of source code must retain the above copyright
14 | // notice, this list of conditions and the following disclaimer.
15 | //
16 | // Redistributions in binary form must reproduce the above
17 | // copyright notice, this list of conditions and the following
18 | // disclaimer in the documentation and/or other materials provided
19 | // with the distribution.
20 | //
21 | // Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc.
22 | // Ltd., nor the names of their contributors may be used to endorse
23 | // or promote products derived from this software without specific
24 | // prior written permission.
25 | //
26 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
29 | // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
30 | // COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
32 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
36 | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 | // POSSIBILITY OF SUCH DAMAGE.
38 |
39 | #pragma once
40 |
41 | #include
42 | #include
43 | #include
44 |
45 | namespace rx {
46 | class RoHelper {
47 | public:
48 | RoHelper(RO_INIT_TYPE init_type);
49 | ~RoHelper();
50 | bool WinRtAvailable() const;
51 | bool SupportedWindowsRelease();
52 | HRESULT GetStringReference(PCWSTR source, HSTRING* act,
53 | HSTRING_HEADER* header);
54 | HRESULT GetActivationFactory(const HSTRING act, const IID& interfaceId,
55 | void** fac);
56 | HRESULT WindowsCompareStringOrdinal(HSTRING one, HSTRING two, int* result);
57 | HRESULT CreateDispatcherQueueController(
58 | DispatcherQueueOptions options,
59 | ABI::Windows::System::IDispatcherQueueController**
60 | dispatcherQueueController);
61 | HRESULT WindowsDeleteString(HSTRING one);
62 | HRESULT RoInitialize(RO_INIT_TYPE type);
63 | void RoUninitialize();
64 |
65 | private:
66 | using WindowsCreateStringReference_ = HRESULT __stdcall(PCWSTR, UINT32,
67 | HSTRING_HEADER*,
68 | HSTRING*);
69 |
70 | using GetActivationFactory_ = HRESULT __stdcall(HSTRING, REFIID, void**);
71 |
72 | using WindowsCompareStringOrginal_ = HRESULT __stdcall(HSTRING, HSTRING,
73 | int*);
74 |
75 | using WindowsDeleteString_ = HRESULT __stdcall(HSTRING);
76 |
77 | using CreateDispatcherQueueController_ =
78 | HRESULT __stdcall(DispatcherQueueOptions,
79 | ABI::Windows::System::IDispatcherQueueController**);
80 |
81 | using RoInitialize_ = HRESULT __stdcall(RO_INIT_TYPE);
82 | using RoUninitialize_ = void __stdcall();
83 |
84 | WindowsCreateStringReference_* mFpWindowsCreateStringReference;
85 | GetActivationFactory_* mFpGetActivationFactory;
86 | WindowsCompareStringOrginal_* mFpWindowsCompareStringOrdinal;
87 | CreateDispatcherQueueController_* mFpCreateDispatcherQueueController;
88 | WindowsDeleteString_* mFpWindowsDeleteString;
89 | RoInitialize_* mFpRoInitialize;
90 | RoUninitialize_* mFpRoUninitialize;
91 |
92 | bool mWinRtAvailable;
93 |
94 | HMODULE mComBaseModule;
95 | HMODULE mCoreMessagingModule;
96 | };
97 | } // namespace rx
98 |
--------------------------------------------------------------------------------
/windows/util/string_converter.cc:
--------------------------------------------------------------------------------
1 | #include "string_converter.h"
2 |
3 | #include
4 |
5 | namespace util {
6 | std::string Utf8FromUtf16(std::wstring_view utf16_string) {
7 | if (utf16_string.empty()) {
8 | return std::string();
9 | }
10 |
11 | auto src_length = static_cast(utf16_string.size());
12 | int target_length =
13 | ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(),
14 | src_length, nullptr, 0, nullptr, nullptr);
15 |
16 | std::string utf8_string;
17 | if (target_length <= 0 || target_length > utf8_string.max_size()) {
18 | return utf8_string;
19 | }
20 | utf8_string.resize(target_length);
21 | int converted_length = ::WideCharToMultiByte(
22 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), src_length,
23 | utf8_string.data(), target_length, nullptr, nullptr);
24 | if (converted_length == 0) {
25 | return std::string();
26 | }
27 | return utf8_string;
28 | }
29 |
30 | std::wstring Utf16FromUtf8(std::string_view utf8_string) {
31 | if (utf8_string.empty()) {
32 | return std::wstring();
33 | }
34 |
35 | auto src_length = static_cast(utf8_string.size());
36 | int target_length =
37 | ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(),
38 | src_length, nullptr, 0);
39 |
40 | std::wstring utf16_string;
41 | if (target_length <= 0 || target_length > utf16_string.max_size()) {
42 | return utf16_string;
43 | }
44 | utf16_string.resize(target_length);
45 | int converted_length =
46 | ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(),
47 | src_length, utf16_string.data(), target_length);
48 | if (converted_length == 0) {
49 | return std::wstring();
50 | }
51 | return utf16_string;
52 | }
53 |
54 | } // namespace util
55 |
--------------------------------------------------------------------------------
/windows/util/string_converter.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace util {
6 | std::string Utf8FromUtf16(std::wstring_view utf16_string);
7 | std::wstring Utf16FromUtf8(std::string_view utf8_string);
8 | } // namespace util
9 |
--------------------------------------------------------------------------------
/windows/webview.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include
10 |
11 | class WebviewHost;
12 |
13 | enum class WebviewLoadingState { None, Loading, NavigationCompleted };
14 |
15 | enum class WebviewPointerButton { None, Primary, Secondary, Tertiary };
16 |
17 | enum class WebviewPointerEventKind { Activate, Down, Enter, Leave, Up, Update };
18 |
19 | enum class WebviewDownloadEventKind {
20 | DownloadStarted,
21 | DownloadCompleted,
22 | DownloadProgress
23 | };
24 |
25 | enum class WebviewPermissionKind {
26 | Unknown,
27 | Microphone,
28 | Camera,
29 | GeoLocation,
30 | Notifications,
31 | OtherSensors,
32 | ClipboardRead
33 | };
34 |
35 | enum class WebviewPermissionState { Default, Allow, Deny };
36 |
37 | enum class WebviewPopupWindowPolicy { Allow, Deny, ShowInSameWindow };
38 |
39 | enum class WebviewHostResourceAccessKind { Deny, Allow, DenyCors };
40 |
41 | struct WebviewHistoryChanged {
42 | BOOL can_go_back;
43 | BOOL can_go_forward;
44 | };
45 |
46 | struct WebviewDownloadEvent {
47 | WebviewDownloadEventKind kind;
48 | std::string url;
49 | std::string resultFilePath;
50 | INT64 bytesReceived;
51 | INT64 totalBytesToReceive;
52 | };
53 |
54 | struct VirtualKeyState {
55 | public:
56 | inline void set_isLeftButtonDown(bool is_down) {
57 | set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS::
58 | COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_LEFT_BUTTON,
59 | is_down);
60 | }
61 |
62 | inline void set_isRightButtonDown(bool is_down) {
63 | set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS::
64 | COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_RIGHT_BUTTON,
65 | is_down);
66 | }
67 |
68 | inline void set_isMiddleButtonDown(bool is_down) {
69 | set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS::
70 | COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_MIDDLE_BUTTON,
71 | is_down);
72 | }
73 |
74 | inline COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS state() const { return state_; }
75 |
76 | private:
77 | COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS state_ =
78 | COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS::
79 | COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_NONE;
80 |
81 | inline void set(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS key, bool flag) {
82 | if (flag) {
83 | state_ |= key;
84 | } else {
85 | state_ &= ~key;
86 | }
87 | }
88 | };
89 |
90 | struct EventRegistrations {
91 | EventRegistrationToken source_changed_token_{};
92 | EventRegistrationToken content_loading_token_{};
93 | EventRegistrationToken navigation_completed_token_{};
94 | EventRegistrationToken history_changed_token_{};
95 | EventRegistrationToken document_title_changed_token_{};
96 | EventRegistrationToken cursor_changed_token_{};
97 | EventRegistrationToken got_focus_token_{};
98 | EventRegistrationToken lost_focus_token_{};
99 | EventRegistrationToken web_message_received_token_{};
100 | EventRegistrationToken permission_requested_token_{};
101 | EventRegistrationToken devtools_protocol_event_token_{};
102 | EventRegistrationToken new_windows_requested_token_{};
103 | EventRegistrationToken contains_fullscreen_element_changed_token_{};
104 | EventRegistrationToken download_starting_token_{};
105 | EventRegistrationToken download_bytes_received_token_{};
106 | EventRegistrationToken download_state_changed_token_{};
107 | };
108 |
109 | class Webview {
110 | public:
111 | friend class WebviewHost;
112 |
113 | typedef std::function UrlChangedCallback;
114 | typedef std::function LoadingStateChangedCallback;
115 | typedef std::function
116 | OnLoadErrorCallback;
117 | typedef std::function HistoryChangedCallback;
118 | typedef std::function DevtoolsProtocolEventCallback;
119 | typedef std::function DocumentTitleChangedCallback;
120 | typedef std::function
121 | SurfaceSizeChangedCallback;
122 | typedef std::function CursorChangedCallback;
123 | typedef std::function FocusChangedCallback;
124 | typedef std::function
125 | AddScriptToExecuteOnDocumentCreatedCallback;
126 | typedef std::function ScriptExecutedCallback;
127 | typedef std::function