@yufengtadian/freedom-cli 1.12.1 → 1.12.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -14
- package/bin/freedom.js +5 -1
- package/lib/build.js +49 -8
- package/lib/cli.js +107 -80
- package/lib/config.js +108 -31
- package/lib/shell.js +73 -20
- package/lib/theme.js +105 -0
- package/lib/tui.js +21 -3
- package/lib/update.js +109 -0
- package/lib/utils.js +14 -1
- package/package.json +1 -1
- package/shell/win-x64/freedom-shell.exe +0 -0
- package/templates/go/go.mod +6 -0
- package/templates/go/go.sum +2 -2
- package/templates/go/pkg/freedom/assets/freedom.js +6 -0
- package/templates/go/pkg/freedom/assets/index.html +1 -1
- package/templates/go/pkg/freedom/backend_proc.go +4 -4
- package/templates/go/pkg/freedom/configfile.go +12 -7
- package/templates/go/pkg/freedom/freedom.go +27 -11
- package/templates/go/pkg/freedom/window_other.go +64 -12
- package/templates/go/pkg/freedom/window_windows.go +244 -29
- package/templates/go/webview_go/.github/workflows/ci.yaml +62 -0
- package/templates/go/webview_go/CHANGELOG.md +15 -0
- package/templates/go/webview_go/LICENSE +22 -0
- package/templates/go/webview_go/README.md +50 -0
- package/templates/go/webview_go/examples/basic/main.go +12 -0
- package/templates/go/webview_go/examples/bind/main.go +38 -0
- package/templates/go/webview_go/glue.c +36 -0
- package/templates/go/webview_go/go.mod +3 -0
- package/templates/go/webview_go/go.sum +0 -0
- package/templates/go/webview_go/libs/mswebview2/LICENSE +27 -0
- package/templates/go/webview_go/libs/mswebview2/include/WebView2.h +23568 -0
- package/templates/go/webview_go/libs/mswebview2/include/vendor.go +2 -0
- package/templates/go/webview_go/libs/mswebview2/vendor.go +2 -0
- package/templates/go/webview_go/libs/mswebview2/version.txt +1 -0
- package/templates/go/webview_go/libs/webview/LICENSE +22 -0
- package/templates/go/webview_go/libs/webview/include/vendor.go +2 -0
- package/templates/go/webview_go/libs/webview/include/webview.h +3871 -0
- package/templates/go/webview_go/libs/webview/vendor.go +2 -0
- package/templates/go/webview_go/libs/webview/version.txt +1 -0
- package/templates/go/webview_go/webview.cc +1 -0
- package/templates/go/webview_go/webview.go +379 -0
- package/templates/go/webview_go/webview_test.go +50 -0
- package/templates/project/freedom.config.js +5 -5
- package/templates/project/index.html +45 -5
- package/templates/project/src/main.js +149 -8
- package/tutorial/tutorial.html +4 -5
|
@@ -0,0 +1,3871 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* MIT License
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2017 Serge Zaitsev
|
|
5
|
+
* Copyright (c) 2022 Steffen André Langnes
|
|
6
|
+
*
|
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
*
|
|
14
|
+
* The above copyright notice and this permission notice shall be included in
|
|
15
|
+
* all copies or substantial portions of the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
* SOFTWARE.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/// @file webview.h
|
|
27
|
+
|
|
28
|
+
#ifndef WEBVIEW_H
|
|
29
|
+
#define WEBVIEW_H
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Used to specify function linkage such as extern, inline, etc.
|
|
33
|
+
*
|
|
34
|
+
* When @c WEBVIEW_API is not already defined, the defaults are as follows:
|
|
35
|
+
*
|
|
36
|
+
* - @c inline when compiling C++ code.
|
|
37
|
+
* - @c extern when compiling C code.
|
|
38
|
+
*
|
|
39
|
+
* The following macros can be used to automatically set an appropriate
|
|
40
|
+
* value for @c WEBVIEW_API:
|
|
41
|
+
*
|
|
42
|
+
* - Define @c WEBVIEW_BUILD_SHARED when building a shared library.
|
|
43
|
+
* - Define @c WEBVIEW_SHARED when using a shared library.
|
|
44
|
+
* - Define @c WEBVIEW_STATIC when building or using a static library.
|
|
45
|
+
*/
|
|
46
|
+
#ifndef WEBVIEW_API
|
|
47
|
+
#if defined(WEBVIEW_SHARED) || defined(WEBVIEW_BUILD_SHARED)
|
|
48
|
+
#if defined(_WIN32) || defined(__CYGWIN__)
|
|
49
|
+
#if defined(WEBVIEW_BUILD_SHARED)
|
|
50
|
+
#define WEBVIEW_API __declspec(dllexport)
|
|
51
|
+
#else
|
|
52
|
+
#define WEBVIEW_API __declspec(dllimport)
|
|
53
|
+
#endif
|
|
54
|
+
#else
|
|
55
|
+
#define WEBVIEW_API __attribute__((visibility("default")))
|
|
56
|
+
#endif
|
|
57
|
+
#elif !defined(WEBVIEW_STATIC) && defined(__cplusplus)
|
|
58
|
+
#define WEBVIEW_API inline
|
|
59
|
+
#else
|
|
60
|
+
#define WEBVIEW_API extern
|
|
61
|
+
#endif
|
|
62
|
+
#endif
|
|
63
|
+
|
|
64
|
+
/// @name Version
|
|
65
|
+
/// @{
|
|
66
|
+
|
|
67
|
+
#ifndef WEBVIEW_VERSION_MAJOR
|
|
68
|
+
/// The current library major version.
|
|
69
|
+
#define WEBVIEW_VERSION_MAJOR 0
|
|
70
|
+
#endif
|
|
71
|
+
|
|
72
|
+
#ifndef WEBVIEW_VERSION_MINOR
|
|
73
|
+
/// The current library minor version.
|
|
74
|
+
#define WEBVIEW_VERSION_MINOR 11
|
|
75
|
+
#endif
|
|
76
|
+
|
|
77
|
+
#ifndef WEBVIEW_VERSION_PATCH
|
|
78
|
+
/// The current library patch version.
|
|
79
|
+
#define WEBVIEW_VERSION_PATCH 0
|
|
80
|
+
#endif
|
|
81
|
+
|
|
82
|
+
#ifndef WEBVIEW_VERSION_PRE_RELEASE
|
|
83
|
+
/// SemVer 2.0.0 pre-release labels prefixed with "-".
|
|
84
|
+
#define WEBVIEW_VERSION_PRE_RELEASE ""
|
|
85
|
+
#endif
|
|
86
|
+
|
|
87
|
+
#ifndef WEBVIEW_VERSION_BUILD_METADATA
|
|
88
|
+
/// SemVer 2.0.0 build metadata prefixed with "+".
|
|
89
|
+
#define WEBVIEW_VERSION_BUILD_METADATA ""
|
|
90
|
+
#endif
|
|
91
|
+
|
|
92
|
+
/// @}
|
|
93
|
+
|
|
94
|
+
/// @name Used internally
|
|
95
|
+
/// @{
|
|
96
|
+
|
|
97
|
+
/// Utility macro for stringifying a macro argument.
|
|
98
|
+
#define WEBVIEW_STRINGIFY(x) #x
|
|
99
|
+
|
|
100
|
+
/// Utility macro for stringifying the result of a macro argument expansion.
|
|
101
|
+
#define WEBVIEW_EXPAND_AND_STRINGIFY(x) WEBVIEW_STRINGIFY(x)
|
|
102
|
+
|
|
103
|
+
/// @}
|
|
104
|
+
|
|
105
|
+
/// @name Version
|
|
106
|
+
/// @{
|
|
107
|
+
|
|
108
|
+
/// SemVer 2.0.0 version number in MAJOR.MINOR.PATCH format.
|
|
109
|
+
#define WEBVIEW_VERSION_NUMBER \
|
|
110
|
+
WEBVIEW_EXPAND_AND_STRINGIFY(WEBVIEW_VERSION_MAJOR) \
|
|
111
|
+
"." WEBVIEW_EXPAND_AND_STRINGIFY( \
|
|
112
|
+
WEBVIEW_VERSION_MINOR) "." WEBVIEW_EXPAND_AND_STRINGIFY(WEBVIEW_VERSION_PATCH)
|
|
113
|
+
|
|
114
|
+
/// @}
|
|
115
|
+
|
|
116
|
+
/// Holds the elements of a MAJOR.MINOR.PATCH version number.
|
|
117
|
+
typedef struct {
|
|
118
|
+
/// Major version.
|
|
119
|
+
unsigned int major;
|
|
120
|
+
/// Minor version.
|
|
121
|
+
unsigned int minor;
|
|
122
|
+
/// Patch version.
|
|
123
|
+
unsigned int patch;
|
|
124
|
+
} webview_version_t;
|
|
125
|
+
|
|
126
|
+
/// Holds the library's version information.
|
|
127
|
+
typedef struct {
|
|
128
|
+
/// The elements of the version number.
|
|
129
|
+
webview_version_t version;
|
|
130
|
+
/// SemVer 2.0.0 version number in MAJOR.MINOR.PATCH format.
|
|
131
|
+
char version_number[32];
|
|
132
|
+
/// SemVer 2.0.0 pre-release labels prefixed with "-" if specified, otherwise
|
|
133
|
+
/// an empty string.
|
|
134
|
+
char pre_release[48];
|
|
135
|
+
/// SemVer 2.0.0 build metadata prefixed with "+", otherwise an empty string.
|
|
136
|
+
char build_metadata[48];
|
|
137
|
+
} webview_version_info_t;
|
|
138
|
+
|
|
139
|
+
/// Pointer to a webview instance.
|
|
140
|
+
typedef void *webview_t;
|
|
141
|
+
|
|
142
|
+
/// Native handle kind. The actual type depends on the backend.
|
|
143
|
+
typedef enum {
|
|
144
|
+
/// Top-level window. @c GtkWindow pointer (GTK), @c NSWindow pointer (Cocoa)
|
|
145
|
+
/// or @c HWND (Win32).
|
|
146
|
+
WEBVIEW_NATIVE_HANDLE_KIND_UI_WINDOW,
|
|
147
|
+
/// Browser widget. @c GtkWidget pointer (GTK), @c NSView pointer (Cocoa) or
|
|
148
|
+
/// @c HWND (Win32).
|
|
149
|
+
WEBVIEW_NATIVE_HANDLE_KIND_UI_WIDGET,
|
|
150
|
+
/// Browser controller. @c WebKitWebView pointer (WebKitGTK), @c WKWebView
|
|
151
|
+
/// pointer (Cocoa/WebKit) or @c ICoreWebView2Controller pointer
|
|
152
|
+
/// (Win32/WebView2).
|
|
153
|
+
WEBVIEW_NATIVE_HANDLE_KIND_BROWSER_CONTROLLER
|
|
154
|
+
} webview_native_handle_kind_t;
|
|
155
|
+
|
|
156
|
+
/// Window size hints
|
|
157
|
+
typedef enum {
|
|
158
|
+
/// Width and height are default size.
|
|
159
|
+
WEBVIEW_HINT_NONE,
|
|
160
|
+
/// Width and height are minimum bounds.
|
|
161
|
+
WEBVIEW_HINT_MIN,
|
|
162
|
+
/// Width and height are maximum bounds.
|
|
163
|
+
WEBVIEW_HINT_MAX,
|
|
164
|
+
/// Window size can not be changed by a user.
|
|
165
|
+
WEBVIEW_HINT_FIXED
|
|
166
|
+
} webview_hint_t;
|
|
167
|
+
|
|
168
|
+
#ifdef __cplusplus
|
|
169
|
+
extern "C" {
|
|
170
|
+
#endif
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Creates a new webview instance.
|
|
174
|
+
*
|
|
175
|
+
* @param debug Enable developer tools if supported by the backend.
|
|
176
|
+
* @param window Optional native window handle, i.e. @c GtkWindow pointer
|
|
177
|
+
* @c NSWindow pointer (Cocoa) or @c HWND (Win32). If non-null,
|
|
178
|
+
* the webview widget is embedded into the given window, and the
|
|
179
|
+
* caller is expected to assume responsibility for the window as
|
|
180
|
+
* well as application lifecycle. If the window handle is null,
|
|
181
|
+
* a new window is created and both the window and application
|
|
182
|
+
* lifecycle are managed by the webview instance.
|
|
183
|
+
* @remark Win32: The function also accepts a pointer to @c HWND (Win32) in the
|
|
184
|
+
* window parameter for backward compatibility.
|
|
185
|
+
* @remark Win32/WebView2: @c CoInitializeEx should be called with
|
|
186
|
+
* @c COINIT_APARTMENTTHREADED before attempting to call this function
|
|
187
|
+
* with an existing window. Omitting this step may cause WebView2
|
|
188
|
+
* initialization to fail.
|
|
189
|
+
* @return @c NULL on failure. Creation can fail for various reasons such
|
|
190
|
+
* as when required runtime dependencies are missing or when window
|
|
191
|
+
* creation fails.
|
|
192
|
+
*/
|
|
193
|
+
WEBVIEW_API webview_t webview_create(int debug, void *window);
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Destroys a webview instance and closes the native window.
|
|
197
|
+
*
|
|
198
|
+
* @param w The webview instance.
|
|
199
|
+
*/
|
|
200
|
+
WEBVIEW_API void webview_destroy(webview_t w);
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Runs the main loop until it's terminated.
|
|
204
|
+
*
|
|
205
|
+
* @param w The webview instance.
|
|
206
|
+
*/
|
|
207
|
+
WEBVIEW_API void webview_run(webview_t w);
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Stops the main loop. It is safe to call this function from another other
|
|
211
|
+
* background thread.
|
|
212
|
+
*
|
|
213
|
+
* @param w The webview instance.
|
|
214
|
+
*/
|
|
215
|
+
WEBVIEW_API void webview_terminate(webview_t w);
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Schedules a function to be invoked on the thread with the run/event loop.
|
|
219
|
+
* Use this function e.g. to interact with the library or native handles.
|
|
220
|
+
*
|
|
221
|
+
* @param w The webview instance.
|
|
222
|
+
* @param fn The function to be invoked.
|
|
223
|
+
* @param arg An optional argument passed along to the callback function.
|
|
224
|
+
*/
|
|
225
|
+
WEBVIEW_API void
|
|
226
|
+
webview_dispatch(webview_t w, void (*fn)(webview_t w, void *arg), void *arg);
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Returns the native handle of the window associated with the webview instance.
|
|
230
|
+
* The handle can be a @c GtkWindow pointer (GTK), @c NSWindow pointer (Cocoa)
|
|
231
|
+
* or @c HWND (Win32).
|
|
232
|
+
*
|
|
233
|
+
* @param w The webview instance.
|
|
234
|
+
* @return The handle of the native window.
|
|
235
|
+
*/
|
|
236
|
+
WEBVIEW_API void *webview_get_window(webview_t w);
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get a native handle of choice.
|
|
240
|
+
*
|
|
241
|
+
* @param w The webview instance.
|
|
242
|
+
* @param kind The kind of handle to retrieve.
|
|
243
|
+
* @return The native handle or @c NULL.
|
|
244
|
+
* @since 0.11
|
|
245
|
+
*/
|
|
246
|
+
WEBVIEW_API void *webview_get_native_handle(webview_t w,
|
|
247
|
+
webview_native_handle_kind_t kind);
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Updates the title of the native window.
|
|
251
|
+
*
|
|
252
|
+
* @param w The webview instance.
|
|
253
|
+
* @param title The new title.
|
|
254
|
+
*/
|
|
255
|
+
WEBVIEW_API void webview_set_title(webview_t w, const char *title);
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Updates the size of the native window.
|
|
259
|
+
*
|
|
260
|
+
* @param w The webview instance.
|
|
261
|
+
* @param width New width.
|
|
262
|
+
* @param height New height.
|
|
263
|
+
* @param hints Size hints.
|
|
264
|
+
*/
|
|
265
|
+
WEBVIEW_API void webview_set_size(webview_t w, int width, int height,
|
|
266
|
+
webview_hint_t hints);
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Controls whether the native window shows its system title bar decoration.
|
|
270
|
+
* Used by frameless mode (front-end draws its own min/max/close buttons).
|
|
271
|
+
*
|
|
272
|
+
* @param w The webview instance.
|
|
273
|
+
* @param decorated Non-zero to show the native title bar, zero for frameless.
|
|
274
|
+
*/
|
|
275
|
+
WEBVIEW_API void webview_set_decorated(webview_t w, int decorated);
|
|
276
|
+
|
|
277
|
+
/** Native window control actions (front-end custom title bar buttons). */
|
|
278
|
+
typedef enum webview_window_action {
|
|
279
|
+
WEBVIEW_WINDOW_ACTION_MINIMIZE = 0,
|
|
280
|
+
WEBVIEW_WINDOW_ACTION_MAXIMIZE = 1,
|
|
281
|
+
WEBVIEW_WINDOW_ACTION_UNMAXIMIZE = 2,
|
|
282
|
+
WEBVIEW_WINDOW_ACTION_RESTORE = 3,
|
|
283
|
+
WEBVIEW_WINDOW_ACTION_CLOSE = 4,
|
|
284
|
+
WEBVIEW_WINDOW_ACTION_TOGGLE_MAXIMIZE = 5,
|
|
285
|
+
WEBVIEW_WINDOW_ACTION_IS_MAXIMIZED = 6
|
|
286
|
+
} webview_window_action_t;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Executes a native window control action (minimize/maximize/restore/close).
|
|
290
|
+
* Returns 0 on success, -1 if unsupported. For IS_MAXIMIZED returns 1/0.
|
|
291
|
+
*
|
|
292
|
+
* @param w The webview instance.
|
|
293
|
+
* @param action The action to execute (see webview_window_action_t).
|
|
294
|
+
*/
|
|
295
|
+
WEBVIEW_API int webview_window_control(webview_t w, webview_window_action_t action);
|
|
296
|
+
WEBVIEW_API int webview_window_begin_move_drag(webview_t w);
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Navigates webview to the given URL. URL may be a properly encoded data URI.
|
|
300
|
+
*
|
|
301
|
+
* Example:
|
|
302
|
+
* @code{.c}
|
|
303
|
+
* webview_navigate(w, "https://github.com/webview/webview");
|
|
304
|
+
* webview_navigate(w, "data:text/html,%3Ch1%3EHello%3C%2Fh1%3E");
|
|
305
|
+
* webview_navigate(w, "data:text/html;base64,PGgxPkhlbGxvPC9oMT4=");
|
|
306
|
+
* @endcode
|
|
307
|
+
*
|
|
308
|
+
* @param w The webview instance.
|
|
309
|
+
* @param url URL.
|
|
310
|
+
*/
|
|
311
|
+
WEBVIEW_API void webview_navigate(webview_t w, const char *url);
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Load HTML content into the webview.
|
|
315
|
+
*
|
|
316
|
+
* Example:
|
|
317
|
+
* @code{.c}
|
|
318
|
+
* webview_set_html(w, "<h1>Hello</h1>");
|
|
319
|
+
* @endcode
|
|
320
|
+
*
|
|
321
|
+
* @param w The webview instance.
|
|
322
|
+
* @param html HTML content.
|
|
323
|
+
*/
|
|
324
|
+
WEBVIEW_API void webview_set_html(webview_t w, const char *html);
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Injects JavaScript code to be executed immediately upon loading a page.
|
|
328
|
+
* The code will be executed before @c window.onload.
|
|
329
|
+
*
|
|
330
|
+
* @param w The webview instance.
|
|
331
|
+
* @param js JS content.
|
|
332
|
+
*/
|
|
333
|
+
WEBVIEW_API void webview_init(webview_t w, const char *js);
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Evaluates arbitrary JavaScript code.
|
|
337
|
+
*
|
|
338
|
+
* Use bindings if you need to communicate the result of the evaluation.
|
|
339
|
+
*
|
|
340
|
+
* @param w The webview instance.
|
|
341
|
+
* @param js JS content.
|
|
342
|
+
*/
|
|
343
|
+
WEBVIEW_API void webview_eval(webview_t w, const char *js);
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Binds a function pointer to a new global JavaScript function.
|
|
347
|
+
*
|
|
348
|
+
* Internally, JS glue code is injected to create the JS function by the
|
|
349
|
+
* given name. The callback function is passed a sequential request
|
|
350
|
+
* identifier, a request string and a user-provided argument. The request
|
|
351
|
+
* string is a JSON array of the arguments passed to the JS function.
|
|
352
|
+
*
|
|
353
|
+
* @param w The webview instance.
|
|
354
|
+
* @param name Name of the JS function.
|
|
355
|
+
* @param fn Callback function.
|
|
356
|
+
* @param arg User argument.
|
|
357
|
+
*/
|
|
358
|
+
WEBVIEW_API void webview_bind(webview_t w, const char *name,
|
|
359
|
+
void (*fn)(const char *seq, const char *req,
|
|
360
|
+
void *arg),
|
|
361
|
+
void *arg);
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Removes a binding created with webview_bind().
|
|
365
|
+
*
|
|
366
|
+
* @param w The webview instance.
|
|
367
|
+
* @param name Name of the binding.
|
|
368
|
+
*/
|
|
369
|
+
WEBVIEW_API void webview_unbind(webview_t w, const char *name);
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Responds to a binding call from the JS side.
|
|
373
|
+
*
|
|
374
|
+
* @param w The webview instance.
|
|
375
|
+
* @param seq The sequence number of the binding call. Pass along the value
|
|
376
|
+
* received in the binding handler (see webview_bind()).
|
|
377
|
+
* @param status A status of zero tells the JS side that the binding call was
|
|
378
|
+
* succesful; any other value indicates an error.
|
|
379
|
+
* @param result The result of the binding call to be returned to the JS side.
|
|
380
|
+
* This must either be a valid JSON value or an empty string for
|
|
381
|
+
* the primitive JS value @c undefined.
|
|
382
|
+
*/
|
|
383
|
+
WEBVIEW_API void webview_return(webview_t w, const char *seq, int status,
|
|
384
|
+
const char *result);
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Get the library's version information.
|
|
388
|
+
*
|
|
389
|
+
* @since 0.10
|
|
390
|
+
*/
|
|
391
|
+
WEBVIEW_API const webview_version_info_t *webview_version(void);
|
|
392
|
+
|
|
393
|
+
#ifdef __cplusplus
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
#ifndef WEBVIEW_HEADER
|
|
397
|
+
|
|
398
|
+
#if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) && !defined(WEBVIEW_EDGE)
|
|
399
|
+
#if defined(__APPLE__)
|
|
400
|
+
#define WEBVIEW_COCOA
|
|
401
|
+
#elif defined(__unix__)
|
|
402
|
+
#define WEBVIEW_GTK
|
|
403
|
+
#elif defined(_WIN32)
|
|
404
|
+
#define WEBVIEW_EDGE
|
|
405
|
+
#else
|
|
406
|
+
#error "please, specify webview backend"
|
|
407
|
+
#endif
|
|
408
|
+
#endif
|
|
409
|
+
|
|
410
|
+
#ifndef WEBVIEW_DEPRECATED
|
|
411
|
+
#if __cplusplus >= 201402L
|
|
412
|
+
#define WEBVIEW_DEPRECATED(reason) [[deprecated(reason)]]
|
|
413
|
+
#elif defined(_MSC_VER)
|
|
414
|
+
#define WEBVIEW_DEPRECATED(reason) __declspec(deprecated(reason))
|
|
415
|
+
#else
|
|
416
|
+
#define WEBVIEW_DEPRECATED(reason) __attribute__((deprecated(reason)))
|
|
417
|
+
#endif
|
|
418
|
+
#endif
|
|
419
|
+
|
|
420
|
+
#ifndef WEBVIEW_DEPRECATED_PRIVATE
|
|
421
|
+
#define WEBVIEW_DEPRECATED_PRIVATE \
|
|
422
|
+
WEBVIEW_DEPRECATED("Private API should not be used")
|
|
423
|
+
#endif
|
|
424
|
+
|
|
425
|
+
#include <algorithm>
|
|
426
|
+
#include <array>
|
|
427
|
+
#include <atomic>
|
|
428
|
+
#include <cassert>
|
|
429
|
+
#include <cstdint>
|
|
430
|
+
#include <functional>
|
|
431
|
+
#include <future>
|
|
432
|
+
#include <map>
|
|
433
|
+
#include <string>
|
|
434
|
+
#include <utility>
|
|
435
|
+
#include <vector>
|
|
436
|
+
|
|
437
|
+
#include <cstring>
|
|
438
|
+
|
|
439
|
+
#if defined(_WIN32)
|
|
440
|
+
#define WIN32_LEAN_AND_MEAN
|
|
441
|
+
#include <windows.h>
|
|
442
|
+
#else
|
|
443
|
+
#include <dlfcn.h>
|
|
444
|
+
#endif
|
|
445
|
+
|
|
446
|
+
namespace webview {
|
|
447
|
+
|
|
448
|
+
using dispatch_fn_t = std::function<void()>;
|
|
449
|
+
|
|
450
|
+
namespace detail {
|
|
451
|
+
|
|
452
|
+
// The library's version information.
|
|
453
|
+
constexpr const webview_version_info_t library_version_info{
|
|
454
|
+
{WEBVIEW_VERSION_MAJOR, WEBVIEW_VERSION_MINOR, WEBVIEW_VERSION_PATCH},
|
|
455
|
+
WEBVIEW_VERSION_NUMBER,
|
|
456
|
+
WEBVIEW_VERSION_PRE_RELEASE,
|
|
457
|
+
WEBVIEW_VERSION_BUILD_METADATA};
|
|
458
|
+
|
|
459
|
+
#if defined(_WIN32)
|
|
460
|
+
// Converts a narrow (UTF-8-encoded) string into a wide (UTF-16-encoded) string.
|
|
461
|
+
inline std::wstring widen_string(const std::string &input) {
|
|
462
|
+
if (input.empty()) {
|
|
463
|
+
return std::wstring();
|
|
464
|
+
}
|
|
465
|
+
UINT cp = CP_UTF8;
|
|
466
|
+
DWORD flags = MB_ERR_INVALID_CHARS;
|
|
467
|
+
auto input_c = input.c_str();
|
|
468
|
+
auto input_length = static_cast<int>(input.size());
|
|
469
|
+
auto required_length =
|
|
470
|
+
MultiByteToWideChar(cp, flags, input_c, input_length, nullptr, 0);
|
|
471
|
+
if (required_length > 0) {
|
|
472
|
+
std::wstring output(static_cast<std::size_t>(required_length), L'\0');
|
|
473
|
+
if (MultiByteToWideChar(cp, flags, input_c, input_length, &output[0],
|
|
474
|
+
required_length) > 0) {
|
|
475
|
+
return output;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// Failed to convert string from UTF-8 to UTF-16
|
|
479
|
+
return std::wstring();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Converts a wide (UTF-16-encoded) string into a narrow (UTF-8-encoded) string.
|
|
483
|
+
inline std::string narrow_string(const std::wstring &input) {
|
|
484
|
+
struct wc_flags {
|
|
485
|
+
enum TYPE : unsigned int {
|
|
486
|
+
// WC_ERR_INVALID_CHARS
|
|
487
|
+
err_invalid_chars = 0x00000080U
|
|
488
|
+
};
|
|
489
|
+
};
|
|
490
|
+
if (input.empty()) {
|
|
491
|
+
return std::string();
|
|
492
|
+
}
|
|
493
|
+
UINT cp = CP_UTF8;
|
|
494
|
+
DWORD flags = wc_flags::err_invalid_chars;
|
|
495
|
+
auto input_c = input.c_str();
|
|
496
|
+
auto input_length = static_cast<int>(input.size());
|
|
497
|
+
auto required_length = WideCharToMultiByte(cp, flags, input_c, input_length,
|
|
498
|
+
nullptr, 0, nullptr, nullptr);
|
|
499
|
+
if (required_length > 0) {
|
|
500
|
+
std::string output(static_cast<std::size_t>(required_length), '\0');
|
|
501
|
+
if (WideCharToMultiByte(cp, flags, input_c, input_length, &output[0],
|
|
502
|
+
required_length, nullptr, nullptr) > 0) {
|
|
503
|
+
return output;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// Failed to convert string from UTF-16 to UTF-8
|
|
507
|
+
return std::string();
|
|
508
|
+
}
|
|
509
|
+
#endif
|
|
510
|
+
|
|
511
|
+
inline int json_parse_c(const char *s, size_t sz, const char *key, size_t keysz,
|
|
512
|
+
const char **value, size_t *valuesz) {
|
|
513
|
+
enum {
|
|
514
|
+
JSON_STATE_VALUE,
|
|
515
|
+
JSON_STATE_LITERAL,
|
|
516
|
+
JSON_STATE_STRING,
|
|
517
|
+
JSON_STATE_ESCAPE,
|
|
518
|
+
JSON_STATE_UTF8
|
|
519
|
+
} state = JSON_STATE_VALUE;
|
|
520
|
+
const char *k = nullptr;
|
|
521
|
+
int index = 1;
|
|
522
|
+
int depth = 0;
|
|
523
|
+
int utf8_bytes = 0;
|
|
524
|
+
|
|
525
|
+
*value = nullptr;
|
|
526
|
+
*valuesz = 0;
|
|
527
|
+
|
|
528
|
+
if (key == nullptr) {
|
|
529
|
+
index = static_cast<decltype(index)>(keysz);
|
|
530
|
+
if (index < 0) {
|
|
531
|
+
return -1;
|
|
532
|
+
}
|
|
533
|
+
keysz = 0;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
for (; sz > 0; s++, sz--) {
|
|
537
|
+
enum {
|
|
538
|
+
JSON_ACTION_NONE,
|
|
539
|
+
JSON_ACTION_START,
|
|
540
|
+
JSON_ACTION_END,
|
|
541
|
+
JSON_ACTION_START_STRUCT,
|
|
542
|
+
JSON_ACTION_END_STRUCT
|
|
543
|
+
} action = JSON_ACTION_NONE;
|
|
544
|
+
auto c = static_cast<unsigned char>(*s);
|
|
545
|
+
switch (state) {
|
|
546
|
+
case JSON_STATE_VALUE:
|
|
547
|
+
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
|
|
548
|
+
c == ':') {
|
|
549
|
+
continue;
|
|
550
|
+
} else if (c == '"') {
|
|
551
|
+
action = JSON_ACTION_START;
|
|
552
|
+
state = JSON_STATE_STRING;
|
|
553
|
+
} else if (c == '{' || c == '[') {
|
|
554
|
+
action = JSON_ACTION_START_STRUCT;
|
|
555
|
+
} else if (c == '}' || c == ']') {
|
|
556
|
+
action = JSON_ACTION_END_STRUCT;
|
|
557
|
+
} else if (c == 't' || c == 'f' || c == 'n' || c == '-' ||
|
|
558
|
+
(c >= '0' && c <= '9')) {
|
|
559
|
+
action = JSON_ACTION_START;
|
|
560
|
+
state = JSON_STATE_LITERAL;
|
|
561
|
+
} else {
|
|
562
|
+
return -1;
|
|
563
|
+
}
|
|
564
|
+
break;
|
|
565
|
+
case JSON_STATE_LITERAL:
|
|
566
|
+
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
|
|
567
|
+
c == ']' || c == '}' || c == ':') {
|
|
568
|
+
state = JSON_STATE_VALUE;
|
|
569
|
+
s--;
|
|
570
|
+
sz++;
|
|
571
|
+
action = JSON_ACTION_END;
|
|
572
|
+
} else if (c < 32 || c > 126) {
|
|
573
|
+
return -1;
|
|
574
|
+
} // fallthrough
|
|
575
|
+
case JSON_STATE_STRING:
|
|
576
|
+
if (c < 32 || (c > 126 && c < 192)) {
|
|
577
|
+
return -1;
|
|
578
|
+
} else if (c == '"') {
|
|
579
|
+
action = JSON_ACTION_END;
|
|
580
|
+
state = JSON_STATE_VALUE;
|
|
581
|
+
} else if (c == '\\') {
|
|
582
|
+
state = JSON_STATE_ESCAPE;
|
|
583
|
+
} else if (c >= 192 && c < 224) {
|
|
584
|
+
utf8_bytes = 1;
|
|
585
|
+
state = JSON_STATE_UTF8;
|
|
586
|
+
} else if (c >= 224 && c < 240) {
|
|
587
|
+
utf8_bytes = 2;
|
|
588
|
+
state = JSON_STATE_UTF8;
|
|
589
|
+
} else if (c >= 240 && c < 247) {
|
|
590
|
+
utf8_bytes = 3;
|
|
591
|
+
state = JSON_STATE_UTF8;
|
|
592
|
+
} else if (c >= 128 && c < 192) {
|
|
593
|
+
return -1;
|
|
594
|
+
}
|
|
595
|
+
break;
|
|
596
|
+
case JSON_STATE_ESCAPE:
|
|
597
|
+
if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' ||
|
|
598
|
+
c == 'n' || c == 'r' || c == 't' || c == 'u') {
|
|
599
|
+
state = JSON_STATE_STRING;
|
|
600
|
+
} else {
|
|
601
|
+
return -1;
|
|
602
|
+
}
|
|
603
|
+
break;
|
|
604
|
+
case JSON_STATE_UTF8:
|
|
605
|
+
if (c < 128 || c > 191) {
|
|
606
|
+
return -1;
|
|
607
|
+
}
|
|
608
|
+
utf8_bytes--;
|
|
609
|
+
if (utf8_bytes == 0) {
|
|
610
|
+
state = JSON_STATE_STRING;
|
|
611
|
+
}
|
|
612
|
+
break;
|
|
613
|
+
default:
|
|
614
|
+
return -1;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (action == JSON_ACTION_END_STRUCT) {
|
|
618
|
+
depth--;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (depth == 1) {
|
|
622
|
+
if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT) {
|
|
623
|
+
if (index == 0) {
|
|
624
|
+
*value = s;
|
|
625
|
+
} else if (keysz > 0 && index == 1) {
|
|
626
|
+
k = s;
|
|
627
|
+
} else {
|
|
628
|
+
index--;
|
|
629
|
+
}
|
|
630
|
+
} else if (action == JSON_ACTION_END ||
|
|
631
|
+
action == JSON_ACTION_END_STRUCT) {
|
|
632
|
+
if (*value != nullptr && index == 0) {
|
|
633
|
+
*valuesz = (size_t)(s + 1 - *value);
|
|
634
|
+
return 0;
|
|
635
|
+
} else if (keysz > 0 && k != nullptr) {
|
|
636
|
+
if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0) {
|
|
637
|
+
index = 0;
|
|
638
|
+
} else {
|
|
639
|
+
index = 2;
|
|
640
|
+
}
|
|
641
|
+
k = nullptr;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (action == JSON_ACTION_START_STRUCT) {
|
|
647
|
+
depth++;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return -1;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
constexpr bool is_json_special_char(char c) {
|
|
654
|
+
return c == '"' || c == '\\' || c == '\b' || c == '\f' || c == '\n' ||
|
|
655
|
+
c == '\r' || c == '\t';
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
constexpr bool is_ascii_control_char(char c) { return c >= 0 && c <= 0x1f; }
|
|
659
|
+
|
|
660
|
+
inline std::string json_escape(const std::string &s, bool add_quotes = true) {
|
|
661
|
+
// Calculate the size of the resulting string.
|
|
662
|
+
// Add space for the double quotes.
|
|
663
|
+
size_t required_length = add_quotes ? 2 : 0;
|
|
664
|
+
for (auto c : s) {
|
|
665
|
+
if (is_json_special_char(c)) {
|
|
666
|
+
// '\' and a single following character
|
|
667
|
+
required_length += 2;
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
if (is_ascii_control_char(c)) {
|
|
671
|
+
// '\', 'u', 4 digits
|
|
672
|
+
required_length += 6;
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
++required_length;
|
|
676
|
+
}
|
|
677
|
+
// Allocate memory for resulting string only once.
|
|
678
|
+
std::string result;
|
|
679
|
+
result.reserve(required_length);
|
|
680
|
+
if (add_quotes) {
|
|
681
|
+
result += '"';
|
|
682
|
+
}
|
|
683
|
+
// Copy string while escaping characters.
|
|
684
|
+
for (auto c : s) {
|
|
685
|
+
if (is_json_special_char(c)) {
|
|
686
|
+
static constexpr char special_escape_table[256] =
|
|
687
|
+
"\0\0\0\0\0\0\0\0btn\0fr\0\0"
|
|
688
|
+
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
|
689
|
+
"\0\0\"\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
|
690
|
+
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
|
691
|
+
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
|
692
|
+
"\0\0\0\0\0\0\0\0\0\0\0\0\\";
|
|
693
|
+
result += '\\';
|
|
694
|
+
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
|
|
695
|
+
result += special_escape_table[static_cast<unsigned char>(c)];
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
if (is_ascii_control_char(c)) {
|
|
699
|
+
// Escape as \u00xx
|
|
700
|
+
static constexpr char hex_alphabet[]{"0123456789abcdef"};
|
|
701
|
+
auto uc = static_cast<unsigned char>(c);
|
|
702
|
+
auto h = (uc >> 4) & 0x0f;
|
|
703
|
+
auto l = uc & 0x0f;
|
|
704
|
+
result += "\\u00";
|
|
705
|
+
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-constant-array-index)
|
|
706
|
+
result += hex_alphabet[h];
|
|
707
|
+
result += hex_alphabet[l];
|
|
708
|
+
// NOLINTEND(cppcoreguidelines-pro-bounds-constant-array-index)
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
result += c;
|
|
712
|
+
}
|
|
713
|
+
if (add_quotes) {
|
|
714
|
+
result += '"';
|
|
715
|
+
}
|
|
716
|
+
// Should have calculated the exact amount of memory needed
|
|
717
|
+
assert(required_length == result.size());
|
|
718
|
+
return result;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
inline int json_unescape(const char *s, size_t n, char *out) {
|
|
722
|
+
int r = 0;
|
|
723
|
+
if (*s++ != '"') {
|
|
724
|
+
return -1;
|
|
725
|
+
}
|
|
726
|
+
while (n > 2) {
|
|
727
|
+
char c = *s;
|
|
728
|
+
if (c == '\\') {
|
|
729
|
+
s++;
|
|
730
|
+
n--;
|
|
731
|
+
switch (*s) {
|
|
732
|
+
case 'b':
|
|
733
|
+
c = '\b';
|
|
734
|
+
break;
|
|
735
|
+
case 'f':
|
|
736
|
+
c = '\f';
|
|
737
|
+
break;
|
|
738
|
+
case 'n':
|
|
739
|
+
c = '\n';
|
|
740
|
+
break;
|
|
741
|
+
case 'r':
|
|
742
|
+
c = '\r';
|
|
743
|
+
break;
|
|
744
|
+
case 't':
|
|
745
|
+
c = '\t';
|
|
746
|
+
break;
|
|
747
|
+
case '\\':
|
|
748
|
+
c = '\\';
|
|
749
|
+
break;
|
|
750
|
+
case '/':
|
|
751
|
+
c = '/';
|
|
752
|
+
break;
|
|
753
|
+
case '\"':
|
|
754
|
+
c = '\"';
|
|
755
|
+
break;
|
|
756
|
+
default: // TODO: support unicode decoding
|
|
757
|
+
return -1;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if (out != nullptr) {
|
|
761
|
+
*out++ = c;
|
|
762
|
+
}
|
|
763
|
+
s++;
|
|
764
|
+
n--;
|
|
765
|
+
r++;
|
|
766
|
+
}
|
|
767
|
+
if (*s != '"') {
|
|
768
|
+
return -1;
|
|
769
|
+
}
|
|
770
|
+
if (out != nullptr) {
|
|
771
|
+
*out = '\0';
|
|
772
|
+
}
|
|
773
|
+
return r;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
inline std::string json_parse(const std::string &s, const std::string &key,
|
|
777
|
+
const int index) {
|
|
778
|
+
const char *value;
|
|
779
|
+
size_t value_sz;
|
|
780
|
+
if (key.empty()) {
|
|
781
|
+
json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz);
|
|
782
|
+
} else {
|
|
783
|
+
json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value,
|
|
784
|
+
&value_sz);
|
|
785
|
+
}
|
|
786
|
+
if (value != nullptr) {
|
|
787
|
+
if (value[0] != '"') {
|
|
788
|
+
return {value, value_sz};
|
|
789
|
+
}
|
|
790
|
+
int n = json_unescape(value, value_sz, nullptr);
|
|
791
|
+
if (n > 0) {
|
|
792
|
+
char *decoded = new char[n + 1];
|
|
793
|
+
json_unescape(value, value_sz, decoded);
|
|
794
|
+
std::string result(decoded, n);
|
|
795
|
+
delete[] decoded;
|
|
796
|
+
return result;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return "";
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Holds a symbol name and associated type for code clarity.
|
|
803
|
+
template <typename T> class library_symbol {
|
|
804
|
+
public:
|
|
805
|
+
using type = T;
|
|
806
|
+
|
|
807
|
+
constexpr explicit library_symbol(const char *name) : m_name(name) {}
|
|
808
|
+
constexpr const char *get_name() const { return m_name; }
|
|
809
|
+
|
|
810
|
+
private:
|
|
811
|
+
const char *m_name;
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
// Loads a native shared library and allows one to get addresses for those
|
|
815
|
+
// symbols.
|
|
816
|
+
class native_library {
|
|
817
|
+
public:
|
|
818
|
+
native_library() = default;
|
|
819
|
+
|
|
820
|
+
explicit native_library(const std::string &name)
|
|
821
|
+
: m_handle{load_library(name)} {}
|
|
822
|
+
|
|
823
|
+
#ifdef _WIN32
|
|
824
|
+
explicit native_library(const std::wstring &name)
|
|
825
|
+
: m_handle{load_library(name)} {}
|
|
826
|
+
#endif
|
|
827
|
+
|
|
828
|
+
~native_library() {
|
|
829
|
+
if (m_handle) {
|
|
830
|
+
#ifdef _WIN32
|
|
831
|
+
FreeLibrary(m_handle);
|
|
832
|
+
#else
|
|
833
|
+
dlclose(m_handle);
|
|
834
|
+
#endif
|
|
835
|
+
m_handle = nullptr;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
native_library(const native_library &other) = delete;
|
|
840
|
+
native_library &operator=(const native_library &other) = delete;
|
|
841
|
+
native_library(native_library &&other) noexcept { *this = std::move(other); }
|
|
842
|
+
|
|
843
|
+
native_library &operator=(native_library &&other) noexcept {
|
|
844
|
+
if (this == &other) {
|
|
845
|
+
return *this;
|
|
846
|
+
}
|
|
847
|
+
m_handle = other.m_handle;
|
|
848
|
+
other.m_handle = nullptr;
|
|
849
|
+
return *this;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Returns true if the library is currently loaded; otherwise false.
|
|
853
|
+
operator bool() const { return is_loaded(); }
|
|
854
|
+
|
|
855
|
+
// Get the address for the specified symbol or nullptr if not found.
|
|
856
|
+
template <typename Symbol>
|
|
857
|
+
typename Symbol::type get(const Symbol &symbol) const {
|
|
858
|
+
if (is_loaded()) {
|
|
859
|
+
// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
860
|
+
#ifdef _WIN32
|
|
861
|
+
#ifdef __GNUC__
|
|
862
|
+
#pragma GCC diagnostic push
|
|
863
|
+
#pragma GCC diagnostic ignored "-Wcast-function-type"
|
|
864
|
+
#endif
|
|
865
|
+
return reinterpret_cast<typename Symbol::type>(
|
|
866
|
+
GetProcAddress(m_handle, symbol.get_name()));
|
|
867
|
+
#ifdef __GNUC__
|
|
868
|
+
#pragma GCC diagnostic pop
|
|
869
|
+
#endif
|
|
870
|
+
#else
|
|
871
|
+
return reinterpret_cast<typename Symbol::type>(
|
|
872
|
+
dlsym(m_handle, symbol.get_name()));
|
|
873
|
+
#endif
|
|
874
|
+
// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
875
|
+
}
|
|
876
|
+
return nullptr;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// Returns true if the library is currently loaded; otherwise false.
|
|
880
|
+
bool is_loaded() const { return !!m_handle; }
|
|
881
|
+
|
|
882
|
+
void detach() { m_handle = nullptr; }
|
|
883
|
+
|
|
884
|
+
// Returns true if the library by the given name is currently loaded; otherwise false.
|
|
885
|
+
static inline bool is_loaded(const std::string &name) {
|
|
886
|
+
#ifdef _WIN32
|
|
887
|
+
auto handle = GetModuleHandleW(widen_string(name).c_str());
|
|
888
|
+
#else
|
|
889
|
+
auto handle = dlopen(name.c_str(), RTLD_NOW | RTLD_NOLOAD);
|
|
890
|
+
if (handle) {
|
|
891
|
+
dlclose(handle);
|
|
892
|
+
}
|
|
893
|
+
#endif
|
|
894
|
+
return !!handle;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
private:
|
|
898
|
+
#ifdef _WIN32
|
|
899
|
+
using mod_handle_t = HMODULE;
|
|
900
|
+
#else
|
|
901
|
+
using mod_handle_t = void *;
|
|
902
|
+
#endif
|
|
903
|
+
|
|
904
|
+
static inline mod_handle_t load_library(const std::string &name) {
|
|
905
|
+
#ifdef _WIN32
|
|
906
|
+
return load_library(widen_string(name));
|
|
907
|
+
#else
|
|
908
|
+
return dlopen(name.c_str(), RTLD_NOW);
|
|
909
|
+
#endif
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
#ifdef _WIN32
|
|
913
|
+
static inline mod_handle_t load_library(const std::wstring &name) {
|
|
914
|
+
return LoadLibraryW(name.c_str());
|
|
915
|
+
}
|
|
916
|
+
#endif
|
|
917
|
+
|
|
918
|
+
mod_handle_t m_handle{};
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
class engine_base {
|
|
922
|
+
public:
|
|
923
|
+
virtual ~engine_base() = default;
|
|
924
|
+
|
|
925
|
+
void navigate(const std::string &url) {
|
|
926
|
+
if (url.empty()) {
|
|
927
|
+
navigate_impl("about:blank");
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
navigate_impl(url);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
using binding_t = std::function<void(std::string, std::string, void *)>;
|
|
934
|
+
class binding_ctx_t {
|
|
935
|
+
public:
|
|
936
|
+
binding_ctx_t(binding_t callback, void *arg)
|
|
937
|
+
: callback(callback), arg(arg) {}
|
|
938
|
+
// This function is called upon execution of the bound JS function
|
|
939
|
+
binding_t callback;
|
|
940
|
+
// This user-supplied argument is passed to the callback
|
|
941
|
+
void *arg;
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
using sync_binding_t = std::function<std::string(std::string)>;
|
|
945
|
+
|
|
946
|
+
// Synchronous bind
|
|
947
|
+
void bind(const std::string &name, sync_binding_t fn) {
|
|
948
|
+
auto wrapper = [this, fn](const std::string &seq, const std::string &req,
|
|
949
|
+
void * /*arg*/) { resolve(seq, 0, fn(req)); };
|
|
950
|
+
bind(name, wrapper, nullptr);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Asynchronous bind
|
|
954
|
+
void bind(const std::string &name, binding_t fn, void *arg) {
|
|
955
|
+
// NOLINTNEXTLINE(readability-container-contains): contains() requires C++20
|
|
956
|
+
if (bindings.count(name) > 0) {
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
bindings.emplace(name, binding_ctx_t(fn, arg));
|
|
960
|
+
auto js = "(function() { var name = '" + name + "';" + R""(
|
|
961
|
+
var RPC = window._rpc = (window._rpc || {nextSeq: 1});
|
|
962
|
+
window[name] = function() {
|
|
963
|
+
var seq = RPC.nextSeq++;
|
|
964
|
+
var promise = new Promise(function(resolve, reject) {
|
|
965
|
+
RPC[seq] = {
|
|
966
|
+
resolve: resolve,
|
|
967
|
+
reject: reject,
|
|
968
|
+
};
|
|
969
|
+
});
|
|
970
|
+
window.external.invoke(JSON.stringify({
|
|
971
|
+
id: seq,
|
|
972
|
+
method: name,
|
|
973
|
+
params: Array.prototype.slice.call(arguments),
|
|
974
|
+
}));
|
|
975
|
+
return promise;
|
|
976
|
+
}
|
|
977
|
+
})())"";
|
|
978
|
+
init(js);
|
|
979
|
+
eval(js);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
void unbind(const std::string &name) {
|
|
983
|
+
auto found = bindings.find(name);
|
|
984
|
+
if (found != bindings.end()) {
|
|
985
|
+
auto js = "delete window['" + name + "'];";
|
|
986
|
+
init(js);
|
|
987
|
+
eval(js);
|
|
988
|
+
bindings.erase(found);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
void resolve(const std::string &seq, int status, const std::string &result) {
|
|
993
|
+
// NOLINTNEXTLINE(modernize-avoid-bind): Lambda with move requires C++14
|
|
994
|
+
dispatch(std::bind(
|
|
995
|
+
[seq, status, this](std::string escaped_result) {
|
|
996
|
+
std::string js;
|
|
997
|
+
js += "(function(){var seq = \"";
|
|
998
|
+
js += seq;
|
|
999
|
+
js += "\";\n";
|
|
1000
|
+
js += "var status = ";
|
|
1001
|
+
js += std::to_string(status);
|
|
1002
|
+
js += ";\n";
|
|
1003
|
+
js += "var result = ";
|
|
1004
|
+
js += escaped_result;
|
|
1005
|
+
js += ";\
|
|
1006
|
+
var promise = window._rpc[seq];\
|
|
1007
|
+
delete window._rpc[seq];\
|
|
1008
|
+
if (result !== undefined) {\
|
|
1009
|
+
try {\
|
|
1010
|
+
result = JSON.parse(result);\
|
|
1011
|
+
} catch {\
|
|
1012
|
+
promise.reject(new Error(\"Failed to parse binding result as JSON\"));\
|
|
1013
|
+
return;\
|
|
1014
|
+
}\
|
|
1015
|
+
}\
|
|
1016
|
+
if (status === 0) {\
|
|
1017
|
+
promise.resolve(result);\
|
|
1018
|
+
} else {\
|
|
1019
|
+
promise.reject(result);\
|
|
1020
|
+
}\
|
|
1021
|
+
})()";
|
|
1022
|
+
eval(js);
|
|
1023
|
+
},
|
|
1024
|
+
result.empty() ? "undefined" : json_escape(result)));
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
void *window() { return window_impl(); }
|
|
1028
|
+
void *widget() { return widget_impl(); }
|
|
1029
|
+
void *browser_controller() { return browser_controller_impl(); };
|
|
1030
|
+
void run() { run_impl(); }
|
|
1031
|
+
void terminate() { terminate_impl(); }
|
|
1032
|
+
void dispatch(std::function<void()> f) { dispatch_impl(f); }
|
|
1033
|
+
void set_title(const std::string &title) { set_title_impl(title); }
|
|
1034
|
+
|
|
1035
|
+
// set_decorated 控制原生窗口标题栏装饰(frameless 模式下由前端自绘标题栏接管)。
|
|
1036
|
+
// - GTK: gtk_window_set_decorated
|
|
1037
|
+
// - Cocoa: 隐藏标题栏 + 透明标题栏 + 移除原生最小化/关闭按钮(保留 Titled 承载拖动区域)
|
|
1038
|
+
// - Win32: no-op(Windows frameless 由 freedom 壳层 WndProc 处理,见 window_windows.go)
|
|
1039
|
+
void set_decorated(bool decorated) { set_decorated_impl(decorated); }
|
|
1040
|
+
|
|
1041
|
+
// window_control 执行原生窗口控制(最小化/最大化/还原/关闭等),
|
|
1042
|
+
// 供前端自绘标题栏三按钮使用。action 取值见 webview_window_action_t。
|
|
1043
|
+
// 返回 0 表示成功;-1 表示该平台/动作不支持;isMaximized 返回 1/0。
|
|
1044
|
+
int window_control(int action) { return window_control_impl(action); }
|
|
1045
|
+
int begin_move_drag() { return begin_move_drag_impl(); }
|
|
1046
|
+
|
|
1047
|
+
void set_size(int width, int height, webview_hint_t hints) {
|
|
1048
|
+
set_size_impl(width, height, hints);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
void set_html(const std::string &html) { set_html_impl(html); }
|
|
1052
|
+
void init(const std::string &js) { init_impl(js); }
|
|
1053
|
+
void eval(const std::string &js) { eval_impl(js); }
|
|
1054
|
+
|
|
1055
|
+
protected:
|
|
1056
|
+
virtual void navigate_impl(const std::string &url) = 0;
|
|
1057
|
+
virtual void *window_impl() = 0;
|
|
1058
|
+
virtual void *widget_impl() = 0;
|
|
1059
|
+
virtual void *browser_controller_impl() = 0;
|
|
1060
|
+
virtual void run_impl() = 0;
|
|
1061
|
+
virtual void terminate_impl() = 0;
|
|
1062
|
+
virtual void dispatch_impl(std::function<void()> f) = 0;
|
|
1063
|
+
virtual void set_title_impl(const std::string &title) = 0;
|
|
1064
|
+
virtual void set_size_impl(int width, int height, webview_hint_t hints) = 0;
|
|
1065
|
+
virtual void set_decorated_impl(bool decorated) = 0;
|
|
1066
|
+
virtual int window_control_impl(int action) = 0;
|
|
1067
|
+
virtual int begin_move_drag_impl() = 0;
|
|
1068
|
+
virtual void set_html_impl(const std::string &html) = 0;
|
|
1069
|
+
virtual void init_impl(const std::string &js) = 0;
|
|
1070
|
+
virtual void eval_impl(const std::string &js) = 0;
|
|
1071
|
+
|
|
1072
|
+
virtual void on_message(const std::string &msg) {
|
|
1073
|
+
auto seq = json_parse(msg, "id", 0);
|
|
1074
|
+
auto name = json_parse(msg, "method", 0);
|
|
1075
|
+
auto args = json_parse(msg, "params", 0);
|
|
1076
|
+
auto found = bindings.find(name);
|
|
1077
|
+
if (found == bindings.end()) {
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
const auto &context = found->second;
|
|
1081
|
+
context.callback(seq, args, context.arg);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
virtual void on_window_created() { inc_window_count(); }
|
|
1085
|
+
|
|
1086
|
+
virtual void on_window_destroyed(bool skip_termination = false) {
|
|
1087
|
+
if (dec_window_count() <= 0) {
|
|
1088
|
+
if (!skip_termination) {
|
|
1089
|
+
terminate();
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
private:
|
|
1095
|
+
static std::atomic_uint &window_ref_count() {
|
|
1096
|
+
static std::atomic_uint ref_count{0};
|
|
1097
|
+
return ref_count;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
static unsigned int inc_window_count() { return ++window_ref_count(); }
|
|
1101
|
+
|
|
1102
|
+
static unsigned int dec_window_count() {
|
|
1103
|
+
auto &count = window_ref_count();
|
|
1104
|
+
if (count > 0) {
|
|
1105
|
+
return --count;
|
|
1106
|
+
}
|
|
1107
|
+
return 0;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
std::map<std::string, binding_ctx_t> bindings;
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
} // namespace detail
|
|
1114
|
+
|
|
1115
|
+
WEBVIEW_DEPRECATED_PRIVATE
|
|
1116
|
+
inline int json_parse_c(const char *s, size_t sz, const char *key, size_t keysz,
|
|
1117
|
+
const char **value, size_t *valuesz) {
|
|
1118
|
+
return detail::json_parse_c(s, sz, key, keysz, value, valuesz);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
WEBVIEW_DEPRECATED_PRIVATE
|
|
1122
|
+
inline std::string json_escape(const std::string &s) {
|
|
1123
|
+
return detail::json_escape(s);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
WEBVIEW_DEPRECATED_PRIVATE
|
|
1127
|
+
inline int json_unescape(const char *s, size_t n, char *out) {
|
|
1128
|
+
return detail::json_unescape(s, n, out);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
WEBVIEW_DEPRECATED_PRIVATE
|
|
1132
|
+
inline std::string json_parse(const std::string &s, const std::string &key,
|
|
1133
|
+
const int index) {
|
|
1134
|
+
return detail::json_parse(s, key, index);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
} // namespace webview
|
|
1138
|
+
|
|
1139
|
+
#if defined(WEBVIEW_GTK)
|
|
1140
|
+
//
|
|
1141
|
+
// ====================================================================
|
|
1142
|
+
//
|
|
1143
|
+
// This implementation uses webkit2gtk backend. It requires gtk+3.0 and
|
|
1144
|
+
// webkit2gtk-4.0 libraries. Proper compiler flags can be retrieved via:
|
|
1145
|
+
//
|
|
1146
|
+
// pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0
|
|
1147
|
+
//
|
|
1148
|
+
// ====================================================================
|
|
1149
|
+
//
|
|
1150
|
+
#include <cstdlib>
|
|
1151
|
+
|
|
1152
|
+
#include <JavaScriptCore/JavaScript.h>
|
|
1153
|
+
#include <gtk/gtk.h>
|
|
1154
|
+
#include <webkit2/webkit2.h>
|
|
1155
|
+
|
|
1156
|
+
#ifdef GDK_WINDOWING_X11
|
|
1157
|
+
#include <gdk/gdkx.h>
|
|
1158
|
+
#endif
|
|
1159
|
+
|
|
1160
|
+
#include <fcntl.h>
|
|
1161
|
+
#include <sys/stat.h>
|
|
1162
|
+
|
|
1163
|
+
namespace webview {
|
|
1164
|
+
namespace detail {
|
|
1165
|
+
|
|
1166
|
+
// Namespace containing workaround for WebKit 2.42 when using NVIDIA GPU
|
|
1167
|
+
// driver.
|
|
1168
|
+
// See WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=261874
|
|
1169
|
+
// Please remove all of the code in this namespace when it's no longer needed.
|
|
1170
|
+
namespace webkit_dmabuf {
|
|
1171
|
+
|
|
1172
|
+
// Get environment variable. Not thread-safe.
|
|
1173
|
+
static inline std::string get_env(const std::string &name) {
|
|
1174
|
+
auto *value = std::getenv(name.c_str());
|
|
1175
|
+
if (value) {
|
|
1176
|
+
return {value};
|
|
1177
|
+
}
|
|
1178
|
+
return {};
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Set environment variable. Not thread-safe.
|
|
1182
|
+
static inline void set_env(const std::string &name, const std::string &value) {
|
|
1183
|
+
::setenv(name.c_str(), value.c_str(), 1);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// Checks whether the NVIDIA GPU driver is used based on whether the kernel
|
|
1187
|
+
// module is loaded.
|
|
1188
|
+
static inline bool is_using_nvidia_driver() {
|
|
1189
|
+
struct ::stat buffer {};
|
|
1190
|
+
if (::stat("/sys/module/nvidia", &buffer) != 0) {
|
|
1191
|
+
return false;
|
|
1192
|
+
}
|
|
1193
|
+
return S_ISDIR(buffer.st_mode);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// Checks whether the windowing system is Wayland.
|
|
1197
|
+
static inline bool is_wayland_display() {
|
|
1198
|
+
if (!get_env("WAYLAND_DISPLAY").empty()) {
|
|
1199
|
+
return true;
|
|
1200
|
+
}
|
|
1201
|
+
if (get_env("XDG_SESSION_TYPE") == "wayland") {
|
|
1202
|
+
return true;
|
|
1203
|
+
}
|
|
1204
|
+
if (get_env("DESKTOP_SESSION").find("wayland") != std::string::npos) {
|
|
1205
|
+
return true;
|
|
1206
|
+
}
|
|
1207
|
+
return false;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// Checks whether the GDK X11 backend is used.
|
|
1211
|
+
// See: https://docs.gtk.org/gdk3/class.DisplayManager.html
|
|
1212
|
+
static inline bool is_gdk_x11_backend() {
|
|
1213
|
+
#ifdef GDK_WINDOWING_X11
|
|
1214
|
+
auto *manager = gdk_display_manager_get();
|
|
1215
|
+
auto *display = gdk_display_manager_get_default_display(manager);
|
|
1216
|
+
return GDK_IS_X11_DISPLAY(display); // NOLINT(misc-const-correctness)
|
|
1217
|
+
#else
|
|
1218
|
+
return false;
|
|
1219
|
+
#endif
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// Checks whether WebKit is affected by bug when using DMA-BUF renderer.
|
|
1223
|
+
// Returns true if all of the following conditions are met:
|
|
1224
|
+
// - WebKit version is >= 2.42 (please narrow this down when there's a fix).
|
|
1225
|
+
// - Environment variables are empty or not set:
|
|
1226
|
+
// - WEBKIT_DISABLE_DMABUF_RENDERER
|
|
1227
|
+
// - Windowing system is not Wayland.
|
|
1228
|
+
// - GDK backend is X11.
|
|
1229
|
+
// - NVIDIA GPU driver is used.
|
|
1230
|
+
static inline bool is_webkit_dmabuf_bugged() {
|
|
1231
|
+
auto wk_major = webkit_get_major_version();
|
|
1232
|
+
auto wk_minor = webkit_get_minor_version();
|
|
1233
|
+
// TODO: Narrow down affected WebKit version when there's a fixed version
|
|
1234
|
+
auto is_affected_wk_version = wk_major == 2 && wk_minor >= 42;
|
|
1235
|
+
if (!is_affected_wk_version) {
|
|
1236
|
+
return false;
|
|
1237
|
+
}
|
|
1238
|
+
if (!get_env("WEBKIT_DISABLE_DMABUF_RENDERER").empty()) {
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
if (is_wayland_display()) {
|
|
1242
|
+
return false;
|
|
1243
|
+
}
|
|
1244
|
+
if (!is_gdk_x11_backend()) {
|
|
1245
|
+
return false;
|
|
1246
|
+
}
|
|
1247
|
+
if (!is_using_nvidia_driver()) {
|
|
1248
|
+
return false;
|
|
1249
|
+
}
|
|
1250
|
+
return true;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// Applies workaround for WebKit DMA-BUF bug if needed.
|
|
1254
|
+
// See WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=261874
|
|
1255
|
+
static inline void apply_webkit_dmabuf_workaround() {
|
|
1256
|
+
if (!is_webkit_dmabuf_bugged()) {
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
set_env("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
|
1260
|
+
}
|
|
1261
|
+
} // namespace webkit_dmabuf
|
|
1262
|
+
|
|
1263
|
+
namespace webkit_symbols {
|
|
1264
|
+
using webkit_web_view_evaluate_javascript_t =
|
|
1265
|
+
void (*)(WebKitWebView *, const char *, gssize, const char *, const char *,
|
|
1266
|
+
GCancellable *, GAsyncReadyCallback, gpointer);
|
|
1267
|
+
|
|
1268
|
+
using webkit_web_view_run_javascript_t = void (*)(WebKitWebView *,
|
|
1269
|
+
const gchar *, GCancellable *,
|
|
1270
|
+
GAsyncReadyCallback,
|
|
1271
|
+
gpointer);
|
|
1272
|
+
|
|
1273
|
+
constexpr auto webkit_web_view_evaluate_javascript =
|
|
1274
|
+
library_symbol<webkit_web_view_evaluate_javascript_t>(
|
|
1275
|
+
"webkit_web_view_evaluate_javascript");
|
|
1276
|
+
constexpr auto webkit_web_view_run_javascript =
|
|
1277
|
+
library_symbol<webkit_web_view_run_javascript_t>(
|
|
1278
|
+
"webkit_web_view_run_javascript");
|
|
1279
|
+
} // namespace webkit_symbols
|
|
1280
|
+
|
|
1281
|
+
class gtk_webkit_engine : public engine_base {
|
|
1282
|
+
public:
|
|
1283
|
+
gtk_webkit_engine(bool debug, void *window)
|
|
1284
|
+
: m_owns_window{!window}, m_window(static_cast<GtkWidget *>(window)) {
|
|
1285
|
+
if (m_owns_window) {
|
|
1286
|
+
if (gtk_init_check(nullptr, nullptr) == FALSE) {
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
1290
|
+
on_window_created();
|
|
1291
|
+
g_signal_connect(G_OBJECT(m_window), "destroy",
|
|
1292
|
+
G_CALLBACK(+[](GtkWidget *, gpointer arg) {
|
|
1293
|
+
auto *w = static_cast<gtk_webkit_engine *>(arg);
|
|
1294
|
+
// Widget destroyed along with window.
|
|
1295
|
+
w->m_webview = nullptr;
|
|
1296
|
+
w->m_window = nullptr;
|
|
1297
|
+
w->on_window_destroyed();
|
|
1298
|
+
}),
|
|
1299
|
+
this);
|
|
1300
|
+
}
|
|
1301
|
+
webkit_dmabuf::apply_webkit_dmabuf_workaround();
|
|
1302
|
+
// Initialize webview widget
|
|
1303
|
+
m_webview = webkit_web_view_new();
|
|
1304
|
+
WebKitUserContentManager *manager =
|
|
1305
|
+
webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
|
|
1306
|
+
g_signal_connect(manager, "script-message-received::external",
|
|
1307
|
+
G_CALLBACK(+[](WebKitUserContentManager *,
|
|
1308
|
+
WebKitJavascriptResult *r, gpointer arg) {
|
|
1309
|
+
auto *w = static_cast<gtk_webkit_engine *>(arg);
|
|
1310
|
+
char *s = get_string_from_js_result(r);
|
|
1311
|
+
w->on_message(s);
|
|
1312
|
+
g_free(s);
|
|
1313
|
+
}),
|
|
1314
|
+
this);
|
|
1315
|
+
webkit_user_content_manager_register_script_message_handler(manager,
|
|
1316
|
+
"external");
|
|
1317
|
+
init("window.external={invoke:function(s){window.webkit.messageHandlers."
|
|
1318
|
+
"external.postMessage(s);}}");
|
|
1319
|
+
|
|
1320
|
+
gtk_container_add(GTK_CONTAINER(m_window), GTK_WIDGET(m_webview));
|
|
1321
|
+
gtk_widget_show(GTK_WIDGET(m_webview));
|
|
1322
|
+
|
|
1323
|
+
WebKitSettings *settings =
|
|
1324
|
+
webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_webview));
|
|
1325
|
+
webkit_settings_set_javascript_can_access_clipboard(settings, true);
|
|
1326
|
+
if (debug) {
|
|
1327
|
+
webkit_settings_set_enable_write_console_messages_to_stdout(settings,
|
|
1328
|
+
true);
|
|
1329
|
+
webkit_settings_set_enable_developer_extras(settings, true);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
if (m_owns_window) {
|
|
1333
|
+
gtk_widget_grab_focus(GTK_WIDGET(m_webview));
|
|
1334
|
+
gtk_widget_show_all(m_window);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
gtk_webkit_engine(const gtk_webkit_engine &) = delete;
|
|
1339
|
+
gtk_webkit_engine &operator=(const gtk_webkit_engine &) = delete;
|
|
1340
|
+
gtk_webkit_engine(gtk_webkit_engine &&) = delete;
|
|
1341
|
+
gtk_webkit_engine &operator=(gtk_webkit_engine &&) = delete;
|
|
1342
|
+
|
|
1343
|
+
virtual ~gtk_webkit_engine() {
|
|
1344
|
+
if (m_webview) {
|
|
1345
|
+
gtk_widget_destroy(GTK_WIDGET(m_webview));
|
|
1346
|
+
m_webview = nullptr;
|
|
1347
|
+
}
|
|
1348
|
+
if (m_window) {
|
|
1349
|
+
if (m_owns_window) {
|
|
1350
|
+
// Disconnect handlers to avoid callbacks invoked during destruction.
|
|
1351
|
+
g_signal_handlers_disconnect_by_data(GTK_WINDOW(m_window), this);
|
|
1352
|
+
gtk_window_close(GTK_WINDOW(m_window));
|
|
1353
|
+
on_window_destroyed(true);
|
|
1354
|
+
}
|
|
1355
|
+
m_window = nullptr;
|
|
1356
|
+
}
|
|
1357
|
+
if (m_owns_window) {
|
|
1358
|
+
// Needed for the window to close immediately.
|
|
1359
|
+
deplete_run_loop_event_queue();
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
void *window_impl() override { return (void *)m_window; }
|
|
1364
|
+
void *widget_impl() override { return (void *)m_webview; }
|
|
1365
|
+
void *browser_controller_impl() override { return (void *)m_webview; };
|
|
1366
|
+
void run_impl() override { gtk_main(); }
|
|
1367
|
+
void terminate_impl() override {
|
|
1368
|
+
dispatch_impl([] { gtk_main_quit(); });
|
|
1369
|
+
}
|
|
1370
|
+
void dispatch_impl(std::function<void()> f) override {
|
|
1371
|
+
g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void *f) -> int {
|
|
1372
|
+
(*static_cast<dispatch_fn_t *>(f))();
|
|
1373
|
+
return G_SOURCE_REMOVE;
|
|
1374
|
+
}),
|
|
1375
|
+
new std::function<void()>(f),
|
|
1376
|
+
[](void *f) { delete static_cast<dispatch_fn_t *>(f); });
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
void set_title_impl(const std::string &title) override {
|
|
1380
|
+
gtk_window_set_title(GTK_WINDOW(m_window), title.c_str());
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// frameless 模式去掉 WM 窗口装饰(标题栏 / 边框),内容由前端自绘标题栏接管。
|
|
1384
|
+
// gtk_window_set_decorated(FALSE) 仅移除标题栏装饰,窗口仍可正常移动 / 最大化。
|
|
1385
|
+
void set_decorated_impl(bool decorated) override {
|
|
1386
|
+
gtk_window_set_decorated(GTK_WINDOW(m_window), decorated ? TRUE : FALSE);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// GTK 原生窗口控制:最小化/最大化/还原/关闭均走 GtkWindow API,
|
|
1390
|
+
// 与前端自绘标题栏三按钮一一对应。
|
|
1391
|
+
int window_control_impl(int action) override {
|
|
1392
|
+
auto win = GTK_WINDOW(m_window);
|
|
1393
|
+
switch ((webview_window_action_t)action) {
|
|
1394
|
+
case WEBVIEW_WINDOW_ACTION_MINIMIZE:
|
|
1395
|
+
gtk_window_iconify(win);
|
|
1396
|
+
return 0;
|
|
1397
|
+
case WEBVIEW_WINDOW_ACTION_MAXIMIZE:
|
|
1398
|
+
gtk_window_maximize(win);
|
|
1399
|
+
return 0;
|
|
1400
|
+
case WEBVIEW_WINDOW_ACTION_UNMAXIMIZE:
|
|
1401
|
+
gtk_window_unmaximize(win);
|
|
1402
|
+
return 0;
|
|
1403
|
+
case WEBVIEW_WINDOW_ACTION_RESTORE:
|
|
1404
|
+
gtk_window_present(win);
|
|
1405
|
+
gtk_window_unmaximize(win);
|
|
1406
|
+
return 0;
|
|
1407
|
+
case WEBVIEW_WINDOW_ACTION_CLOSE:
|
|
1408
|
+
gtk_window_close(win);
|
|
1409
|
+
return 0;
|
|
1410
|
+
case WEBVIEW_WINDOW_ACTION_TOGGLE_MAXIMIZE:
|
|
1411
|
+
if (gtk_window_is_maximized(win)) {
|
|
1412
|
+
gtk_window_unmaximize(win);
|
|
1413
|
+
} else {
|
|
1414
|
+
gtk_window_maximize(win);
|
|
1415
|
+
}
|
|
1416
|
+
return 0;
|
|
1417
|
+
case WEBVIEW_WINDOW_ACTION_IS_MAXIMIZED:
|
|
1418
|
+
return gtk_window_is_maximized(win) ? 1 : 0;
|
|
1419
|
+
default:
|
|
1420
|
+
return -1;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// 无边框窗口拖动:root_x/root_y=-1 表示用当前指针位置,由窗口管理器接管拖动。
|
|
1425
|
+
// 与 Windows 的 WM_NCLBUTTONDOWN+HTCAPTION 语义等价,保留页面双击/右键事件。
|
|
1426
|
+
int begin_move_drag_impl() override {
|
|
1427
|
+
gtk_window_begin_move_drag(GTK_WINDOW(m_window), GDK_BUTTON_PRESS, -1, -1,
|
|
1428
|
+
gtk_get_current_event_time());
|
|
1429
|
+
return 0;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
void set_size_impl(int width, int height, webview_hint_t hints) override {
|
|
1433
|
+
gtk_window_set_resizable(GTK_WINDOW(m_window), hints != WEBVIEW_HINT_FIXED);
|
|
1434
|
+
if (hints == WEBVIEW_HINT_NONE) {
|
|
1435
|
+
gtk_window_resize(GTK_WINDOW(m_window), width, height);
|
|
1436
|
+
} else if (hints == WEBVIEW_HINT_FIXED) {
|
|
1437
|
+
gtk_widget_set_size_request(m_window, width, height);
|
|
1438
|
+
} else {
|
|
1439
|
+
GdkGeometry g;
|
|
1440
|
+
g.min_width = g.max_width = width;
|
|
1441
|
+
g.min_height = g.max_height = height;
|
|
1442
|
+
GdkWindowHints h =
|
|
1443
|
+
(hints == WEBVIEW_HINT_MIN ? GDK_HINT_MIN_SIZE : GDK_HINT_MAX_SIZE);
|
|
1444
|
+
// This defines either MIN_SIZE, or MAX_SIZE, but not both:
|
|
1445
|
+
gtk_window_set_geometry_hints(GTK_WINDOW(m_window), nullptr, &g, h);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
void navigate_impl(const std::string &url) override {
|
|
1450
|
+
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url.c_str());
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
void set_html_impl(const std::string &html) override {
|
|
1454
|
+
webkit_web_view_load_html(WEBKIT_WEB_VIEW(m_webview), html.c_str(),
|
|
1455
|
+
nullptr);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
void init_impl(const std::string &js) override {
|
|
1459
|
+
WebKitUserContentManager *manager =
|
|
1460
|
+
webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
|
|
1461
|
+
webkit_user_content_manager_add_script(
|
|
1462
|
+
manager,
|
|
1463
|
+
webkit_user_script_new(js.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
|
|
1464
|
+
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,
|
|
1465
|
+
nullptr, nullptr));
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
void eval_impl(const std::string &js) override {
|
|
1469
|
+
auto &lib = get_webkit_library();
|
|
1470
|
+
auto wkmajor = webkit_get_major_version();
|
|
1471
|
+
auto wkminor = webkit_get_minor_version();
|
|
1472
|
+
if ((wkmajor == 2 && wkminor >= 40) || wkmajor > 2) {
|
|
1473
|
+
if (auto fn =
|
|
1474
|
+
lib.get(webkit_symbols::webkit_web_view_evaluate_javascript)) {
|
|
1475
|
+
fn(WEBKIT_WEB_VIEW(m_webview), js.c_str(),
|
|
1476
|
+
static_cast<gssize>(js.size()), nullptr, nullptr, nullptr, nullptr,
|
|
1477
|
+
nullptr);
|
|
1478
|
+
}
|
|
1479
|
+
} else if (auto fn =
|
|
1480
|
+
lib.get(webkit_symbols::webkit_web_view_run_javascript)) {
|
|
1481
|
+
fn(WEBKIT_WEB_VIEW(m_webview), js.c_str(), nullptr, nullptr, nullptr);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
private:
|
|
1486
|
+
static char *get_string_from_js_result(WebKitJavascriptResult *r) {
|
|
1487
|
+
char *s;
|
|
1488
|
+
#if (WEBKIT_MAJOR_VERSION == 2 && WEBKIT_MINOR_VERSION >= 22) || \
|
|
1489
|
+
WEBKIT_MAJOR_VERSION > 2
|
|
1490
|
+
JSCValue *value = webkit_javascript_result_get_js_value(r);
|
|
1491
|
+
s = jsc_value_to_string(value);
|
|
1492
|
+
#else
|
|
1493
|
+
JSGlobalContextRef ctx = webkit_javascript_result_get_global_context(r);
|
|
1494
|
+
JSValueRef value = webkit_javascript_result_get_value(r);
|
|
1495
|
+
JSStringRef js = JSValueToStringCopy(ctx, value, nullptr);
|
|
1496
|
+
size_t n = JSStringGetMaximumUTF8CStringSize(js);
|
|
1497
|
+
s = g_new(char, n);
|
|
1498
|
+
JSStringGetUTF8CString(js, s, n);
|
|
1499
|
+
JSStringRelease(js);
|
|
1500
|
+
#endif
|
|
1501
|
+
return s;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
static const native_library &get_webkit_library() {
|
|
1505
|
+
static const native_library non_loaded_lib;
|
|
1506
|
+
static native_library loaded_lib;
|
|
1507
|
+
|
|
1508
|
+
if (loaded_lib.is_loaded()) {
|
|
1509
|
+
return loaded_lib;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
constexpr std::array<const char *, 2> lib_names{"libwebkit2gtk-4.1.so",
|
|
1513
|
+
"libwebkit2gtk-4.0.so"};
|
|
1514
|
+
auto found =
|
|
1515
|
+
std::find_if(lib_names.begin(), lib_names.end(), [](const char *name) {
|
|
1516
|
+
return native_library::is_loaded(name);
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
if (found == lib_names.end()) {
|
|
1520
|
+
return non_loaded_lib;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
loaded_lib = native_library(*found);
|
|
1524
|
+
|
|
1525
|
+
auto loaded = loaded_lib.is_loaded();
|
|
1526
|
+
if (!loaded) {
|
|
1527
|
+
return non_loaded_lib;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
return loaded_lib;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
// Blocks while depleting the run loop of events.
|
|
1534
|
+
void deplete_run_loop_event_queue() {
|
|
1535
|
+
bool done{};
|
|
1536
|
+
dispatch([&] { done = true; });
|
|
1537
|
+
while (!done) {
|
|
1538
|
+
gtk_main_iteration();
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
bool m_owns_window{};
|
|
1543
|
+
GtkWidget *m_window{};
|
|
1544
|
+
GtkWidget *m_webview{};
|
|
1545
|
+
};
|
|
1546
|
+
|
|
1547
|
+
} // namespace detail
|
|
1548
|
+
|
|
1549
|
+
using browser_engine = detail::gtk_webkit_engine;
|
|
1550
|
+
|
|
1551
|
+
} // namespace webview
|
|
1552
|
+
|
|
1553
|
+
#elif defined(WEBVIEW_COCOA)
|
|
1554
|
+
|
|
1555
|
+
//
|
|
1556
|
+
// ====================================================================
|
|
1557
|
+
//
|
|
1558
|
+
// This implementation uses Cocoa WKWebView backend on macOS. It is
|
|
1559
|
+
// written using ObjC runtime and uses WKWebView class as a browser runtime.
|
|
1560
|
+
// You should pass "-framework Webkit" flag to the compiler.
|
|
1561
|
+
//
|
|
1562
|
+
// ====================================================================
|
|
1563
|
+
//
|
|
1564
|
+
|
|
1565
|
+
#include <CoreGraphics/CoreGraphics.h>
|
|
1566
|
+
#include <objc/NSObjCRuntime.h>
|
|
1567
|
+
#include <objc/objc-runtime.h>
|
|
1568
|
+
|
|
1569
|
+
namespace webview {
|
|
1570
|
+
namespace detail {
|
|
1571
|
+
namespace objc {
|
|
1572
|
+
|
|
1573
|
+
// A convenient template function for unconditionally casting the specified
|
|
1574
|
+
// C-like function into a function that can be called with the given return
|
|
1575
|
+
// type and arguments. Caller takes full responsibility for ensuring that
|
|
1576
|
+
// the function call is valid. It is assumed that the function will not
|
|
1577
|
+
// throw exceptions.
|
|
1578
|
+
template <typename Result, typename Callable, typename... Args>
|
|
1579
|
+
Result invoke(Callable callable, Args... args) noexcept {
|
|
1580
|
+
return reinterpret_cast<Result (*)(Args...)>(callable)(args...);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// Calls objc_msgSend.
|
|
1584
|
+
template <typename Result, typename... Args>
|
|
1585
|
+
Result msg_send(Args... args) noexcept {
|
|
1586
|
+
return invoke<Result>(objc_msgSend, args...);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// Wrapper around NSAutoreleasePool that drains the pool on destruction.
|
|
1590
|
+
class autoreleasepool {
|
|
1591
|
+
public:
|
|
1592
|
+
autoreleasepool()
|
|
1593
|
+
: m_pool(msg_send<id>(objc_getClass("NSAutoreleasePool"),
|
|
1594
|
+
sel_registerName("new"))) {}
|
|
1595
|
+
|
|
1596
|
+
~autoreleasepool() {
|
|
1597
|
+
if (m_pool) {
|
|
1598
|
+
msg_send<void>(m_pool, sel_registerName("drain"));
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
autoreleasepool(const autoreleasepool &) = delete;
|
|
1603
|
+
autoreleasepool &operator=(const autoreleasepool &) = delete;
|
|
1604
|
+
autoreleasepool(autoreleasepool &&) = delete;
|
|
1605
|
+
autoreleasepool &operator=(autoreleasepool &&) = delete;
|
|
1606
|
+
|
|
1607
|
+
private:
|
|
1608
|
+
id m_pool{};
|
|
1609
|
+
};
|
|
1610
|
+
|
|
1611
|
+
inline id autoreleased(id object) {
|
|
1612
|
+
msg_send<void>(object, sel_registerName("autorelease"));
|
|
1613
|
+
return object;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
} // namespace objc
|
|
1617
|
+
|
|
1618
|
+
enum NSBackingStoreType : NSUInteger { NSBackingStoreBuffered = 2 };
|
|
1619
|
+
|
|
1620
|
+
enum NSWindowStyleMask : NSUInteger {
|
|
1621
|
+
NSWindowStyleMaskTitled = 1,
|
|
1622
|
+
NSWindowStyleMaskClosable = 2,
|
|
1623
|
+
NSWindowStyleMaskMiniaturizable = 4,
|
|
1624
|
+
NSWindowStyleMaskResizable = 8
|
|
1625
|
+
};
|
|
1626
|
+
|
|
1627
|
+
enum NSWindowTitleVisibility : NSInteger {
|
|
1628
|
+
NSWindowTitleVisible = 0,
|
|
1629
|
+
NSWindowTitleHidden = 1
|
|
1630
|
+
};
|
|
1631
|
+
|
|
1632
|
+
enum NSApplicationActivationPolicy : NSInteger {
|
|
1633
|
+
NSApplicationActivationPolicyRegular = 0
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
enum WKUserScriptInjectionTime : NSInteger {
|
|
1637
|
+
WKUserScriptInjectionTimeAtDocumentStart = 0
|
|
1638
|
+
};
|
|
1639
|
+
|
|
1640
|
+
enum NSModalResponse : NSInteger { NSModalResponseOK = 1 };
|
|
1641
|
+
|
|
1642
|
+
// Convenient conversion of string literals.
|
|
1643
|
+
inline id operator"" _cls(const char *s, std::size_t) {
|
|
1644
|
+
return (id)objc_getClass(s);
|
|
1645
|
+
}
|
|
1646
|
+
inline SEL operator"" _sel(const char *s, std::size_t) {
|
|
1647
|
+
return sel_registerName(s);
|
|
1648
|
+
}
|
|
1649
|
+
inline id operator"" _str(const char *s, std::size_t) {
|
|
1650
|
+
return objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel, s);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
class cocoa_wkwebview_engine : public engine_base {
|
|
1654
|
+
public:
|
|
1655
|
+
cocoa_wkwebview_engine(bool debug, void *window)
|
|
1656
|
+
: m_debug{debug}, m_window{static_cast<id>(window)}, m_owns_window{
|
|
1657
|
+
!window} {
|
|
1658
|
+
auto app = get_shared_application();
|
|
1659
|
+
// See comments related to application lifecycle in create_app_delegate().
|
|
1660
|
+
if (!m_owns_window) {
|
|
1661
|
+
set_up_window();
|
|
1662
|
+
} else {
|
|
1663
|
+
// Only set the app delegate if it hasn't already been set.
|
|
1664
|
+
auto delegate = objc::msg_send<id>(app, "delegate"_sel);
|
|
1665
|
+
if (delegate) {
|
|
1666
|
+
set_up_window();
|
|
1667
|
+
} else {
|
|
1668
|
+
m_app_delegate = create_app_delegate();
|
|
1669
|
+
objc_setAssociatedObject(m_app_delegate, "webview", (id)this,
|
|
1670
|
+
OBJC_ASSOCIATION_ASSIGN);
|
|
1671
|
+
objc::msg_send<void>(app, "setDelegate:"_sel, m_app_delegate);
|
|
1672
|
+
|
|
1673
|
+
// Start the main run loop so that the app delegate gets the
|
|
1674
|
+
// NSApplicationDidFinishLaunchingNotification notification after the run
|
|
1675
|
+
// loop has started in order to perform further initialization.
|
|
1676
|
+
// We need to return from this constructor so this run loop is only
|
|
1677
|
+
// temporary.
|
|
1678
|
+
// Skip the main loop if this isn't the first instance of this class
|
|
1679
|
+
// because the launch event is only sent once. Instead, proceed to
|
|
1680
|
+
// create a window.
|
|
1681
|
+
if (get_and_set_is_first_instance()) {
|
|
1682
|
+
objc::msg_send<void>(app, "run"_sel);
|
|
1683
|
+
} else {
|
|
1684
|
+
set_up_window();
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
cocoa_wkwebview_engine(const cocoa_wkwebview_engine &) = delete;
|
|
1691
|
+
cocoa_wkwebview_engine &operator=(const cocoa_wkwebview_engine &) = delete;
|
|
1692
|
+
cocoa_wkwebview_engine(cocoa_wkwebview_engine &&) = delete;
|
|
1693
|
+
cocoa_wkwebview_engine &operator=(cocoa_wkwebview_engine &&) = delete;
|
|
1694
|
+
|
|
1695
|
+
virtual ~cocoa_wkwebview_engine() {
|
|
1696
|
+
objc::autoreleasepool arp;
|
|
1697
|
+
if (m_window) {
|
|
1698
|
+
if (m_webview) {
|
|
1699
|
+
if (m_webview == objc::msg_send<id>(m_window, "contentView"_sel)) {
|
|
1700
|
+
objc::msg_send<void>(m_window, "setContentView:"_sel, nullptr);
|
|
1701
|
+
}
|
|
1702
|
+
objc::msg_send<void>(m_webview, "release"_sel);
|
|
1703
|
+
m_webview = nullptr;
|
|
1704
|
+
}
|
|
1705
|
+
if (m_owns_window) {
|
|
1706
|
+
// Replace delegate to avoid callbacks and other bad things during
|
|
1707
|
+
// destruction.
|
|
1708
|
+
objc::msg_send<void>(m_window, "setDelegate:"_sel, nullptr);
|
|
1709
|
+
objc::msg_send<void>(m_window, "close"_sel);
|
|
1710
|
+
on_window_destroyed(true);
|
|
1711
|
+
}
|
|
1712
|
+
m_window = nullptr;
|
|
1713
|
+
}
|
|
1714
|
+
if (m_window_delegate) {
|
|
1715
|
+
objc::msg_send<void>(m_window_delegate, "release"_sel);
|
|
1716
|
+
m_window_delegate = nullptr;
|
|
1717
|
+
}
|
|
1718
|
+
if (m_app_delegate) {
|
|
1719
|
+
auto app = get_shared_application();
|
|
1720
|
+
objc::msg_send<void>(app, "setDelegate:"_sel, nullptr);
|
|
1721
|
+
// Make sure to release the delegate we created.
|
|
1722
|
+
objc::msg_send<void>(m_app_delegate, "release"_sel);
|
|
1723
|
+
m_app_delegate = nullptr;
|
|
1724
|
+
}
|
|
1725
|
+
if (m_owns_window) {
|
|
1726
|
+
// Needed for the window to close immediately.
|
|
1727
|
+
deplete_run_loop_event_queue();
|
|
1728
|
+
}
|
|
1729
|
+
// TODO: Figure out why m_manager is still alive after the autoreleasepool
|
|
1730
|
+
// has been drained.
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
void *window_impl() override { return (void *)m_window; }
|
|
1734
|
+
void *widget_impl() override { return (void *)m_webview; }
|
|
1735
|
+
void *browser_controller_impl() override { return (void *)m_webview; };
|
|
1736
|
+
void terminate_impl() override { stop_run_loop(); }
|
|
1737
|
+
void run_impl() override {
|
|
1738
|
+
auto app = get_shared_application();
|
|
1739
|
+
objc::msg_send<void>(app, "run"_sel);
|
|
1740
|
+
}
|
|
1741
|
+
void dispatch_impl(std::function<void()> f) override {
|
|
1742
|
+
dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f),
|
|
1743
|
+
(dispatch_function_t)([](void *arg) {
|
|
1744
|
+
auto f = static_cast<dispatch_fn_t *>(arg);
|
|
1745
|
+
(*f)();
|
|
1746
|
+
delete f;
|
|
1747
|
+
}));
|
|
1748
|
+
}
|
|
1749
|
+
void set_title_impl(const std::string &title) override {
|
|
1750
|
+
objc::autoreleasepool arp;
|
|
1751
|
+
|
|
1752
|
+
objc::msg_send<void>(m_window, "setTitle:"_sel,
|
|
1753
|
+
objc::msg_send<id>("NSString"_cls,
|
|
1754
|
+
"stringWithUTF8String:"_sel,
|
|
1755
|
+
title.c_str()));
|
|
1756
|
+
}
|
|
1757
|
+
void set_size_impl(int width, int height, webview_hint_t hints) override {
|
|
1758
|
+
objc::autoreleasepool arp;
|
|
1759
|
+
|
|
1760
|
+
auto style = static_cast<NSWindowStyleMask>(
|
|
1761
|
+
NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
|
1762
|
+
NSWindowStyleMaskMiniaturizable);
|
|
1763
|
+
if (hints != WEBVIEW_HINT_FIXED) {
|
|
1764
|
+
style =
|
|
1765
|
+
static_cast<NSWindowStyleMask>(style | NSWindowStyleMaskResizable);
|
|
1766
|
+
}
|
|
1767
|
+
objc::msg_send<void>(m_window, "setStyleMask:"_sel, style);
|
|
1768
|
+
|
|
1769
|
+
if (hints == WEBVIEW_HINT_MIN) {
|
|
1770
|
+
objc::msg_send<void>(m_window, "setContentMinSize:"_sel,
|
|
1771
|
+
CGSizeMake(width, height));
|
|
1772
|
+
} else if (hints == WEBVIEW_HINT_MAX) {
|
|
1773
|
+
objc::msg_send<void>(m_window, "setContentMaxSize:"_sel,
|
|
1774
|
+
CGSizeMake(width, height));
|
|
1775
|
+
} else {
|
|
1776
|
+
objc::msg_send<void>(m_window, "setFrame:display:animate:"_sel,
|
|
1777
|
+
CGRectMake(0, 0, width, height), YES, NO);
|
|
1778
|
+
}
|
|
1779
|
+
objc::msg_send<void>(m_window, "center"_sel);
|
|
1780
|
+
}
|
|
1781
|
+
// frameless 模式:隐藏标题栏 + 标题栏透明 + 移除原生最小化/关闭按钮。
|
|
1782
|
+
// 保留 NSWindowStyleMaskTitled(配合透明标题栏承载前端拖动区域),
|
|
1783
|
+
// 前端通过 freedom.window.* 自绘最小化/最大化/关闭按钮。
|
|
1784
|
+
void set_decorated_impl(bool decorated) override {
|
|
1785
|
+
objc::autoreleasepool arp;
|
|
1786
|
+
if (decorated) {
|
|
1787
|
+
auto style = static_cast<NSWindowStyleMask>(
|
|
1788
|
+
NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
|
1789
|
+
NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable);
|
|
1790
|
+
objc::msg_send<void>(m_window, "setStyleMask:"_sel, style);
|
|
1791
|
+
objc::msg_send<void>(m_window, "setTitleVisibility:"_sel,
|
|
1792
|
+
NSWindowTitleVisible);
|
|
1793
|
+
objc::msg_send<void>(m_window, "setTitlebarAppearsTransparent:"_sel, NO);
|
|
1794
|
+
objc::msg_send<void>(m_window, "setMovableByWindowBackground:"_sel, NO);
|
|
1795
|
+
} else {
|
|
1796
|
+
auto style = static_cast<NSWindowStyleMask>(
|
|
1797
|
+
NSWindowStyleMaskTitled | NSWindowStyleMaskResizable);
|
|
1798
|
+
objc::msg_send<void>(m_window, "setStyleMask:"_sel, style);
|
|
1799
|
+
objc::msg_send<void>(m_window, "setTitleVisibility:"_sel,
|
|
1800
|
+
NSWindowTitleHidden);
|
|
1801
|
+
objc::msg_send<void>(m_window, "setTitlebarAppearsTransparent:"_sel, YES);
|
|
1802
|
+
objc::msg_send<void>(m_window, "setMovableByWindowBackground:"_sel, YES);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
// Cocoa 原生窗口控制:最小化走 performMiniaturize:,最大化/还原走 zoom:
|
|
1806
|
+
// (macOS 标准"最大化"语义,铺满当前屏幕可用区,非全屏独占),
|
|
1807
|
+
// 关闭走 performClose:,最大化状态用 isZoomed 查询。
|
|
1808
|
+
int window_control_impl(int action) override {
|
|
1809
|
+
objc::autoreleasepool arp;
|
|
1810
|
+
switch ((webview_window_action_t)action) {
|
|
1811
|
+
case WEBVIEW_WINDOW_ACTION_MINIMIZE:
|
|
1812
|
+
objc::msg_send<void>(m_window, "performMiniaturize:"_sel, nullptr);
|
|
1813
|
+
return 0;
|
|
1814
|
+
case WEBVIEW_WINDOW_ACTION_MAXIMIZE:
|
|
1815
|
+
if (!objc::msg_send<BOOL>(m_window, "isZoomed"_sel)) {
|
|
1816
|
+
objc::msg_send<void>(m_window, "zoom:"_sel, nullptr);
|
|
1817
|
+
}
|
|
1818
|
+
return 0;
|
|
1819
|
+
case WEBVIEW_WINDOW_ACTION_UNMAXIMIZE:
|
|
1820
|
+
case WEBVIEW_WINDOW_ACTION_RESTORE:
|
|
1821
|
+
if (objc::msg_send<BOOL>(m_window, "isZoomed"_sel)) {
|
|
1822
|
+
objc::msg_send<void>(m_window, "zoom:"_sel, nullptr);
|
|
1823
|
+
}
|
|
1824
|
+
objc::msg_send<void>(m_window, "deminiaturize:"_sel, nullptr);
|
|
1825
|
+
return 0;
|
|
1826
|
+
case WEBVIEW_WINDOW_ACTION_CLOSE:
|
|
1827
|
+
objc::msg_send<void>(m_window, "performClose:"_sel, nullptr);
|
|
1828
|
+
return 0;
|
|
1829
|
+
case WEBVIEW_WINDOW_ACTION_TOGGLE_MAXIMIZE:
|
|
1830
|
+
objc::msg_send<void>(m_window, "zoom:"_sel, nullptr);
|
|
1831
|
+
return 0;
|
|
1832
|
+
case WEBVIEW_WINDOW_ACTION_IS_MAXIMIZED:
|
|
1833
|
+
return objc::msg_send<BOOL>(m_window, "isZoomed"_sel) ? 1 : 0;
|
|
1834
|
+
default:
|
|
1835
|
+
return -1;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// 无边框窗口拖动:用当前鼠标屏幕位置合成 NSLeftMouseDragged 事件,
|
|
1840
|
+
// 调 performWindowDragWithEvent: 进入 AppKit 原生窗口拖动循环。
|
|
1841
|
+
int begin_move_drag_impl() override {
|
|
1842
|
+
objc::autoreleasepool arp;
|
|
1843
|
+
auto loc = objc::msg_send<CGPoint>("NSEvent"_cls, "mouseLocation"_sel);
|
|
1844
|
+
auto wnum = objc::msg_send<NSInteger>(m_window, "windowNumber"_sel);
|
|
1845
|
+
auto evt = objc::msg_send<id>(
|
|
1846
|
+
"NSEvent"_cls,
|
|
1847
|
+
"mouseEventWithType:location:modifierFlags:timestamp:windowNumber:context:eventNumber:clickCount:pressure:"_sel,
|
|
1848
|
+
(NSUInteger)1 /*NSLeftMouseDragged*/, loc, (NSUInteger)0, (double)0.0,
|
|
1849
|
+
wnum, (id)nullptr, (NSInteger)0, (NSInteger)1, (float)1.0);
|
|
1850
|
+
objc::msg_send<void>(m_window, "performWindowDragWithEvent:"_sel, evt);
|
|
1851
|
+
return 0;
|
|
1852
|
+
}
|
|
1853
|
+
void navigate_impl(const std::string &url) override {
|
|
1854
|
+
objc::autoreleasepool arp;
|
|
1855
|
+
|
|
1856
|
+
auto nsurl = objc::msg_send<id>(
|
|
1857
|
+
"NSURL"_cls, "URLWithString:"_sel,
|
|
1858
|
+
objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel,
|
|
1859
|
+
url.c_str()));
|
|
1860
|
+
|
|
1861
|
+
objc::msg_send<void>(
|
|
1862
|
+
m_webview, "loadRequest:"_sel,
|
|
1863
|
+
objc::msg_send<id>("NSURLRequest"_cls, "requestWithURL:"_sel, nsurl));
|
|
1864
|
+
}
|
|
1865
|
+
void set_html_impl(const std::string &html) override {
|
|
1866
|
+
objc::autoreleasepool arp;
|
|
1867
|
+
objc::msg_send<void>(m_webview, "loadHTMLString:baseURL:"_sel,
|
|
1868
|
+
objc::msg_send<id>("NSString"_cls,
|
|
1869
|
+
"stringWithUTF8String:"_sel,
|
|
1870
|
+
html.c_str()),
|
|
1871
|
+
nullptr);
|
|
1872
|
+
}
|
|
1873
|
+
void init_impl(const std::string &js) override {
|
|
1874
|
+
objc::autoreleasepool arp;
|
|
1875
|
+
auto script = objc::autoreleased(objc::msg_send<id>(
|
|
1876
|
+
objc::msg_send<id>("WKUserScript"_cls, "alloc"_sel),
|
|
1877
|
+
"initWithSource:injectionTime:forMainFrameOnly:"_sel,
|
|
1878
|
+
objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel,
|
|
1879
|
+
js.c_str()),
|
|
1880
|
+
WKUserScriptInjectionTimeAtDocumentStart, YES));
|
|
1881
|
+
objc::msg_send<void>(m_manager, "addUserScript:"_sel, script);
|
|
1882
|
+
}
|
|
1883
|
+
void eval_impl(const std::string &js) override {
|
|
1884
|
+
objc::autoreleasepool arp;
|
|
1885
|
+
objc::msg_send<void>(m_webview, "evaluateJavaScript:completionHandler:"_sel,
|
|
1886
|
+
objc::msg_send<id>("NSString"_cls,
|
|
1887
|
+
"stringWithUTF8String:"_sel,
|
|
1888
|
+
js.c_str()),
|
|
1889
|
+
nullptr);
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
private:
|
|
1893
|
+
id create_app_delegate() {
|
|
1894
|
+
objc::autoreleasepool arp;
|
|
1895
|
+
constexpr auto class_name = "WebviewAppDelegate";
|
|
1896
|
+
// Avoid crash due to registering same class twice
|
|
1897
|
+
auto cls = objc_lookUpClass(class_name);
|
|
1898
|
+
if (!cls) {
|
|
1899
|
+
// Note: Avoid registering the class name "AppDelegate" as it is the
|
|
1900
|
+
// default name in projects created with Xcode, and using the same name
|
|
1901
|
+
// causes objc_registerClassPair to crash.
|
|
1902
|
+
cls = objc_allocateClassPair((Class) "NSResponder"_cls, class_name, 0);
|
|
1903
|
+
class_addProtocol(cls, objc_getProtocol("NSTouchBarProvider"));
|
|
1904
|
+
class_addMethod(cls,
|
|
1905
|
+
"applicationShouldTerminateAfterLastWindowClosed:"_sel,
|
|
1906
|
+
(IMP)(+[](id, SEL, id) -> BOOL { return NO; }), "c@:@");
|
|
1907
|
+
class_addMethod(cls, "applicationDidFinishLaunching:"_sel,
|
|
1908
|
+
(IMP)(+[](id self, SEL, id notification) {
|
|
1909
|
+
auto app =
|
|
1910
|
+
objc::msg_send<id>(notification, "object"_sel);
|
|
1911
|
+
auto w = get_associated_webview(self);
|
|
1912
|
+
w->on_application_did_finish_launching(self, app);
|
|
1913
|
+
}),
|
|
1914
|
+
"v@:@");
|
|
1915
|
+
objc_registerClassPair(cls);
|
|
1916
|
+
}
|
|
1917
|
+
return objc::msg_send<id>((id)cls, "new"_sel);
|
|
1918
|
+
}
|
|
1919
|
+
id create_script_message_handler() {
|
|
1920
|
+
objc::autoreleasepool arp;
|
|
1921
|
+
constexpr auto class_name = "WebviewWKScriptMessageHandler";
|
|
1922
|
+
// Avoid crash due to registering same class twice
|
|
1923
|
+
auto cls = objc_lookUpClass(class_name);
|
|
1924
|
+
if (!cls) {
|
|
1925
|
+
cls = objc_allocateClassPair((Class) "NSResponder"_cls, class_name, 0);
|
|
1926
|
+
class_addProtocol(cls, objc_getProtocol("WKScriptMessageHandler"));
|
|
1927
|
+
class_addMethod(
|
|
1928
|
+
cls, "userContentController:didReceiveScriptMessage:"_sel,
|
|
1929
|
+
(IMP)(+[](id self, SEL, id, id msg) {
|
|
1930
|
+
auto w = get_associated_webview(self);
|
|
1931
|
+
w->on_message(objc::msg_send<const char *>(
|
|
1932
|
+
objc::msg_send<id>(msg, "body"_sel), "UTF8String"_sel));
|
|
1933
|
+
}),
|
|
1934
|
+
"v@:@@");
|
|
1935
|
+
objc_registerClassPair(cls);
|
|
1936
|
+
}
|
|
1937
|
+
auto instance = objc::msg_send<id>((id)cls, "new"_sel);
|
|
1938
|
+
objc_setAssociatedObject(instance, "webview", (id)this,
|
|
1939
|
+
OBJC_ASSOCIATION_ASSIGN);
|
|
1940
|
+
return instance;
|
|
1941
|
+
}
|
|
1942
|
+
static id create_webkit_ui_delegate() {
|
|
1943
|
+
objc::autoreleasepool arp;
|
|
1944
|
+
constexpr auto class_name = "WebviewWKUIDelegate";
|
|
1945
|
+
// Avoid crash due to registering same class twice
|
|
1946
|
+
auto cls = objc_lookUpClass(class_name);
|
|
1947
|
+
if (!cls) {
|
|
1948
|
+
cls = objc_allocateClassPair((Class) "NSObject"_cls, class_name, 0);
|
|
1949
|
+
class_addProtocol(cls, objc_getProtocol("WKUIDelegate"));
|
|
1950
|
+
class_addMethod(
|
|
1951
|
+
cls,
|
|
1952
|
+
"webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:"_sel,
|
|
1953
|
+
(IMP)(+[](id, SEL, id, id parameters, id, id completion_handler) {
|
|
1954
|
+
auto allows_multiple_selection =
|
|
1955
|
+
objc::msg_send<BOOL>(parameters, "allowsMultipleSelection"_sel);
|
|
1956
|
+
auto allows_directories =
|
|
1957
|
+
objc::msg_send<BOOL>(parameters, "allowsDirectories"_sel);
|
|
1958
|
+
|
|
1959
|
+
// Show a panel for selecting files.
|
|
1960
|
+
auto panel = objc::msg_send<id>("NSOpenPanel"_cls, "openPanel"_sel);
|
|
1961
|
+
objc::msg_send<void>(panel, "setCanChooseFiles:"_sel, YES);
|
|
1962
|
+
objc::msg_send<void>(panel, "setCanChooseDirectories:"_sel,
|
|
1963
|
+
allows_directories);
|
|
1964
|
+
objc::msg_send<void>(panel, "setAllowsMultipleSelection:"_sel,
|
|
1965
|
+
allows_multiple_selection);
|
|
1966
|
+
auto modal_response =
|
|
1967
|
+
objc::msg_send<NSModalResponse>(panel, "runModal"_sel);
|
|
1968
|
+
|
|
1969
|
+
// Get the URLs for the selected files. If the modal was canceled
|
|
1970
|
+
// then we pass null to the completion handler to signify
|
|
1971
|
+
// cancellation.
|
|
1972
|
+
id urls = modal_response == NSModalResponseOK
|
|
1973
|
+
? objc::msg_send<id>(panel, "URLs"_sel)
|
|
1974
|
+
: nullptr;
|
|
1975
|
+
|
|
1976
|
+
// Invoke the completion handler block.
|
|
1977
|
+
auto sig = objc::msg_send<id>(
|
|
1978
|
+
"NSMethodSignature"_cls, "signatureWithObjCTypes:"_sel, "v@?@");
|
|
1979
|
+
auto invocation = objc::msg_send<id>(
|
|
1980
|
+
"NSInvocation"_cls, "invocationWithMethodSignature:"_sel, sig);
|
|
1981
|
+
objc::msg_send<void>(invocation, "setTarget:"_sel,
|
|
1982
|
+
completion_handler);
|
|
1983
|
+
objc::msg_send<void>(invocation, "setArgument:atIndex:"_sel, &urls,
|
|
1984
|
+
1);
|
|
1985
|
+
objc::msg_send<void>(invocation, "invoke"_sel);
|
|
1986
|
+
}),
|
|
1987
|
+
"v@:@@@@");
|
|
1988
|
+
objc_registerClassPair(cls);
|
|
1989
|
+
}
|
|
1990
|
+
return objc::msg_send<id>((id)cls, "new"_sel);
|
|
1991
|
+
}
|
|
1992
|
+
static id create_window_delegate() {
|
|
1993
|
+
objc::autoreleasepool arp;
|
|
1994
|
+
constexpr auto class_name = "WebviewNSWindowDelegate";
|
|
1995
|
+
// Avoid crash due to registering same class twice
|
|
1996
|
+
auto cls = objc_lookUpClass(class_name);
|
|
1997
|
+
if (!cls) {
|
|
1998
|
+
cls = objc_allocateClassPair((Class) "NSObject"_cls, class_name, 0);
|
|
1999
|
+
class_addProtocol(cls, objc_getProtocol("NSWindowDelegate"));
|
|
2000
|
+
class_addMethod(cls, "windowWillClose:"_sel,
|
|
2001
|
+
(IMP)(+[](id self, SEL, id notification) {
|
|
2002
|
+
auto window =
|
|
2003
|
+
objc::msg_send<id>(notification, "object"_sel);
|
|
2004
|
+
auto w = get_associated_webview(self);
|
|
2005
|
+
w->on_window_will_close(self, window);
|
|
2006
|
+
}),
|
|
2007
|
+
"v@:@");
|
|
2008
|
+
objc_registerClassPair(cls);
|
|
2009
|
+
}
|
|
2010
|
+
return objc::msg_send<id>((id)cls, "new"_sel);
|
|
2011
|
+
}
|
|
2012
|
+
static id get_shared_application() {
|
|
2013
|
+
return objc::msg_send<id>("NSApplication"_cls, "sharedApplication"_sel);
|
|
2014
|
+
}
|
|
2015
|
+
static cocoa_wkwebview_engine *get_associated_webview(id object) {
|
|
2016
|
+
auto w =
|
|
2017
|
+
(cocoa_wkwebview_engine *)objc_getAssociatedObject(object, "webview");
|
|
2018
|
+
assert(w);
|
|
2019
|
+
return w;
|
|
2020
|
+
}
|
|
2021
|
+
static id get_main_bundle() noexcept {
|
|
2022
|
+
return objc::msg_send<id>("NSBundle"_cls, "mainBundle"_sel);
|
|
2023
|
+
}
|
|
2024
|
+
static bool is_app_bundled() noexcept {
|
|
2025
|
+
auto bundle = get_main_bundle();
|
|
2026
|
+
if (!bundle) {
|
|
2027
|
+
return false;
|
|
2028
|
+
}
|
|
2029
|
+
auto bundle_path = objc::msg_send<id>(bundle, "bundlePath"_sel);
|
|
2030
|
+
auto bundled =
|
|
2031
|
+
objc::msg_send<BOOL>(bundle_path, "hasSuffix:"_sel, ".app"_str);
|
|
2032
|
+
return !!bundled;
|
|
2033
|
+
}
|
|
2034
|
+
void on_application_did_finish_launching(id /*delegate*/, id app) {
|
|
2035
|
+
// See comments related to application lifecycle in create_app_delegate().
|
|
2036
|
+
if (m_owns_window) {
|
|
2037
|
+
// Stop the main run loop so that we can return
|
|
2038
|
+
// from the constructor.
|
|
2039
|
+
stop_run_loop();
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
// Activate the app if it is not bundled.
|
|
2043
|
+
// Bundled apps launched from Finder are activated automatically but
|
|
2044
|
+
// otherwise not. Activating the app even when it has been launched from
|
|
2045
|
+
// Finder does not seem to be harmful but calling this function is rarely
|
|
2046
|
+
// needed as proper activation is normally taken care of for us.
|
|
2047
|
+
// Bundled apps have a default activation policy of
|
|
2048
|
+
// NSApplicationActivationPolicyRegular while non-bundled apps have a
|
|
2049
|
+
// default activation policy of NSApplicationActivationPolicyProhibited.
|
|
2050
|
+
if (!is_app_bundled()) {
|
|
2051
|
+
// "setActivationPolicy:" must be invoked before
|
|
2052
|
+
// "activateIgnoringOtherApps:" for activation to work.
|
|
2053
|
+
objc::msg_send<void>(app, "setActivationPolicy:"_sel,
|
|
2054
|
+
NSApplicationActivationPolicyRegular);
|
|
2055
|
+
// Activate the app regardless of other active apps.
|
|
2056
|
+
// This can be obtrusive so we only do it when necessary.
|
|
2057
|
+
objc::msg_send<void>(app, "activateIgnoringOtherApps:"_sel, YES);
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
set_up_window();
|
|
2061
|
+
}
|
|
2062
|
+
void on_window_will_close(id /*delegate*/, id /*window*/) {
|
|
2063
|
+
// Widget destroyed along with window.
|
|
2064
|
+
m_webview = nullptr;
|
|
2065
|
+
m_window = nullptr;
|
|
2066
|
+
dispatch([this] { on_window_destroyed(); });
|
|
2067
|
+
}
|
|
2068
|
+
void set_up_window() {
|
|
2069
|
+
objc::autoreleasepool arp;
|
|
2070
|
+
|
|
2071
|
+
// Main window
|
|
2072
|
+
if (m_owns_window) {
|
|
2073
|
+
m_window = objc::msg_send<id>("NSWindow"_cls, "alloc"_sel);
|
|
2074
|
+
auto style = NSWindowStyleMaskTitled;
|
|
2075
|
+
m_window = objc::msg_send<id>(
|
|
2076
|
+
m_window, "initWithContentRect:styleMask:backing:defer:"_sel,
|
|
2077
|
+
CGRectMake(0, 0, 0, 0), style, NSBackingStoreBuffered, NO);
|
|
2078
|
+
|
|
2079
|
+
m_window_delegate = create_window_delegate();
|
|
2080
|
+
objc_setAssociatedObject(m_window_delegate, "webview", (id)this,
|
|
2081
|
+
OBJC_ASSOCIATION_ASSIGN);
|
|
2082
|
+
objc::msg_send<void>(m_window, "setDelegate:"_sel, m_window_delegate);
|
|
2083
|
+
|
|
2084
|
+
on_window_created();
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
set_up_web_view();
|
|
2088
|
+
|
|
2089
|
+
objc::msg_send<void>(m_window, "setContentView:"_sel, m_webview);
|
|
2090
|
+
|
|
2091
|
+
if (m_owns_window) {
|
|
2092
|
+
objc::msg_send<void>(m_window, "makeKeyAndOrderFront:"_sel, nullptr);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
void set_up_web_view() {
|
|
2096
|
+
objc::autoreleasepool arp;
|
|
2097
|
+
|
|
2098
|
+
auto config = objc::autoreleased(
|
|
2099
|
+
objc::msg_send<id>("WKWebViewConfiguration"_cls, "new"_sel));
|
|
2100
|
+
|
|
2101
|
+
m_manager = objc::msg_send<id>(config, "userContentController"_sel);
|
|
2102
|
+
m_webview = objc::msg_send<id>("WKWebView"_cls, "alloc"_sel);
|
|
2103
|
+
|
|
2104
|
+
auto preferences = objc::msg_send<id>(config, "preferences"_sel);
|
|
2105
|
+
auto yes_value =
|
|
2106
|
+
objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES);
|
|
2107
|
+
|
|
2108
|
+
if (m_debug) {
|
|
2109
|
+
// Equivalent Obj-C:
|
|
2110
|
+
// [[config preferences] setValue:@YES forKey:@"developerExtrasEnabled"];
|
|
2111
|
+
objc::msg_send<id>(preferences, "setValue:forKey:"_sel, yes_value,
|
|
2112
|
+
"developerExtrasEnabled"_str);
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
// Equivalent Obj-C:
|
|
2116
|
+
// [[config preferences] setValue:@YES forKey:@"fullScreenEnabled"];
|
|
2117
|
+
objc::msg_send<id>(preferences, "setValue:forKey:"_sel, yes_value,
|
|
2118
|
+
"fullScreenEnabled"_str);
|
|
2119
|
+
|
|
2120
|
+
// Equivalent Obj-C:
|
|
2121
|
+
// [[config preferences] setValue:@YES forKey:@"javaScriptCanAccessClipboard"];
|
|
2122
|
+
objc::msg_send<id>(preferences, "setValue:forKey:"_sel, yes_value,
|
|
2123
|
+
"javaScriptCanAccessClipboard"_str);
|
|
2124
|
+
|
|
2125
|
+
// Equivalent Obj-C:
|
|
2126
|
+
// [[config preferences] setValue:@YES forKey:@"DOMPasteAllowed"];
|
|
2127
|
+
objc::msg_send<id>(preferences, "setValue:forKey:"_sel, yes_value,
|
|
2128
|
+
"DOMPasteAllowed"_str);
|
|
2129
|
+
|
|
2130
|
+
auto ui_delegate = objc::autoreleased(create_webkit_ui_delegate());
|
|
2131
|
+
objc::msg_send<void>(m_webview, "initWithFrame:configuration:"_sel,
|
|
2132
|
+
CGRectMake(0, 0, 0, 0), config);
|
|
2133
|
+
objc::msg_send<void>(m_webview, "setUIDelegate:"_sel, ui_delegate);
|
|
2134
|
+
|
|
2135
|
+
if (m_debug) {
|
|
2136
|
+
// Explicitly make WKWebView inspectable via Safari on OS versions that
|
|
2137
|
+
// disable the feature by default (macOS 13.3 and later) and support
|
|
2138
|
+
// enabling it. According to Apple, the behavior on older OS versions is
|
|
2139
|
+
// for content to always be inspectable in "debug builds".
|
|
2140
|
+
// Testing shows that this is true for macOS 12.6 but somehow not 10.15.
|
|
2141
|
+
// https://webkit.org/blog/13936/enabling-the-inspection-of-web-content-in-apps/
|
|
2142
|
+
#if defined(__has_builtin)
|
|
2143
|
+
#if __has_builtin(__builtin_available)
|
|
2144
|
+
if (__builtin_available(macOS 13.3, iOS 16.4, tvOS 16.4, *)) {
|
|
2145
|
+
objc::msg_send<void>(
|
|
2146
|
+
m_webview, "setInspectable:"_sel,
|
|
2147
|
+
objc::msg_send<id>("NSNumber"_cls, "numberWithBool:"_sel, YES));
|
|
2148
|
+
}
|
|
2149
|
+
#else
|
|
2150
|
+
#error __builtin_available not supported by compiler
|
|
2151
|
+
#endif
|
|
2152
|
+
#else
|
|
2153
|
+
#error __has_builtin not supported by compiler
|
|
2154
|
+
#endif
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
auto script_message_handler =
|
|
2158
|
+
objc::autoreleased(create_script_message_handler());
|
|
2159
|
+
objc::msg_send<void>(m_manager, "addScriptMessageHandler:name:"_sel,
|
|
2160
|
+
script_message_handler, "external"_str);
|
|
2161
|
+
|
|
2162
|
+
init(R""(
|
|
2163
|
+
window.external = {
|
|
2164
|
+
invoke: function(s) {
|
|
2165
|
+
window.webkit.messageHandlers.external.postMessage(s);
|
|
2166
|
+
},
|
|
2167
|
+
};
|
|
2168
|
+
)"");
|
|
2169
|
+
}
|
|
2170
|
+
void stop_run_loop() {
|
|
2171
|
+
objc::autoreleasepool arp;
|
|
2172
|
+
auto app = get_shared_application();
|
|
2173
|
+
// Request the run loop to stop. This doesn't immediately stop the loop.
|
|
2174
|
+
objc::msg_send<void>(app, "stop:"_sel, nullptr);
|
|
2175
|
+
// The run loop will stop after processing an NSEvent.
|
|
2176
|
+
// Event type: NSEventTypeApplicationDefined (macOS 10.12+),
|
|
2177
|
+
// NSApplicationDefined (macOS 10.0–10.12)
|
|
2178
|
+
int type = 15;
|
|
2179
|
+
auto event = objc::msg_send<id>(
|
|
2180
|
+
"NSEvent"_cls,
|
|
2181
|
+
"otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:"_sel,
|
|
2182
|
+
type, CGPointMake(0, 0), 0, 0, 0, nullptr, 0, 0, 0);
|
|
2183
|
+
objc::msg_send<void>(app, "postEvent:atStart:"_sel, event, YES);
|
|
2184
|
+
}
|
|
2185
|
+
static bool get_and_set_is_first_instance() noexcept {
|
|
2186
|
+
static std::atomic_bool first{true};
|
|
2187
|
+
bool temp = first;
|
|
2188
|
+
if (temp) {
|
|
2189
|
+
first = false;
|
|
2190
|
+
}
|
|
2191
|
+
return temp;
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
// Blocks while depleting the run loop of events.
|
|
2195
|
+
void deplete_run_loop_event_queue() {
|
|
2196
|
+
objc::autoreleasepool arp;
|
|
2197
|
+
auto app = get_shared_application();
|
|
2198
|
+
bool done{};
|
|
2199
|
+
dispatch([&] { done = true; });
|
|
2200
|
+
auto mask = NSUIntegerMax; // NSEventMaskAny
|
|
2201
|
+
// NSDefaultRunLoopMode
|
|
2202
|
+
auto mode = objc::msg_send<id>("NSString"_cls, "stringWithUTF8String:"_sel,
|
|
2203
|
+
"kCFRunLoopDefaultMode");
|
|
2204
|
+
while (!done) {
|
|
2205
|
+
objc::autoreleasepool arp;
|
|
2206
|
+
auto event = objc::msg_send<id>(
|
|
2207
|
+
app, "nextEventMatchingMask:untilDate:inMode:dequeue:"_sel, mask,
|
|
2208
|
+
nullptr, mode, YES);
|
|
2209
|
+
if (event) {
|
|
2210
|
+
objc::msg_send<void>(app, "sendEvent:"_sel, event);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
bool m_debug{};
|
|
2216
|
+
id m_app_delegate{};
|
|
2217
|
+
id m_window_delegate{};
|
|
2218
|
+
id m_window{};
|
|
2219
|
+
id m_webview{};
|
|
2220
|
+
id m_manager{};
|
|
2221
|
+
bool m_owns_window{};
|
|
2222
|
+
};
|
|
2223
|
+
|
|
2224
|
+
} // namespace detail
|
|
2225
|
+
|
|
2226
|
+
using browser_engine = detail::cocoa_wkwebview_engine;
|
|
2227
|
+
|
|
2228
|
+
} // namespace webview
|
|
2229
|
+
|
|
2230
|
+
#elif defined(WEBVIEW_EDGE)
|
|
2231
|
+
|
|
2232
|
+
//
|
|
2233
|
+
// ====================================================================
|
|
2234
|
+
//
|
|
2235
|
+
// This implementation uses Win32 API to create a native window. It
|
|
2236
|
+
// uses Edge/Chromium webview2 backend as a browser engine.
|
|
2237
|
+
//
|
|
2238
|
+
// ====================================================================
|
|
2239
|
+
//
|
|
2240
|
+
|
|
2241
|
+
#define WIN32_LEAN_AND_MEAN
|
|
2242
|
+
#include <shlobj.h>
|
|
2243
|
+
#include <shlwapi.h>
|
|
2244
|
+
#include <stdlib.h>
|
|
2245
|
+
#include <windows.h>
|
|
2246
|
+
|
|
2247
|
+
#include "WebView2.h"
|
|
2248
|
+
|
|
2249
|
+
#ifdef _MSC_VER
|
|
2250
|
+
#pragma comment(lib, "advapi32.lib")
|
|
2251
|
+
#pragma comment(lib, "ole32.lib")
|
|
2252
|
+
#pragma comment(lib, "shell32.lib")
|
|
2253
|
+
#pragma comment(lib, "shlwapi.lib")
|
|
2254
|
+
#pragma comment(lib, "user32.lib")
|
|
2255
|
+
#pragma comment(lib, "version.lib")
|
|
2256
|
+
#endif
|
|
2257
|
+
|
|
2258
|
+
namespace webview {
|
|
2259
|
+
namespace detail {
|
|
2260
|
+
|
|
2261
|
+
using msg_cb_t = std::function<void(const std::string)>;
|
|
2262
|
+
|
|
2263
|
+
// Parses a version string with 1-4 integral components, e.g. "1.2.3.4".
|
|
2264
|
+
// Missing or invalid components default to 0, and excess components are ignored.
|
|
2265
|
+
template <typename T>
|
|
2266
|
+
std::array<unsigned int, 4>
|
|
2267
|
+
parse_version(const std::basic_string<T> &version) noexcept {
|
|
2268
|
+
auto parse_component = [](auto sb, auto se) -> unsigned int {
|
|
2269
|
+
try {
|
|
2270
|
+
auto n = std::stol(std::basic_string<T>(sb, se));
|
|
2271
|
+
return n < 0 ? 0 : n;
|
|
2272
|
+
} catch (std::exception &) {
|
|
2273
|
+
return 0;
|
|
2274
|
+
}
|
|
2275
|
+
};
|
|
2276
|
+
auto end = version.end();
|
|
2277
|
+
auto sb = version.begin(); // subrange begin
|
|
2278
|
+
auto se = sb; // subrange end
|
|
2279
|
+
unsigned int ci = 0; // component index
|
|
2280
|
+
std::array<unsigned int, 4> components{};
|
|
2281
|
+
while (sb != end && se != end && ci < components.size()) {
|
|
2282
|
+
if (*se == static_cast<T>('.')) {
|
|
2283
|
+
components[ci++] = parse_component(sb, se);
|
|
2284
|
+
sb = ++se;
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
++se;
|
|
2288
|
+
}
|
|
2289
|
+
if (sb < se && ci < components.size()) {
|
|
2290
|
+
components[ci] = parse_component(sb, se);
|
|
2291
|
+
}
|
|
2292
|
+
return components;
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
template <typename T, std::size_t Length>
|
|
2296
|
+
auto parse_version(const T (&version)[Length]) noexcept {
|
|
2297
|
+
return parse_version(std::basic_string<T>(version, Length));
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
std::wstring get_file_version_string(const std::wstring &file_path) noexcept {
|
|
2301
|
+
DWORD dummy_handle; // Unused
|
|
2302
|
+
DWORD info_buffer_length =
|
|
2303
|
+
GetFileVersionInfoSizeW(file_path.c_str(), &dummy_handle);
|
|
2304
|
+
if (info_buffer_length == 0) {
|
|
2305
|
+
return std::wstring();
|
|
2306
|
+
}
|
|
2307
|
+
std::vector<char> info_buffer;
|
|
2308
|
+
info_buffer.reserve(info_buffer_length);
|
|
2309
|
+
if (!GetFileVersionInfoW(file_path.c_str(), 0, info_buffer_length,
|
|
2310
|
+
info_buffer.data())) {
|
|
2311
|
+
return std::wstring();
|
|
2312
|
+
}
|
|
2313
|
+
auto sub_block = L"\\StringFileInfo\\040904B0\\ProductVersion";
|
|
2314
|
+
LPWSTR version = nullptr;
|
|
2315
|
+
unsigned int version_length = 0;
|
|
2316
|
+
if (!VerQueryValueW(info_buffer.data(), sub_block,
|
|
2317
|
+
reinterpret_cast<LPVOID *>(&version), &version_length)) {
|
|
2318
|
+
return std::wstring();
|
|
2319
|
+
}
|
|
2320
|
+
if (!version || version_length == 0) {
|
|
2321
|
+
return std::wstring();
|
|
2322
|
+
}
|
|
2323
|
+
return std::wstring(version, version_length);
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
// A wrapper around COM library initialization. Calls CoInitializeEx in the
|
|
2327
|
+
// constructor and CoUninitialize in the destructor.
|
|
2328
|
+
class com_init_wrapper {
|
|
2329
|
+
public:
|
|
2330
|
+
com_init_wrapper() = default;
|
|
2331
|
+
|
|
2332
|
+
com_init_wrapper(DWORD dwCoInit) {
|
|
2333
|
+
// We can safely continue as long as COM was either successfully
|
|
2334
|
+
// initialized or already initialized.
|
|
2335
|
+
// RPC_E_CHANGED_MODE means that CoInitializeEx was already called with
|
|
2336
|
+
// a different concurrency model.
|
|
2337
|
+
switch (CoInitializeEx(nullptr, dwCoInit)) {
|
|
2338
|
+
case S_OK:
|
|
2339
|
+
case S_FALSE:
|
|
2340
|
+
m_initialized = true;
|
|
2341
|
+
break;
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
~com_init_wrapper() {
|
|
2346
|
+
if (m_initialized) {
|
|
2347
|
+
CoUninitialize();
|
|
2348
|
+
m_initialized = false;
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
com_init_wrapper(const com_init_wrapper &other) = delete;
|
|
2353
|
+
com_init_wrapper &operator=(const com_init_wrapper &other) = delete;
|
|
2354
|
+
com_init_wrapper(com_init_wrapper &&other) { *this = std::move(other); }
|
|
2355
|
+
|
|
2356
|
+
com_init_wrapper &operator=(com_init_wrapper &&other) {
|
|
2357
|
+
if (this == &other) {
|
|
2358
|
+
return *this;
|
|
2359
|
+
}
|
|
2360
|
+
m_initialized = std::exchange(other.m_initialized, false);
|
|
2361
|
+
return *this;
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
bool is_initialized() const { return m_initialized; }
|
|
2365
|
+
|
|
2366
|
+
private:
|
|
2367
|
+
bool m_initialized = false;
|
|
2368
|
+
};
|
|
2369
|
+
|
|
2370
|
+
namespace ntdll_symbols {
|
|
2371
|
+
using RtlGetVersion_t =
|
|
2372
|
+
unsigned int /*NTSTATUS*/ (WINAPI *)(RTL_OSVERSIONINFOW *);
|
|
2373
|
+
|
|
2374
|
+
constexpr auto RtlGetVersion = library_symbol<RtlGetVersion_t>("RtlGetVersion");
|
|
2375
|
+
} // namespace ntdll_symbols
|
|
2376
|
+
|
|
2377
|
+
namespace user32_symbols {
|
|
2378
|
+
using DPI_AWARENESS_CONTEXT = HANDLE;
|
|
2379
|
+
using SetProcessDpiAwarenessContext_t = BOOL(WINAPI *)(DPI_AWARENESS_CONTEXT);
|
|
2380
|
+
using SetProcessDPIAware_t = BOOL(WINAPI *)();
|
|
2381
|
+
using GetDpiForWindow_t = UINT(WINAPI *)(HWND);
|
|
2382
|
+
using EnableNonClientDpiScaling_t = BOOL(WINAPI *)(HWND);
|
|
2383
|
+
using AdjustWindowRectExForDpi_t = BOOL(WINAPI *)(LPRECT, DWORD, BOOL, DWORD,
|
|
2384
|
+
UINT);
|
|
2385
|
+
using GetWindowDpiAwarenessContext_t = DPI_AWARENESS_CONTEXT(WINAPI *)(HWND);
|
|
2386
|
+
using AreDpiAwarenessContextsEqual_t = BOOL(WINAPI *)(DPI_AWARENESS_CONTEXT,
|
|
2387
|
+
DPI_AWARENESS_CONTEXT);
|
|
2388
|
+
|
|
2389
|
+
// Use intptr_t as the underlying type because we need to
|
|
2390
|
+
// reinterpret_cast<DPI_AWARENESS_CONTEXT> which is a pointer.
|
|
2391
|
+
// Available since Windows 10, version 1607
|
|
2392
|
+
enum class dpi_awareness : intptr_t {
|
|
2393
|
+
per_monitor_v2_aware = -4, // Available since Windows 10, version 1703
|
|
2394
|
+
per_monitor_aware = -3
|
|
2395
|
+
};
|
|
2396
|
+
|
|
2397
|
+
constexpr auto SetProcessDpiAwarenessContext =
|
|
2398
|
+
library_symbol<SetProcessDpiAwarenessContext_t>(
|
|
2399
|
+
"SetProcessDpiAwarenessContext");
|
|
2400
|
+
constexpr auto SetProcessDPIAware =
|
|
2401
|
+
library_symbol<SetProcessDPIAware_t>("SetProcessDPIAware");
|
|
2402
|
+
constexpr auto GetDpiForWindow =
|
|
2403
|
+
library_symbol<GetDpiForWindow_t>("GetDpiForWindow");
|
|
2404
|
+
constexpr auto EnableNonClientDpiScaling =
|
|
2405
|
+
library_symbol<EnableNonClientDpiScaling_t>("EnableNonClientDpiScaling");
|
|
2406
|
+
constexpr auto AdjustWindowRectExForDpi =
|
|
2407
|
+
library_symbol<AdjustWindowRectExForDpi_t>("AdjustWindowRectExForDpi");
|
|
2408
|
+
constexpr auto GetWindowDpiAwarenessContext =
|
|
2409
|
+
library_symbol<GetWindowDpiAwarenessContext_t>(
|
|
2410
|
+
"GetWindowDpiAwarenessContext");
|
|
2411
|
+
constexpr auto AreDpiAwarenessContextsEqual =
|
|
2412
|
+
library_symbol<AreDpiAwarenessContextsEqual_t>(
|
|
2413
|
+
"AreDpiAwarenessContextsEqual");
|
|
2414
|
+
} // namespace user32_symbols
|
|
2415
|
+
|
|
2416
|
+
namespace dwmapi_symbols {
|
|
2417
|
+
typedef enum {
|
|
2418
|
+
// This undocumented value is used instead of DWMWA_USE_IMMERSIVE_DARK_MODE
|
|
2419
|
+
// on Windows 10 older than build 19041 (2004/20H1).
|
|
2420
|
+
DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_V10_0_19041 = 19,
|
|
2421
|
+
// Documented as being supported since Windows 11 build 22000 (21H2) but it
|
|
2422
|
+
// works since Windows 10 build 19041 (2004/20H1).
|
|
2423
|
+
DWMWA_USE_IMMERSIVE_DARK_MODE = 20
|
|
2424
|
+
} DWMWINDOWATTRIBUTE;
|
|
2425
|
+
using DwmSetWindowAttribute_t = HRESULT(WINAPI *)(HWND, DWORD, LPCVOID, DWORD);
|
|
2426
|
+
|
|
2427
|
+
constexpr auto DwmSetWindowAttribute =
|
|
2428
|
+
library_symbol<DwmSetWindowAttribute_t>("DwmSetWindowAttribute");
|
|
2429
|
+
} // namespace dwmapi_symbols
|
|
2430
|
+
|
|
2431
|
+
namespace shcore_symbols {
|
|
2432
|
+
typedef enum { PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS;
|
|
2433
|
+
using SetProcessDpiAwareness_t = HRESULT(WINAPI *)(PROCESS_DPI_AWARENESS);
|
|
2434
|
+
|
|
2435
|
+
constexpr auto SetProcessDpiAwareness =
|
|
2436
|
+
library_symbol<SetProcessDpiAwareness_t>("SetProcessDpiAwareness");
|
|
2437
|
+
} // namespace shcore_symbols
|
|
2438
|
+
|
|
2439
|
+
class reg_key {
|
|
2440
|
+
public:
|
|
2441
|
+
explicit reg_key(HKEY root_key, const wchar_t *sub_key, DWORD options,
|
|
2442
|
+
REGSAM sam_desired) {
|
|
2443
|
+
HKEY handle;
|
|
2444
|
+
auto status =
|
|
2445
|
+
RegOpenKeyExW(root_key, sub_key, options, sam_desired, &handle);
|
|
2446
|
+
if (status == ERROR_SUCCESS) {
|
|
2447
|
+
m_handle = handle;
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
explicit reg_key(HKEY root_key, const std::wstring &sub_key, DWORD options,
|
|
2452
|
+
REGSAM sam_desired)
|
|
2453
|
+
: reg_key(root_key, sub_key.c_str(), options, sam_desired) {}
|
|
2454
|
+
|
|
2455
|
+
virtual ~reg_key() {
|
|
2456
|
+
if (m_handle) {
|
|
2457
|
+
RegCloseKey(m_handle);
|
|
2458
|
+
m_handle = nullptr;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
reg_key(const reg_key &other) = delete;
|
|
2463
|
+
reg_key &operator=(const reg_key &other) = delete;
|
|
2464
|
+
reg_key(reg_key &&other) = delete;
|
|
2465
|
+
reg_key &operator=(reg_key &&other) = delete;
|
|
2466
|
+
|
|
2467
|
+
bool is_open() const { return !!m_handle; }
|
|
2468
|
+
bool get_handle() const { return m_handle; }
|
|
2469
|
+
|
|
2470
|
+
template <typename Container>
|
|
2471
|
+
void query_bytes(const wchar_t *name, Container &result) const {
|
|
2472
|
+
DWORD buf_length = 0;
|
|
2473
|
+
// Get the size of the data in bytes.
|
|
2474
|
+
auto status = RegQueryValueExW(m_handle, name, nullptr, nullptr, nullptr,
|
|
2475
|
+
&buf_length);
|
|
2476
|
+
if (status != ERROR_SUCCESS && status != ERROR_MORE_DATA) {
|
|
2477
|
+
result.resize(0);
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
// Read the data.
|
|
2481
|
+
result.resize(buf_length / sizeof(typename Container::value_type));
|
|
2482
|
+
auto *buf = reinterpret_cast<LPBYTE>(&result[0]);
|
|
2483
|
+
status =
|
|
2484
|
+
RegQueryValueExW(m_handle, name, nullptr, nullptr, buf, &buf_length);
|
|
2485
|
+
if (status != ERROR_SUCCESS) {
|
|
2486
|
+
result.resize(0);
|
|
2487
|
+
return;
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
std::wstring query_string(const wchar_t *name) const {
|
|
2492
|
+
std::wstring result;
|
|
2493
|
+
query_bytes(name, result);
|
|
2494
|
+
// Remove trailing null-characters.
|
|
2495
|
+
for (std::size_t length = result.size(); length > 0; --length) {
|
|
2496
|
+
if (result[length - 1] != 0) {
|
|
2497
|
+
result.resize(length);
|
|
2498
|
+
break;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
return result;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
unsigned int query_uint(const wchar_t *name,
|
|
2505
|
+
unsigned int default_value) const {
|
|
2506
|
+
std::vector<char> data;
|
|
2507
|
+
query_bytes(name, data);
|
|
2508
|
+
if (data.size() < sizeof(DWORD)) {
|
|
2509
|
+
return default_value;
|
|
2510
|
+
}
|
|
2511
|
+
return static_cast<unsigned int>(*reinterpret_cast<DWORD *>(data.data()));
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
private:
|
|
2515
|
+
HKEY m_handle = nullptr;
|
|
2516
|
+
};
|
|
2517
|
+
|
|
2518
|
+
// Compare the specified version against the OS version.
|
|
2519
|
+
// Returns less than 0 if the OS version is less.
|
|
2520
|
+
// Returns 0 if the versions are equal.
|
|
2521
|
+
// Returns greater than 0 if the specified version is greater.
|
|
2522
|
+
inline int compare_os_version(unsigned int major, unsigned int minor,
|
|
2523
|
+
unsigned int build) {
|
|
2524
|
+
// Use RtlGetVersion both to bypass potential issues related to
|
|
2525
|
+
// VerifyVersionInfo and manifests, and because both GetVersion and
|
|
2526
|
+
// GetVersionEx are deprecated.
|
|
2527
|
+
auto ntdll = native_library(L"ntdll.dll");
|
|
2528
|
+
if (auto fn = ntdll.get(ntdll_symbols::RtlGetVersion)) {
|
|
2529
|
+
RTL_OSVERSIONINFOW vi{};
|
|
2530
|
+
vi.dwOSVersionInfoSize = sizeof(vi);
|
|
2531
|
+
if (fn(&vi) != 0) {
|
|
2532
|
+
return false;
|
|
2533
|
+
}
|
|
2534
|
+
if (vi.dwMajorVersion == major) {
|
|
2535
|
+
if (vi.dwMinorVersion == minor) {
|
|
2536
|
+
return static_cast<int>(vi.dwBuildNumber) - static_cast<int>(build);
|
|
2537
|
+
}
|
|
2538
|
+
return static_cast<int>(vi.dwMinorVersion) - static_cast<int>(minor);
|
|
2539
|
+
}
|
|
2540
|
+
return static_cast<int>(vi.dwMajorVersion) - static_cast<int>(major);
|
|
2541
|
+
}
|
|
2542
|
+
return false;
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
inline bool is_per_monitor_v2_awareness_available() {
|
|
2546
|
+
// Windows 10, version 1703
|
|
2547
|
+
return compare_os_version(10, 0, 15063) >= 0;
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
inline bool enable_dpi_awareness() {
|
|
2551
|
+
auto user32 = native_library(L"user32.dll");
|
|
2552
|
+
if (auto fn = user32.get(user32_symbols::SetProcessDpiAwarenessContext)) {
|
|
2553
|
+
auto dpi_awareness =
|
|
2554
|
+
reinterpret_cast<user32_symbols::DPI_AWARENESS_CONTEXT>(
|
|
2555
|
+
is_per_monitor_v2_awareness_available()
|
|
2556
|
+
? user32_symbols::dpi_awareness::per_monitor_v2_aware
|
|
2557
|
+
: user32_symbols::dpi_awareness::per_monitor_aware);
|
|
2558
|
+
if (fn(dpi_awareness)) {
|
|
2559
|
+
return true;
|
|
2560
|
+
}
|
|
2561
|
+
return GetLastError() == ERROR_ACCESS_DENIED;
|
|
2562
|
+
}
|
|
2563
|
+
if (auto shcore = native_library(L"shcore.dll")) {
|
|
2564
|
+
if (auto fn = shcore.get(shcore_symbols::SetProcessDpiAwareness)) {
|
|
2565
|
+
auto result = fn(shcore_symbols::PROCESS_PER_MONITOR_DPI_AWARE);
|
|
2566
|
+
return result == S_OK || result == E_ACCESSDENIED;
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
if (auto fn = user32.get(user32_symbols::SetProcessDPIAware)) {
|
|
2570
|
+
return !!fn();
|
|
2571
|
+
}
|
|
2572
|
+
return true;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
inline bool enable_non_client_dpi_scaling_if_needed(HWND window) {
|
|
2576
|
+
auto user32 = native_library(L"user32.dll");
|
|
2577
|
+
auto get_ctx_fn = user32.get(user32_symbols::GetWindowDpiAwarenessContext);
|
|
2578
|
+
if (!get_ctx_fn) {
|
|
2579
|
+
return true;
|
|
2580
|
+
}
|
|
2581
|
+
auto awareness = get_ctx_fn(window);
|
|
2582
|
+
if (!awareness) {
|
|
2583
|
+
return false;
|
|
2584
|
+
}
|
|
2585
|
+
auto ctx_equal_fn = user32.get(user32_symbols::AreDpiAwarenessContextsEqual);
|
|
2586
|
+
if (!ctx_equal_fn) {
|
|
2587
|
+
return true;
|
|
2588
|
+
}
|
|
2589
|
+
// EnableNonClientDpiScaling is only needed with per monitor v1 awareness.
|
|
2590
|
+
auto per_monitor = reinterpret_cast<user32_symbols::DPI_AWARENESS_CONTEXT>(
|
|
2591
|
+
user32_symbols::dpi_awareness::per_monitor_aware);
|
|
2592
|
+
if (!ctx_equal_fn(awareness, per_monitor)) {
|
|
2593
|
+
return true;
|
|
2594
|
+
}
|
|
2595
|
+
auto enable_fn = user32.get(user32_symbols::EnableNonClientDpiScaling);
|
|
2596
|
+
if (!enable_fn) {
|
|
2597
|
+
return true;
|
|
2598
|
+
}
|
|
2599
|
+
return !!enable_fn(window);
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
constexpr int get_default_window_dpi() {
|
|
2603
|
+
constexpr const int default_dpi = 96; // USER_DEFAULT_SCREEN_DPI
|
|
2604
|
+
return default_dpi;
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
inline int get_window_dpi(HWND window) {
|
|
2608
|
+
auto user32 = native_library(L"user32.dll");
|
|
2609
|
+
if (auto fn = user32.get(user32_symbols::GetDpiForWindow)) {
|
|
2610
|
+
auto dpi = static_cast<int>(fn(window));
|
|
2611
|
+
return dpi;
|
|
2612
|
+
}
|
|
2613
|
+
return get_default_window_dpi();
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
constexpr int scale_value_for_dpi(int value, int from_dpi, int to_dpi) {
|
|
2617
|
+
return (value * to_dpi) / from_dpi;
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
constexpr SIZE scale_size(int width, int height, int from_dpi, int to_dpi) {
|
|
2621
|
+
auto scaled_width = scale_value_for_dpi(width, from_dpi, to_dpi);
|
|
2622
|
+
auto scaled_height = scale_value_for_dpi(height, from_dpi, to_dpi);
|
|
2623
|
+
return {scaled_width, scaled_height};
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
inline SIZE make_window_frame_size(HWND window, int width, int height,
|
|
2627
|
+
int dpi) {
|
|
2628
|
+
auto style = GetWindowLong(window, GWL_STYLE);
|
|
2629
|
+
RECT r{0, 0, width, height};
|
|
2630
|
+
auto user32 = native_library(L"user32.dll");
|
|
2631
|
+
if (auto fn = user32.get(user32_symbols::AdjustWindowRectExForDpi)) {
|
|
2632
|
+
fn(&r, style, FALSE, 0, static_cast<UINT>(dpi));
|
|
2633
|
+
} else {
|
|
2634
|
+
AdjustWindowRect(&r, style, 0);
|
|
2635
|
+
}
|
|
2636
|
+
auto frame_width = r.right - r.left;
|
|
2637
|
+
auto frame_height = r.bottom - r.top;
|
|
2638
|
+
return {frame_width, frame_height};
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
inline bool is_dark_theme_enabled() {
|
|
2642
|
+
constexpr auto *sub_key =
|
|
2643
|
+
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
|
|
2644
|
+
reg_key key(HKEY_CURRENT_USER, sub_key, 0, KEY_READ);
|
|
2645
|
+
if (!key.is_open()) {
|
|
2646
|
+
// Default is light theme
|
|
2647
|
+
return false;
|
|
2648
|
+
}
|
|
2649
|
+
return key.query_uint(L"AppsUseLightTheme", 1) == 0;
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
inline void apply_window_theme(HWND window) {
|
|
2653
|
+
auto dark_theme_enabled = is_dark_theme_enabled();
|
|
2654
|
+
|
|
2655
|
+
// Use "immersive dark mode" on systems that support it.
|
|
2656
|
+
// Changes the color of the window's title bar (light or dark).
|
|
2657
|
+
BOOL use_dark_mode{dark_theme_enabled ? TRUE : FALSE};
|
|
2658
|
+
static native_library dwmapi{L"dwmapi.dll"};
|
|
2659
|
+
if (auto fn = dwmapi.get(dwmapi_symbols::DwmSetWindowAttribute)) {
|
|
2660
|
+
// Try the modern, documented attribute before the older, undocumented one.
|
|
2661
|
+
if (fn(window, dwmapi_symbols::DWMWA_USE_IMMERSIVE_DARK_MODE,
|
|
2662
|
+
&use_dark_mode, sizeof(use_dark_mode)) != S_OK) {
|
|
2663
|
+
fn(window,
|
|
2664
|
+
dwmapi_symbols::DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_V10_0_19041,
|
|
2665
|
+
&use_dark_mode, sizeof(use_dark_mode));
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
// Enable built-in WebView2Loader implementation by default.
|
|
2671
|
+
#ifndef WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL
|
|
2672
|
+
#define WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL 1
|
|
2673
|
+
#endif
|
|
2674
|
+
|
|
2675
|
+
// Link WebView2Loader.dll explicitly by default only if the built-in
|
|
2676
|
+
// implementation is enabled.
|
|
2677
|
+
#ifndef WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK
|
|
2678
|
+
#define WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL
|
|
2679
|
+
#endif
|
|
2680
|
+
|
|
2681
|
+
// Explicit linking of WebView2Loader.dll should be used along with
|
|
2682
|
+
// the built-in implementation.
|
|
2683
|
+
#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1 && \
|
|
2684
|
+
WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK != 1
|
|
2685
|
+
#undef WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK
|
|
2686
|
+
#error Please set WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK=1.
|
|
2687
|
+
#endif
|
|
2688
|
+
|
|
2689
|
+
#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
|
|
2690
|
+
// Gets the last component of a Windows native file path.
|
|
2691
|
+
// For example, if the path is "C:\a\b" then the result is "b".
|
|
2692
|
+
template <typename T>
|
|
2693
|
+
std::basic_string<T>
|
|
2694
|
+
get_last_native_path_component(const std::basic_string<T> &path) {
|
|
2695
|
+
auto pos = path.find_last_of(static_cast<T>('\\'));
|
|
2696
|
+
if (pos != std::basic_string<T>::npos) {
|
|
2697
|
+
return path.substr(pos + 1);
|
|
2698
|
+
}
|
|
2699
|
+
return std::basic_string<T>();
|
|
2700
|
+
}
|
|
2701
|
+
#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
|
|
2702
|
+
|
|
2703
|
+
template <typename T> struct cast_info_t {
|
|
2704
|
+
using type = T;
|
|
2705
|
+
IID iid;
|
|
2706
|
+
};
|
|
2707
|
+
|
|
2708
|
+
namespace mswebview2 {
|
|
2709
|
+
static constexpr IID
|
|
2710
|
+
IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler{
|
|
2711
|
+
0x6C4819F3,
|
|
2712
|
+
0xC9B7,
|
|
2713
|
+
0x4260,
|
|
2714
|
+
{0x81, 0x27, 0xC9, 0xF5, 0xBD, 0xE7, 0xF6, 0x8C}};
|
|
2715
|
+
static constexpr IID
|
|
2716
|
+
IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler{
|
|
2717
|
+
0x4E8A3389,
|
|
2718
|
+
0xC9D8,
|
|
2719
|
+
0x4BD2,
|
|
2720
|
+
{0xB6, 0xB5, 0x12, 0x4F, 0xEE, 0x6C, 0xC1, 0x4D}};
|
|
2721
|
+
static constexpr IID IID_ICoreWebView2PermissionRequestedEventHandler{
|
|
2722
|
+
0x15E1C6A3,
|
|
2723
|
+
0xC72A,
|
|
2724
|
+
0x4DF3,
|
|
2725
|
+
{0x91, 0xD7, 0xD0, 0x97, 0xFB, 0xEC, 0x6B, 0xFD}};
|
|
2726
|
+
static constexpr IID IID_ICoreWebView2WebMessageReceivedEventHandler{
|
|
2727
|
+
0x57213F19,
|
|
2728
|
+
0x00E6,
|
|
2729
|
+
0x49FA,
|
|
2730
|
+
{0x8E, 0x07, 0x89, 0x8E, 0xA0, 0x1E, 0xCB, 0xD2}};
|
|
2731
|
+
|
|
2732
|
+
#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
|
|
2733
|
+
enum class webview2_runtime_type { installed = 0, embedded = 1 };
|
|
2734
|
+
|
|
2735
|
+
namespace webview2_symbols {
|
|
2736
|
+
using CreateWebViewEnvironmentWithOptionsInternal_t =
|
|
2737
|
+
HRESULT(STDMETHODCALLTYPE *)(
|
|
2738
|
+
bool, webview2_runtime_type, PCWSTR, IUnknown *,
|
|
2739
|
+
ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler *);
|
|
2740
|
+
using DllCanUnloadNow_t = HRESULT(STDMETHODCALLTYPE *)();
|
|
2741
|
+
|
|
2742
|
+
static constexpr auto CreateWebViewEnvironmentWithOptionsInternal =
|
|
2743
|
+
library_symbol<CreateWebViewEnvironmentWithOptionsInternal_t>(
|
|
2744
|
+
"CreateWebViewEnvironmentWithOptionsInternal");
|
|
2745
|
+
static constexpr auto DllCanUnloadNow =
|
|
2746
|
+
library_symbol<DllCanUnloadNow_t>("DllCanUnloadNow");
|
|
2747
|
+
} // namespace webview2_symbols
|
|
2748
|
+
#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
|
|
2749
|
+
|
|
2750
|
+
#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
|
|
2751
|
+
namespace webview2_symbols {
|
|
2752
|
+
using CreateCoreWebView2EnvironmentWithOptions_t = HRESULT(STDMETHODCALLTYPE *)(
|
|
2753
|
+
PCWSTR, PCWSTR, ICoreWebView2EnvironmentOptions *,
|
|
2754
|
+
ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler *);
|
|
2755
|
+
using GetAvailableCoreWebView2BrowserVersionString_t =
|
|
2756
|
+
HRESULT(STDMETHODCALLTYPE *)(PCWSTR, LPWSTR *);
|
|
2757
|
+
|
|
2758
|
+
static constexpr auto CreateCoreWebView2EnvironmentWithOptions =
|
|
2759
|
+
library_symbol<CreateCoreWebView2EnvironmentWithOptions_t>(
|
|
2760
|
+
"CreateCoreWebView2EnvironmentWithOptions");
|
|
2761
|
+
static constexpr auto GetAvailableCoreWebView2BrowserVersionString =
|
|
2762
|
+
library_symbol<GetAvailableCoreWebView2BrowserVersionString_t>(
|
|
2763
|
+
"GetAvailableCoreWebView2BrowserVersionString");
|
|
2764
|
+
} // namespace webview2_symbols
|
|
2765
|
+
#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
|
|
2766
|
+
|
|
2767
|
+
class loader {
|
|
2768
|
+
public:
|
|
2769
|
+
HRESULT create_environment_with_options(
|
|
2770
|
+
PCWSTR browser_dir, PCWSTR user_data_dir,
|
|
2771
|
+
ICoreWebView2EnvironmentOptions *env_options,
|
|
2772
|
+
ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler
|
|
2773
|
+
*created_handler) const {
|
|
2774
|
+
#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
|
|
2775
|
+
if (m_lib.is_loaded()) {
|
|
2776
|
+
if (auto fn = m_lib.get(
|
|
2777
|
+
webview2_symbols::CreateCoreWebView2EnvironmentWithOptions)) {
|
|
2778
|
+
return fn(browser_dir, user_data_dir, env_options, created_handler);
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
|
|
2782
|
+
return create_environment_with_options_impl(browser_dir, user_data_dir,
|
|
2783
|
+
env_options, created_handler);
|
|
2784
|
+
#else
|
|
2785
|
+
return S_FALSE;
|
|
2786
|
+
#endif
|
|
2787
|
+
#else
|
|
2788
|
+
return ::CreateCoreWebView2EnvironmentWithOptions(
|
|
2789
|
+
browser_dir, user_data_dir, env_options, created_handler);
|
|
2790
|
+
#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
HRESULT
|
|
2794
|
+
get_available_browser_version_string(PCWSTR browser_dir,
|
|
2795
|
+
LPWSTR *version) const {
|
|
2796
|
+
#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
|
|
2797
|
+
if (m_lib.is_loaded()) {
|
|
2798
|
+
if (auto fn = m_lib.get(
|
|
2799
|
+
webview2_symbols::GetAvailableCoreWebView2BrowserVersionString)) {
|
|
2800
|
+
return fn(browser_dir, version);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
|
|
2804
|
+
return get_available_browser_version_string_impl(browser_dir, version);
|
|
2805
|
+
#else
|
|
2806
|
+
return S_FALSE;
|
|
2807
|
+
#endif
|
|
2808
|
+
#else
|
|
2809
|
+
return ::GetAvailableCoreWebView2BrowserVersionString(browser_dir, version);
|
|
2810
|
+
#endif /* WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK */
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
private:
|
|
2814
|
+
#if WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL == 1
|
|
2815
|
+
struct client_info_t {
|
|
2816
|
+
bool found = false;
|
|
2817
|
+
std::wstring dll_path;
|
|
2818
|
+
std::wstring version;
|
|
2819
|
+
webview2_runtime_type runtime_type;
|
|
2820
|
+
};
|
|
2821
|
+
|
|
2822
|
+
HRESULT create_environment_with_options_impl(
|
|
2823
|
+
PCWSTR browser_dir, PCWSTR user_data_dir,
|
|
2824
|
+
ICoreWebView2EnvironmentOptions *env_options,
|
|
2825
|
+
ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler
|
|
2826
|
+
*created_handler) const {
|
|
2827
|
+
auto found_client = find_available_client(browser_dir);
|
|
2828
|
+
if (!found_client.found) {
|
|
2829
|
+
return -1;
|
|
2830
|
+
}
|
|
2831
|
+
auto client_dll = native_library(found_client.dll_path.c_str());
|
|
2832
|
+
if (auto fn = client_dll.get(
|
|
2833
|
+
webview2_symbols::CreateWebViewEnvironmentWithOptionsInternal)) {
|
|
2834
|
+
return fn(true, found_client.runtime_type, user_data_dir, env_options,
|
|
2835
|
+
created_handler);
|
|
2836
|
+
}
|
|
2837
|
+
if (auto fn = client_dll.get(webview2_symbols::DllCanUnloadNow)) {
|
|
2838
|
+
if (!fn()) {
|
|
2839
|
+
client_dll.detach();
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
return ERROR_SUCCESS;
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
HRESULT
|
|
2846
|
+
get_available_browser_version_string_impl(PCWSTR browser_dir,
|
|
2847
|
+
LPWSTR *version) const {
|
|
2848
|
+
if (!version) {
|
|
2849
|
+
return -1;
|
|
2850
|
+
}
|
|
2851
|
+
auto found_client = find_available_client(browser_dir);
|
|
2852
|
+
if (!found_client.found) {
|
|
2853
|
+
return -1;
|
|
2854
|
+
}
|
|
2855
|
+
auto info_length_bytes =
|
|
2856
|
+
found_client.version.size() * sizeof(found_client.version[0]);
|
|
2857
|
+
auto info = static_cast<LPWSTR>(CoTaskMemAlloc(info_length_bytes));
|
|
2858
|
+
if (!info) {
|
|
2859
|
+
return -1;
|
|
2860
|
+
}
|
|
2861
|
+
CopyMemory(info, found_client.version.c_str(), info_length_bytes);
|
|
2862
|
+
*version = info;
|
|
2863
|
+
return 0;
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
client_info_t find_available_client(PCWSTR browser_dir) const {
|
|
2867
|
+
if (browser_dir) {
|
|
2868
|
+
return find_embedded_client(api_version, browser_dir);
|
|
2869
|
+
}
|
|
2870
|
+
auto found_client =
|
|
2871
|
+
find_installed_client(api_version, true, default_release_channel_guid);
|
|
2872
|
+
if (!found_client.found) {
|
|
2873
|
+
found_client = find_installed_client(api_version, false,
|
|
2874
|
+
default_release_channel_guid);
|
|
2875
|
+
}
|
|
2876
|
+
return found_client;
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
std::wstring make_client_dll_path(const std::wstring &dir) const {
|
|
2880
|
+
auto dll_path = dir;
|
|
2881
|
+
if (!dll_path.empty()) {
|
|
2882
|
+
auto last_char = dir[dir.size() - 1];
|
|
2883
|
+
if (last_char != L'\\' && last_char != L'/') {
|
|
2884
|
+
dll_path += L'\\';
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
dll_path += L"EBWebView\\";
|
|
2888
|
+
#if defined(_M_X64) || defined(__x86_64__)
|
|
2889
|
+
dll_path += L"x64";
|
|
2890
|
+
#elif defined(_M_IX86) || defined(__i386__)
|
|
2891
|
+
dll_path += L"x86";
|
|
2892
|
+
#elif defined(_M_ARM64) || defined(__aarch64__)
|
|
2893
|
+
dll_path += L"arm64";
|
|
2894
|
+
#else
|
|
2895
|
+
#error WebView2 integration for this platform is not yet supported.
|
|
2896
|
+
#endif
|
|
2897
|
+
dll_path += L"\\EmbeddedBrowserWebView.dll";
|
|
2898
|
+
return dll_path;
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
client_info_t
|
|
2902
|
+
find_installed_client(unsigned int min_api_version, bool system,
|
|
2903
|
+
const std::wstring &release_channel) const {
|
|
2904
|
+
std::wstring sub_key = client_state_reg_sub_key;
|
|
2905
|
+
sub_key += release_channel;
|
|
2906
|
+
auto root_key = system ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
|
|
2907
|
+
reg_key key(root_key, sub_key, 0, KEY_READ | KEY_WOW64_32KEY);
|
|
2908
|
+
if (!key.is_open()) {
|
|
2909
|
+
return {};
|
|
2910
|
+
}
|
|
2911
|
+
auto ebwebview_value = key.query_string(L"EBWebView");
|
|
2912
|
+
|
|
2913
|
+
auto client_version_string =
|
|
2914
|
+
get_last_native_path_component(ebwebview_value);
|
|
2915
|
+
auto client_version = parse_version(client_version_string);
|
|
2916
|
+
if (client_version[2] < min_api_version) {
|
|
2917
|
+
// Our API version is greater than the runtime API version.
|
|
2918
|
+
return {};
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
auto client_dll_path = make_client_dll_path(ebwebview_value);
|
|
2922
|
+
return {true, client_dll_path, client_version_string,
|
|
2923
|
+
webview2_runtime_type::installed};
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
client_info_t find_embedded_client(unsigned int min_api_version,
|
|
2927
|
+
const std::wstring &dir) const {
|
|
2928
|
+
auto client_dll_path = make_client_dll_path(dir);
|
|
2929
|
+
|
|
2930
|
+
auto client_version_string = get_file_version_string(client_dll_path);
|
|
2931
|
+
auto client_version = parse_version(client_version_string);
|
|
2932
|
+
if (client_version[2] < min_api_version) {
|
|
2933
|
+
// Our API version is greater than the runtime API version.
|
|
2934
|
+
return {};
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
return {true, client_dll_path, client_version_string,
|
|
2938
|
+
webview2_runtime_type::embedded};
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
// The minimum WebView2 API version we need regardless of the SDK release
|
|
2942
|
+
// actually used. The number comes from the SDK release version,
|
|
2943
|
+
// e.g. 1.0.1150.38. To be safe the SDK should have a number that is greater
|
|
2944
|
+
// than or equal to this number. The Edge browser webview client must
|
|
2945
|
+
// have a number greater than or equal to this number.
|
|
2946
|
+
static constexpr unsigned int api_version = 1150;
|
|
2947
|
+
|
|
2948
|
+
static constexpr auto client_state_reg_sub_key =
|
|
2949
|
+
L"SOFTWARE\\Microsoft\\EdgeUpdate\\ClientState\\";
|
|
2950
|
+
|
|
2951
|
+
// GUID for the stable release channel.
|
|
2952
|
+
static constexpr auto stable_release_guid =
|
|
2953
|
+
L"{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}";
|
|
2954
|
+
|
|
2955
|
+
static constexpr auto default_release_channel_guid = stable_release_guid;
|
|
2956
|
+
#endif /* WEBVIEW_MSWEBVIEW2_BUILTIN_IMPL */
|
|
2957
|
+
|
|
2958
|
+
#if WEBVIEW_MSWEBVIEW2_EXPLICIT_LINK == 1
|
|
2959
|
+
native_library m_lib{L"WebView2Loader.dll"};
|
|
2960
|
+
#endif
|
|
2961
|
+
};
|
|
2962
|
+
|
|
2963
|
+
namespace cast_info {
|
|
2964
|
+
static constexpr auto controller_completed =
|
|
2965
|
+
cast_info_t<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>{
|
|
2966
|
+
IID_ICoreWebView2CreateCoreWebView2ControllerCompletedHandler};
|
|
2967
|
+
|
|
2968
|
+
static constexpr auto environment_completed =
|
|
2969
|
+
cast_info_t<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>{
|
|
2970
|
+
IID_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler};
|
|
2971
|
+
|
|
2972
|
+
static constexpr auto message_received =
|
|
2973
|
+
cast_info_t<ICoreWebView2WebMessageReceivedEventHandler>{
|
|
2974
|
+
IID_ICoreWebView2WebMessageReceivedEventHandler};
|
|
2975
|
+
|
|
2976
|
+
static constexpr auto permission_requested =
|
|
2977
|
+
cast_info_t<ICoreWebView2PermissionRequestedEventHandler>{
|
|
2978
|
+
IID_ICoreWebView2PermissionRequestedEventHandler};
|
|
2979
|
+
} // namespace cast_info
|
|
2980
|
+
} // namespace mswebview2
|
|
2981
|
+
|
|
2982
|
+
class webview2_com_handler
|
|
2983
|
+
: public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
|
|
2984
|
+
public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
|
|
2985
|
+
public ICoreWebView2WebMessageReceivedEventHandler,
|
|
2986
|
+
public ICoreWebView2PermissionRequestedEventHandler {
|
|
2987
|
+
using webview2_com_handler_cb_t =
|
|
2988
|
+
std::function<void(ICoreWebView2Controller *, ICoreWebView2 *webview)>;
|
|
2989
|
+
|
|
2990
|
+
public:
|
|
2991
|
+
webview2_com_handler(HWND hwnd, msg_cb_t msgCb, webview2_com_handler_cb_t cb)
|
|
2992
|
+
: m_window(hwnd), m_msgCb(msgCb), m_cb(cb) {}
|
|
2993
|
+
|
|
2994
|
+
virtual ~webview2_com_handler() = default;
|
|
2995
|
+
webview2_com_handler(const webview2_com_handler &other) = delete;
|
|
2996
|
+
webview2_com_handler &operator=(const webview2_com_handler &other) = delete;
|
|
2997
|
+
webview2_com_handler(webview2_com_handler &&other) = delete;
|
|
2998
|
+
webview2_com_handler &operator=(webview2_com_handler &&other) = delete;
|
|
2999
|
+
|
|
3000
|
+
ULONG STDMETHODCALLTYPE AddRef() { return ++m_ref_count; }
|
|
3001
|
+
ULONG STDMETHODCALLTYPE Release() {
|
|
3002
|
+
if (m_ref_count > 1) {
|
|
3003
|
+
return --m_ref_count;
|
|
3004
|
+
}
|
|
3005
|
+
delete this;
|
|
3006
|
+
return 0;
|
|
3007
|
+
}
|
|
3008
|
+
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv) {
|
|
3009
|
+
using namespace mswebview2::cast_info;
|
|
3010
|
+
|
|
3011
|
+
if (!ppv) {
|
|
3012
|
+
return E_POINTER;
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
// All of the COM interfaces we implement should be added here regardless
|
|
3016
|
+
// of whether they are required.
|
|
3017
|
+
// This is just to be on the safe side in case the WebView2 Runtime ever
|
|
3018
|
+
// requests a pointer to an interface we implement.
|
|
3019
|
+
// The WebView2 Runtime must at the very least be able to get a pointer to
|
|
3020
|
+
// ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler when we use
|
|
3021
|
+
// our custom WebView2 loader implementation, and observations have shown
|
|
3022
|
+
// that it is the only interface requested in this case. None have been
|
|
3023
|
+
// observed to be requested when using the official WebView2 loader.
|
|
3024
|
+
|
|
3025
|
+
if (cast_if_equal_iid(riid, controller_completed, ppv) ||
|
|
3026
|
+
cast_if_equal_iid(riid, environment_completed, ppv) ||
|
|
3027
|
+
cast_if_equal_iid(riid, message_received, ppv) ||
|
|
3028
|
+
cast_if_equal_iid(riid, permission_requested, ppv)) {
|
|
3029
|
+
return S_OK;
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
return E_NOINTERFACE;
|
|
3033
|
+
}
|
|
3034
|
+
HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Environment *env) {
|
|
3035
|
+
if (SUCCEEDED(res)) {
|
|
3036
|
+
res = env->CreateCoreWebView2Controller(m_window, this);
|
|
3037
|
+
if (SUCCEEDED(res)) {
|
|
3038
|
+
return S_OK;
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
try_create_environment();
|
|
3042
|
+
return S_OK;
|
|
3043
|
+
}
|
|
3044
|
+
HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
|
|
3045
|
+
ICoreWebView2Controller *controller) {
|
|
3046
|
+
if (FAILED(res)) {
|
|
3047
|
+
// See try_create_environment() regarding
|
|
3048
|
+
// HRESULT_FROM_WIN32(ERROR_INVALID_STATE).
|
|
3049
|
+
// The result is E_ABORT if the parent window has been destroyed already.
|
|
3050
|
+
switch (res) {
|
|
3051
|
+
case HRESULT_FROM_WIN32(ERROR_INVALID_STATE):
|
|
3052
|
+
case E_ABORT:
|
|
3053
|
+
return S_OK;
|
|
3054
|
+
}
|
|
3055
|
+
try_create_environment();
|
|
3056
|
+
return S_OK;
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
ICoreWebView2 *webview;
|
|
3060
|
+
::EventRegistrationToken token;
|
|
3061
|
+
controller->get_CoreWebView2(&webview);
|
|
3062
|
+
webview->add_WebMessageReceived(this, &token);
|
|
3063
|
+
webview->add_PermissionRequested(this, &token);
|
|
3064
|
+
|
|
3065
|
+
m_cb(controller, webview);
|
|
3066
|
+
return S_OK;
|
|
3067
|
+
}
|
|
3068
|
+
HRESULT STDMETHODCALLTYPE Invoke(
|
|
3069
|
+
ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) {
|
|
3070
|
+
LPWSTR message;
|
|
3071
|
+
args->TryGetWebMessageAsString(&message);
|
|
3072
|
+
m_msgCb(narrow_string(message));
|
|
3073
|
+
sender->PostWebMessageAsString(message);
|
|
3074
|
+
|
|
3075
|
+
CoTaskMemFree(message);
|
|
3076
|
+
return S_OK;
|
|
3077
|
+
}
|
|
3078
|
+
HRESULT STDMETHODCALLTYPE
|
|
3079
|
+
Invoke(ICoreWebView2 * /*sender*/,
|
|
3080
|
+
ICoreWebView2PermissionRequestedEventArgs *args) {
|
|
3081
|
+
COREWEBVIEW2_PERMISSION_KIND kind;
|
|
3082
|
+
args->get_PermissionKind(&kind);
|
|
3083
|
+
if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) {
|
|
3084
|
+
args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
|
|
3085
|
+
}
|
|
3086
|
+
return S_OK;
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
// Checks whether the specified IID equals the IID of the specified type and
|
|
3090
|
+
// if so casts the "this" pointer to T and returns it. Returns nullptr on
|
|
3091
|
+
// mismatching IIDs.
|
|
3092
|
+
// If ppv is specified then the pointer will also be assigned to *ppv.
|
|
3093
|
+
template <typename T>
|
|
3094
|
+
T *cast_if_equal_iid(REFIID riid, const cast_info_t<T> &info,
|
|
3095
|
+
LPVOID *ppv = nullptr) noexcept {
|
|
3096
|
+
T *ptr = nullptr;
|
|
3097
|
+
if (IsEqualIID(riid, info.iid)) {
|
|
3098
|
+
ptr = static_cast<T *>(this);
|
|
3099
|
+
ptr->AddRef();
|
|
3100
|
+
}
|
|
3101
|
+
if (ppv) {
|
|
3102
|
+
*ppv = ptr;
|
|
3103
|
+
}
|
|
3104
|
+
return ptr;
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
// Set the function that will perform the initiating logic for creating
|
|
3108
|
+
// the WebView2 environment.
|
|
3109
|
+
void set_attempt_handler(std::function<HRESULT()> attempt_handler) noexcept {
|
|
3110
|
+
m_attempt_handler = attempt_handler;
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
// Retry creating a WebView2 environment.
|
|
3114
|
+
// The initiating logic for creating the environment is defined by the
|
|
3115
|
+
// caller of set_attempt_handler().
|
|
3116
|
+
void try_create_environment() noexcept {
|
|
3117
|
+
// WebView creation fails with HRESULT_FROM_WIN32(ERROR_INVALID_STATE) if
|
|
3118
|
+
// a running instance using the same user data folder exists, and the
|
|
3119
|
+
// Environment objects have different EnvironmentOptions.
|
|
3120
|
+
// Source: https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment?view=webview2-1.0.1150.38
|
|
3121
|
+
if (m_attempts < m_max_attempts) {
|
|
3122
|
+
++m_attempts;
|
|
3123
|
+
auto res = m_attempt_handler();
|
|
3124
|
+
if (SUCCEEDED(res)) {
|
|
3125
|
+
return;
|
|
3126
|
+
}
|
|
3127
|
+
// Not entirely sure if this error code only applies to
|
|
3128
|
+
// CreateCoreWebView2Controller so we check here as well.
|
|
3129
|
+
if (res == HRESULT_FROM_WIN32(ERROR_INVALID_STATE)) {
|
|
3130
|
+
return;
|
|
3131
|
+
}
|
|
3132
|
+
try_create_environment();
|
|
3133
|
+
return;
|
|
3134
|
+
}
|
|
3135
|
+
// Give up.
|
|
3136
|
+
m_cb(nullptr, nullptr);
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
private:
|
|
3140
|
+
HWND m_window;
|
|
3141
|
+
msg_cb_t m_msgCb;
|
|
3142
|
+
webview2_com_handler_cb_t m_cb;
|
|
3143
|
+
std::atomic<ULONG> m_ref_count{1};
|
|
3144
|
+
std::function<HRESULT()> m_attempt_handler;
|
|
3145
|
+
unsigned int m_max_attempts = 5;
|
|
3146
|
+
unsigned int m_attempts = 0;
|
|
3147
|
+
};
|
|
3148
|
+
|
|
3149
|
+
class win32_edge_engine : public engine_base {
|
|
3150
|
+
public:
|
|
3151
|
+
win32_edge_engine(bool debug, void *window) : m_owns_window{!window} {
|
|
3152
|
+
if (!is_webview2_available()) {
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
HINSTANCE hInstance = GetModuleHandle(nullptr);
|
|
3157
|
+
|
|
3158
|
+
if (m_owns_window) {
|
|
3159
|
+
m_com_init = {COINIT_APARTMENTTHREADED};
|
|
3160
|
+
if (!m_com_init.is_initialized()) {
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3163
|
+
enable_dpi_awareness();
|
|
3164
|
+
|
|
3165
|
+
// Freedom fork: 标题栏图标与 exe 图标一致。
|
|
3166
|
+
// 优先加载 exe 自身嵌入的图标组资源(freedom icon 注入,资源 ID 通常为 1),
|
|
3167
|
+
// 失败则回退到系统默认应用图标。
|
|
3168
|
+
HMODULE hSelf = GetModuleHandleW(nullptr);
|
|
3169
|
+
HICON icon = (HICON)LoadImageW(
|
|
3170
|
+
hSelf, MAKEINTRESOURCEW(1), IMAGE_ICON, GetSystemMetrics(SM_CXICON),
|
|
3171
|
+
GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);
|
|
3172
|
+
if (icon == nullptr) {
|
|
3173
|
+
icon = (HICON)LoadImageW(
|
|
3174
|
+
nullptr, MAKEINTRESOURCEW(32512), IMAGE_ICON,
|
|
3175
|
+
GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON),
|
|
3176
|
+
LR_DEFAULTCOLOR);
|
|
3177
|
+
}
|
|
3178
|
+
HICON icon_sm = (HICON)LoadImageW(
|
|
3179
|
+
hSelf, MAKEINTRESOURCEW(1), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON),
|
|
3180
|
+
GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
|
|
3181
|
+
if (icon_sm == nullptr) {
|
|
3182
|
+
icon_sm = (HICON)LoadImageW(
|
|
3183
|
+
nullptr, MAKEINTRESOURCEW(32512), IMAGE_ICON,
|
|
3184
|
+
GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
|
|
3185
|
+
LR_DEFAULTCOLOR);
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
// Create a top-level window.
|
|
3189
|
+
WNDCLASSEXW wc;
|
|
3190
|
+
ZeroMemory(&wc, sizeof(WNDCLASSEX));
|
|
3191
|
+
wc.cbSize = sizeof(WNDCLASSEX);
|
|
3192
|
+
wc.hInstance = hInstance;
|
|
3193
|
+
wc.lpszClassName = L"webview";
|
|
3194
|
+
wc.hIcon = icon;
|
|
3195
|
+
wc.hIconSm = icon_sm;
|
|
3196
|
+
wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp,
|
|
3197
|
+
LPARAM lp) -> LRESULT {
|
|
3198
|
+
win32_edge_engine *w{};
|
|
3199
|
+
|
|
3200
|
+
if (msg == WM_NCCREATE) {
|
|
3201
|
+
auto *lpcs{reinterpret_cast<LPCREATESTRUCT>(lp)};
|
|
3202
|
+
w = static_cast<win32_edge_engine *>(lpcs->lpCreateParams);
|
|
3203
|
+
w->m_window = hwnd;
|
|
3204
|
+
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(w));
|
|
3205
|
+
enable_non_client_dpi_scaling_if_needed(hwnd);
|
|
3206
|
+
apply_window_theme(hwnd);
|
|
3207
|
+
} else {
|
|
3208
|
+
w = reinterpret_cast<win32_edge_engine *>(
|
|
3209
|
+
GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
|
3210
|
+
}
|
|
3211
|
+
|
|
3212
|
+
if (!w) {
|
|
3213
|
+
return DefWindowProcW(hwnd, msg, wp, lp);
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
switch (msg) {
|
|
3217
|
+
case WM_SIZE:
|
|
3218
|
+
w->resize_widget();
|
|
3219
|
+
break;
|
|
3220
|
+
case WM_CLOSE:
|
|
3221
|
+
DestroyWindow(hwnd);
|
|
3222
|
+
break;
|
|
3223
|
+
case WM_DESTROY:
|
|
3224
|
+
w->m_window = nullptr;
|
|
3225
|
+
SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
|
|
3226
|
+
w->on_window_destroyed();
|
|
3227
|
+
break;
|
|
3228
|
+
case WM_GETMINMAXINFO: {
|
|
3229
|
+
auto lpmmi = (LPMINMAXINFO)lp;
|
|
3230
|
+
if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0) {
|
|
3231
|
+
lpmmi->ptMaxSize = w->m_maxsz;
|
|
3232
|
+
lpmmi->ptMaxTrackSize = w->m_maxsz;
|
|
3233
|
+
}
|
|
3234
|
+
if (w->m_minsz.x > 0 && w->m_minsz.y > 0) {
|
|
3235
|
+
lpmmi->ptMinTrackSize = w->m_minsz;
|
|
3236
|
+
}
|
|
3237
|
+
// Freedom fork: 无边框窗口(WS_CAPTION 被移除,前端自绘标题栏)最大化行为修正。
|
|
3238
|
+
// 系统对无边框窗口的默认最大化会铺满整个屏幕(覆盖任务栏,类似 F11 全屏);
|
|
3239
|
+
// 这里把最大化边界显式限定到窗口所在监视器的工作区(rcWork),
|
|
3240
|
+
// 使"最大化"与标准窗口一致:铺满任务栏上方的工作区,而非整屏全屏。
|
|
3241
|
+
// 注意:无边框窗口因下方 WM_NCCALCSIZE 返回 0,客户区即整个窗口矩形,
|
|
3242
|
+
// 因此这里直接取 rcWork 作为最大化尺寸与位置即可(无需像普通窗口那样
|
|
3243
|
+
// 额外补偿边框,否则会让客户区溢出工作区、盖住任务栏)。
|
|
3244
|
+
auto fstyle = GetWindowLongPtrW(hwnd, GWL_STYLE);
|
|
3245
|
+
if ((fstyle & WS_CAPTION) == 0) {
|
|
3246
|
+
HMONITOR mon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
|
3247
|
+
MONITORINFO mi{};
|
|
3248
|
+
mi.cbSize = sizeof(MONITORINFO);
|
|
3249
|
+
if (mon != nullptr && GetMonitorInfoW(mon, &mi)) {
|
|
3250
|
+
lpmmi->ptMaxPosition.x = mi.rcWork.left;
|
|
3251
|
+
lpmmi->ptMaxPosition.y = mi.rcWork.top;
|
|
3252
|
+
lpmmi->ptMaxSize.x = mi.rcWork.right - mi.rcWork.left;
|
|
3253
|
+
lpmmi->ptMaxSize.y = mi.rcWork.bottom - mi.rcWork.top;
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
} break;
|
|
3257
|
+
// Freedom fork: 无边框窗口彻底清理非客户区。
|
|
3258
|
+
// 打包后 UI 顶部出现"未清理干净的原生标题栏",根因是窗口移除 WS_CAPTION 后
|
|
3259
|
+
// DWM 仍可能按原框架绘制标题栏/边框区域。此处对无边框窗口在计算客户区时
|
|
3260
|
+
// 直接返回 0,让客户区铺满整个窗口,从根源上消除原生标题栏/边框残留。
|
|
3261
|
+
case WM_NCCALCSIZE: {
|
|
3262
|
+
if (wp) {
|
|
3263
|
+
auto fstyle = GetWindowLongPtrW(hwnd, GWL_STYLE);
|
|
3264
|
+
if ((fstyle & WS_CAPTION) == 0) {
|
|
3265
|
+
return 0;
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
} break;
|
|
3269
|
+
// Freedom fork: 无边框窗口保留边缘拖拽调整大小。
|
|
3270
|
+
// 移除 WS_CAPTION 后系统默认把整个窗口都视为客户区(WM_NCHITTEST 返回
|
|
3271
|
+
// HTCLIENT),边缘 resize 会失效。这里在无边框时手动恢复边框 hit-test,
|
|
3272
|
+
// 8px 边缘返回对应的 HT 值,使无边框窗口仍可拖动边缘调整大小。
|
|
3273
|
+
case WM_NCHITTEST: {
|
|
3274
|
+
auto fstyle = GetWindowLongPtrW(hwnd, GWL_STYLE);
|
|
3275
|
+
if ((fstyle & WS_CAPTION) == 0) {
|
|
3276
|
+
POINT pt{};
|
|
3277
|
+
pt.x = (short)LOWORD(lp);
|
|
3278
|
+
pt.y = (short)HIWORD(lp);
|
|
3279
|
+
RECT rc{};
|
|
3280
|
+
GetWindowRect(hwnd, &rc);
|
|
3281
|
+
constexpr int b = 8;
|
|
3282
|
+
const bool top = pt.y < rc.top + b;
|
|
3283
|
+
const bool bottom = pt.y > rc.bottom - b;
|
|
3284
|
+
const bool left = pt.x < rc.left + b;
|
|
3285
|
+
const bool right = pt.x > rc.right - b;
|
|
3286
|
+
if (top && left) return HTTOPLEFT;
|
|
3287
|
+
if (top && right) return HTTOPRIGHT;
|
|
3288
|
+
if (bottom && left) return HTBOTTOMLEFT;
|
|
3289
|
+
if (bottom && right) return HTBOTTOMRIGHT;
|
|
3290
|
+
if (top) return HTTOP;
|
|
3291
|
+
if (bottom) return HTBOTTOM;
|
|
3292
|
+
if (left) return HTLEFT;
|
|
3293
|
+
if (right) return HTRIGHT;
|
|
3294
|
+
return HTCLIENT;
|
|
3295
|
+
}
|
|
3296
|
+
} break;
|
|
3297
|
+
case 0x02E4 /*WM_GETDPISCALEDSIZE*/: {
|
|
3298
|
+
auto dpi = static_cast<int>(wp);
|
|
3299
|
+
auto *size{reinterpret_cast<SIZE *>(lp)};
|
|
3300
|
+
*size = w->get_scaled_size(w->m_dpi, dpi);
|
|
3301
|
+
return TRUE;
|
|
3302
|
+
}
|
|
3303
|
+
case 0x02E0 /*WM_DPICHANGED*/: {
|
|
3304
|
+
// Windows 10: The size we get here is exactly what we supplied to WM_GETDPISCALEDSIZE.
|
|
3305
|
+
// Windows 11: The size we get here is NOT what we supplied to WM_GETDPISCALEDSIZE.
|
|
3306
|
+
// Due to this difference, don't use the suggested bounds.
|
|
3307
|
+
auto dpi = static_cast<int>(HIWORD(wp));
|
|
3308
|
+
w->on_dpi_changed(dpi);
|
|
3309
|
+
break;
|
|
3310
|
+
}
|
|
3311
|
+
case WM_SETTINGCHANGE: {
|
|
3312
|
+
auto *area = reinterpret_cast<const wchar_t *>(lp);
|
|
3313
|
+
if (area) {
|
|
3314
|
+
w->on_system_setting_change(area);
|
|
3315
|
+
}
|
|
3316
|
+
break;
|
|
3317
|
+
}
|
|
3318
|
+
case WM_ACTIVATE:
|
|
3319
|
+
if (LOWORD(wp) != WA_INACTIVE) {
|
|
3320
|
+
w->focus_webview();
|
|
3321
|
+
}
|
|
3322
|
+
break;
|
|
3323
|
+
default:
|
|
3324
|
+
return DefWindowProcW(hwnd, msg, wp, lp);
|
|
3325
|
+
}
|
|
3326
|
+
return 0;
|
|
3327
|
+
});
|
|
3328
|
+
RegisterClassExW(&wc);
|
|
3329
|
+
|
|
3330
|
+
CreateWindowW(L"webview", L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
|
|
3331
|
+
CW_USEDEFAULT, 0, 0, nullptr, nullptr, hInstance, this);
|
|
3332
|
+
if (m_window == nullptr) {
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
3335
|
+
// Freedom fork: 强制刷新窗口标题栏 / 任务栏图标为 exe 图标。
|
|
3336
|
+
SendMessageW(m_window, WM_SETICON, ICON_SMALL, (LPARAM)icon_sm);
|
|
3337
|
+
SendMessageW(m_window, WM_SETICON, ICON_BIG, (LPARAM)icon);
|
|
3338
|
+
on_window_created();
|
|
3339
|
+
|
|
3340
|
+
m_dpi = get_window_dpi(m_window);
|
|
3341
|
+
constexpr const int initial_width = 640;
|
|
3342
|
+
constexpr const int initial_height = 480;
|
|
3343
|
+
set_size(initial_width, initial_height, WEBVIEW_HINT_NONE);
|
|
3344
|
+
} else {
|
|
3345
|
+
m_window = IsWindow(static_cast<HWND>(window))
|
|
3346
|
+
? static_cast<HWND>(window)
|
|
3347
|
+
: *(static_cast<HWND *>(window));
|
|
3348
|
+
m_dpi = get_window_dpi(m_window);
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
// Create a window that WebView2 will be embedded into.
|
|
3352
|
+
WNDCLASSEXW widget_wc{};
|
|
3353
|
+
widget_wc.cbSize = sizeof(WNDCLASSEX);
|
|
3354
|
+
widget_wc.hInstance = hInstance;
|
|
3355
|
+
widget_wc.lpszClassName = L"webview_widget";
|
|
3356
|
+
widget_wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp,
|
|
3357
|
+
LPARAM lp) -> LRESULT {
|
|
3358
|
+
win32_edge_engine *w{};
|
|
3359
|
+
|
|
3360
|
+
if (msg == WM_NCCREATE) {
|
|
3361
|
+
auto *lpcs{reinterpret_cast<LPCREATESTRUCT>(lp)};
|
|
3362
|
+
w = static_cast<win32_edge_engine *>(lpcs->lpCreateParams);
|
|
3363
|
+
w->m_widget = hwnd;
|
|
3364
|
+
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(w));
|
|
3365
|
+
} else {
|
|
3366
|
+
w = reinterpret_cast<win32_edge_engine *>(
|
|
3367
|
+
GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
if (!w) {
|
|
3371
|
+
return DefWindowProcW(hwnd, msg, wp, lp);
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
switch (msg) {
|
|
3375
|
+
case WM_SIZE:
|
|
3376
|
+
w->resize_webview();
|
|
3377
|
+
break;
|
|
3378
|
+
case WM_DESTROY:
|
|
3379
|
+
w->m_widget = nullptr;
|
|
3380
|
+
SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
|
|
3381
|
+
break;
|
|
3382
|
+
default:
|
|
3383
|
+
return DefWindowProcW(hwnd, msg, wp, lp);
|
|
3384
|
+
}
|
|
3385
|
+
return 0;
|
|
3386
|
+
});
|
|
3387
|
+
RegisterClassExW(&widget_wc);
|
|
3388
|
+
CreateWindowExW(WS_EX_CONTROLPARENT, L"webview_widget", nullptr, WS_CHILD,
|
|
3389
|
+
0, 0, 0, 0, m_window, nullptr, hInstance, this);
|
|
3390
|
+
|
|
3391
|
+
// Create a message-only window for internal messaging.
|
|
3392
|
+
WNDCLASSEXW message_wc{};
|
|
3393
|
+
message_wc.cbSize = sizeof(WNDCLASSEX);
|
|
3394
|
+
message_wc.hInstance = hInstance;
|
|
3395
|
+
message_wc.lpszClassName = L"webview_message";
|
|
3396
|
+
message_wc.lpfnWndProc = (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp,
|
|
3397
|
+
LPARAM lp) -> LRESULT {
|
|
3398
|
+
win32_edge_engine *w{};
|
|
3399
|
+
|
|
3400
|
+
if (msg == WM_NCCREATE) {
|
|
3401
|
+
auto *lpcs{reinterpret_cast<LPCREATESTRUCT>(lp)};
|
|
3402
|
+
w = static_cast<win32_edge_engine *>(lpcs->lpCreateParams);
|
|
3403
|
+
w->m_message_window = hwnd;
|
|
3404
|
+
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(w));
|
|
3405
|
+
} else {
|
|
3406
|
+
w = reinterpret_cast<win32_edge_engine *>(
|
|
3407
|
+
GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
if (!w) {
|
|
3411
|
+
return DefWindowProcW(hwnd, msg, wp, lp);
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
switch (msg) {
|
|
3415
|
+
case WM_APP:
|
|
3416
|
+
if (auto f = (dispatch_fn_t *)(lp)) {
|
|
3417
|
+
(*f)();
|
|
3418
|
+
delete f;
|
|
3419
|
+
}
|
|
3420
|
+
break;
|
|
3421
|
+
case WM_DESTROY:
|
|
3422
|
+
w->m_message_window = nullptr;
|
|
3423
|
+
SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
|
|
3424
|
+
break;
|
|
3425
|
+
default:
|
|
3426
|
+
return DefWindowProcW(hwnd, msg, wp, lp);
|
|
3427
|
+
}
|
|
3428
|
+
return 0;
|
|
3429
|
+
});
|
|
3430
|
+
RegisterClassExW(&message_wc);
|
|
3431
|
+
CreateWindowExW(0, L"webview_message", nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE,
|
|
3432
|
+
nullptr, hInstance, this);
|
|
3433
|
+
|
|
3434
|
+
if (m_owns_window) {
|
|
3435
|
+
ShowWindow(m_window, SW_SHOW);
|
|
3436
|
+
UpdateWindow(m_window);
|
|
3437
|
+
SetFocus(m_window);
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
auto cb =
|
|
3441
|
+
std::bind(&win32_edge_engine::on_message, this, std::placeholders::_1);
|
|
3442
|
+
|
|
3443
|
+
embed(m_widget, debug, cb);
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
virtual ~win32_edge_engine() {
|
|
3447
|
+
if (m_com_handler) {
|
|
3448
|
+
m_com_handler->Release();
|
|
3449
|
+
m_com_handler = nullptr;
|
|
3450
|
+
}
|
|
3451
|
+
if (m_webview) {
|
|
3452
|
+
m_webview->Release();
|
|
3453
|
+
m_webview = nullptr;
|
|
3454
|
+
}
|
|
3455
|
+
if (m_controller) {
|
|
3456
|
+
m_controller->Release();
|
|
3457
|
+
m_controller = nullptr;
|
|
3458
|
+
}
|
|
3459
|
+
// Replace wndproc to avoid callbacks and other bad things during
|
|
3460
|
+
// destruction.
|
|
3461
|
+
auto wndproc = reinterpret_cast<LONG_PTR>(
|
|
3462
|
+
+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT {
|
|
3463
|
+
return DefWindowProcW(hwnd, msg, wp, lp);
|
|
3464
|
+
});
|
|
3465
|
+
if (m_widget) {
|
|
3466
|
+
SetWindowLongPtrW(m_widget, GWLP_WNDPROC, wndproc);
|
|
3467
|
+
}
|
|
3468
|
+
if (m_window && m_owns_window) {
|
|
3469
|
+
SetWindowLongPtrW(m_window, GWLP_WNDPROC, wndproc);
|
|
3470
|
+
}
|
|
3471
|
+
if (m_widget) {
|
|
3472
|
+
DestroyWindow(m_widget);
|
|
3473
|
+
m_widget = nullptr;
|
|
3474
|
+
}
|
|
3475
|
+
if (m_window) {
|
|
3476
|
+
if (m_owns_window) {
|
|
3477
|
+
DestroyWindow(m_window);
|
|
3478
|
+
on_window_destroyed(true);
|
|
3479
|
+
}
|
|
3480
|
+
m_window = nullptr;
|
|
3481
|
+
}
|
|
3482
|
+
if (m_owns_window) {
|
|
3483
|
+
// Not strictly needed for windows to close immediately but aligns
|
|
3484
|
+
// behavior across backends.
|
|
3485
|
+
deplete_run_loop_event_queue();
|
|
3486
|
+
}
|
|
3487
|
+
// We need the message window in order to deplete the event queue.
|
|
3488
|
+
if (m_message_window) {
|
|
3489
|
+
SetWindowLongPtrW(m_message_window, GWLP_WNDPROC, wndproc);
|
|
3490
|
+
DestroyWindow(m_message_window);
|
|
3491
|
+
m_message_window = nullptr;
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
win32_edge_engine(const win32_edge_engine &other) = delete;
|
|
3496
|
+
win32_edge_engine &operator=(const win32_edge_engine &other) = delete;
|
|
3497
|
+
win32_edge_engine(win32_edge_engine &&other) = delete;
|
|
3498
|
+
win32_edge_engine &operator=(win32_edge_engine &&other) = delete;
|
|
3499
|
+
|
|
3500
|
+
void run_impl() override {
|
|
3501
|
+
MSG msg;
|
|
3502
|
+
while (GetMessageW(&msg, nullptr, 0, 0) > 0) {
|
|
3503
|
+
TranslateMessage(&msg);
|
|
3504
|
+
DispatchMessageW(&msg);
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
void *window_impl() override { return (void *)m_window; }
|
|
3508
|
+
void *widget_impl() override { return (void *)m_widget; }
|
|
3509
|
+
void *browser_controller_impl() override { return (void *)m_controller; }
|
|
3510
|
+
void terminate_impl() override { PostQuitMessage(0); }
|
|
3511
|
+
void dispatch_impl(dispatch_fn_t f) override {
|
|
3512
|
+
PostMessageW(m_message_window, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
void set_title_impl(const std::string &title) override {
|
|
3516
|
+
SetWindowTextW(m_window, widen_string(title).c_str());
|
|
3517
|
+
}
|
|
3518
|
+
// Windows frameless 由 freedom 壳层 window_windows.go 的 applyTitleBar +
|
|
3519
|
+
// WM_NCHITTEST/WM_GETMINMAXINFO 处理,webview 层保持 no-op。
|
|
3520
|
+
void set_decorated_impl(bool decorated) override { (void)decorated; }
|
|
3521
|
+
// Windows 窗口控制(min/max/close)由 freedom 壳层 window_windows.go
|
|
3522
|
+
// 直接调 user32 处理,webview 层不参与。
|
|
3523
|
+
int window_control_impl(int action) override { (void)action; return -1; }
|
|
3524
|
+
int begin_move_drag_impl() override { return -1; }
|
|
3525
|
+
|
|
3526
|
+
void set_size_impl(int width, int height, webview_hint_t hints) override {
|
|
3527
|
+
auto style = GetWindowLong(m_window, GWL_STYLE);
|
|
3528
|
+
if (hints == WEBVIEW_HINT_FIXED) {
|
|
3529
|
+
style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
|
|
3530
|
+
} else {
|
|
3531
|
+
style |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
|
|
3532
|
+
}
|
|
3533
|
+
SetWindowLong(m_window, GWL_STYLE, style);
|
|
3534
|
+
|
|
3535
|
+
if (hints == WEBVIEW_HINT_MAX) {
|
|
3536
|
+
m_maxsz.x = width;
|
|
3537
|
+
m_maxsz.y = height;
|
|
3538
|
+
} else if (hints == WEBVIEW_HINT_MIN) {
|
|
3539
|
+
m_minsz.x = width;
|
|
3540
|
+
m_minsz.y = height;
|
|
3541
|
+
} else {
|
|
3542
|
+
auto dpi = get_window_dpi(m_window);
|
|
3543
|
+
m_dpi = dpi;
|
|
3544
|
+
auto scaled_size =
|
|
3545
|
+
scale_size(width, height, get_default_window_dpi(), dpi);
|
|
3546
|
+
auto frame_size =
|
|
3547
|
+
make_window_frame_size(m_window, scaled_size.cx, scaled_size.cy, dpi);
|
|
3548
|
+
SetWindowPos(m_window, nullptr, 0, 0, frame_size.cx, frame_size.cy,
|
|
3549
|
+
SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE |
|
|
3550
|
+
SWP_FRAMECHANGED);
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
|
|
3554
|
+
void navigate_impl(const std::string &url) override {
|
|
3555
|
+
auto wurl = widen_string(url);
|
|
3556
|
+
m_webview->Navigate(wurl.c_str());
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
void init_impl(const std::string &js) override {
|
|
3560
|
+
auto wjs = widen_string(js);
|
|
3561
|
+
m_webview->AddScriptToExecuteOnDocumentCreated(wjs.c_str(), nullptr);
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
void eval_impl(const std::string &js) override {
|
|
3565
|
+
auto wjs = widen_string(js);
|
|
3566
|
+
m_webview->ExecuteScript(wjs.c_str(), nullptr);
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
void set_html_impl(const std::string &html) override {
|
|
3570
|
+
m_webview->NavigateToString(widen_string(html).c_str());
|
|
3571
|
+
}
|
|
3572
|
+
|
|
3573
|
+
private:
|
|
3574
|
+
bool embed(HWND wnd, bool debug, msg_cb_t cb) {
|
|
3575
|
+
std::atomic_flag flag = ATOMIC_FLAG_INIT;
|
|
3576
|
+
flag.test_and_set();
|
|
3577
|
+
|
|
3578
|
+
wchar_t currentExePath[MAX_PATH];
|
|
3579
|
+
GetModuleFileNameW(nullptr, currentExePath, MAX_PATH);
|
|
3580
|
+
wchar_t *currentExeName = PathFindFileNameW(currentExePath);
|
|
3581
|
+
|
|
3582
|
+
wchar_t dataPath[MAX_PATH];
|
|
3583
|
+
if (!SUCCEEDED(
|
|
3584
|
+
SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr, 0, dataPath))) {
|
|
3585
|
+
return false;
|
|
3586
|
+
}
|
|
3587
|
+
wchar_t userDataFolder[MAX_PATH];
|
|
3588
|
+
PathCombineW(userDataFolder, dataPath, currentExeName);
|
|
3589
|
+
|
|
3590
|
+
m_com_handler = new webview2_com_handler(
|
|
3591
|
+
wnd, cb,
|
|
3592
|
+
[&](ICoreWebView2Controller *controller, ICoreWebView2 *webview) {
|
|
3593
|
+
if (!controller || !webview) {
|
|
3594
|
+
flag.clear();
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
controller->AddRef();
|
|
3598
|
+
webview->AddRef();
|
|
3599
|
+
m_controller = controller;
|
|
3600
|
+
m_webview = webview;
|
|
3601
|
+
flag.clear();
|
|
3602
|
+
});
|
|
3603
|
+
|
|
3604
|
+
m_com_handler->set_attempt_handler([&] {
|
|
3605
|
+
return m_webview2_loader.create_environment_with_options(
|
|
3606
|
+
nullptr, userDataFolder, nullptr, m_com_handler);
|
|
3607
|
+
});
|
|
3608
|
+
m_com_handler->try_create_environment();
|
|
3609
|
+
|
|
3610
|
+
// Pump the message loop until WebView2 has finished initialization.
|
|
3611
|
+
bool got_quit_msg = false;
|
|
3612
|
+
MSG msg;
|
|
3613
|
+
while (flag.test_and_set() && GetMessageW(&msg, nullptr, 0, 0) >= 0) {
|
|
3614
|
+
if (msg.message == WM_QUIT) {
|
|
3615
|
+
got_quit_msg = true;
|
|
3616
|
+
break;
|
|
3617
|
+
}
|
|
3618
|
+
TranslateMessage(&msg);
|
|
3619
|
+
DispatchMessageW(&msg);
|
|
3620
|
+
}
|
|
3621
|
+
if (got_quit_msg) {
|
|
3622
|
+
return false;
|
|
3623
|
+
}
|
|
3624
|
+
if (!m_controller || !m_webview) {
|
|
3625
|
+
return false;
|
|
3626
|
+
}
|
|
3627
|
+
ICoreWebView2Settings *settings = nullptr;
|
|
3628
|
+
auto res = m_webview->get_Settings(&settings);
|
|
3629
|
+
if (res != S_OK) {
|
|
3630
|
+
return false;
|
|
3631
|
+
}
|
|
3632
|
+
res = settings->put_AreDevToolsEnabled(debug ? TRUE : FALSE);
|
|
3633
|
+
if (res != S_OK) {
|
|
3634
|
+
return false;
|
|
3635
|
+
}
|
|
3636
|
+
res = settings->put_IsStatusBarEnabled(FALSE);
|
|
3637
|
+
if (res != S_OK) {
|
|
3638
|
+
return false;
|
|
3639
|
+
}
|
|
3640
|
+
init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}");
|
|
3641
|
+
resize_webview();
|
|
3642
|
+
m_controller->put_IsVisible(TRUE);
|
|
3643
|
+
ShowWindow(m_widget, SW_SHOW);
|
|
3644
|
+
UpdateWindow(m_widget);
|
|
3645
|
+
if (m_owns_window) {
|
|
3646
|
+
focus_webview();
|
|
3647
|
+
}
|
|
3648
|
+
return true;
|
|
3649
|
+
}
|
|
3650
|
+
|
|
3651
|
+
void resize_widget() {
|
|
3652
|
+
if (m_widget) {
|
|
3653
|
+
RECT r{};
|
|
3654
|
+
if (GetClientRect(GetParent(m_widget), &r)) {
|
|
3655
|
+
MoveWindow(m_widget, r.left, r.top, r.right - r.left, r.bottom - r.top,
|
|
3656
|
+
TRUE);
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
|
|
3661
|
+
void resize_webview() {
|
|
3662
|
+
if (m_widget && m_controller) {
|
|
3663
|
+
RECT bounds{};
|
|
3664
|
+
if (GetClientRect(m_widget, &bounds)) {
|
|
3665
|
+
m_controller->put_Bounds(bounds);
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
|
|
3670
|
+
void focus_webview() {
|
|
3671
|
+
if (m_controller) {
|
|
3672
|
+
m_controller->MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC);
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3676
|
+
bool is_webview2_available() const noexcept {
|
|
3677
|
+
LPWSTR version_info = nullptr;
|
|
3678
|
+
auto res = m_webview2_loader.get_available_browser_version_string(
|
|
3679
|
+
nullptr, &version_info);
|
|
3680
|
+
// The result will be equal to HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)
|
|
3681
|
+
// if the WebView2 runtime is not installed.
|
|
3682
|
+
auto ok = SUCCEEDED(res) && version_info;
|
|
3683
|
+
if (version_info) {
|
|
3684
|
+
CoTaskMemFree(version_info);
|
|
3685
|
+
}
|
|
3686
|
+
return ok;
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3689
|
+
void on_dpi_changed(int dpi) {
|
|
3690
|
+
auto scaled_size = get_scaled_size(m_dpi, dpi);
|
|
3691
|
+
auto frame_size =
|
|
3692
|
+
make_window_frame_size(m_window, scaled_size.cx, scaled_size.cy, dpi);
|
|
3693
|
+
SetWindowPos(m_window, nullptr, 0, 0, frame_size.cx, frame_size.cy,
|
|
3694
|
+
SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_FRAMECHANGED);
|
|
3695
|
+
m_dpi = dpi;
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3698
|
+
SIZE get_size() const {
|
|
3699
|
+
RECT bounds;
|
|
3700
|
+
GetClientRect(m_window, &bounds);
|
|
3701
|
+
auto width = bounds.right - bounds.left;
|
|
3702
|
+
auto height = bounds.bottom - bounds.top;
|
|
3703
|
+
return {width, height};
|
|
3704
|
+
}
|
|
3705
|
+
|
|
3706
|
+
SIZE get_scaled_size(int from_dpi, int to_dpi) const {
|
|
3707
|
+
auto size = get_size();
|
|
3708
|
+
return scale_size(size.cx, size.cy, from_dpi, to_dpi);
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
void on_system_setting_change(const wchar_t *area) {
|
|
3712
|
+
// Detect light/dark mode change in system.
|
|
3713
|
+
if (lstrcmpW(area, L"ImmersiveColorSet") == 0) {
|
|
3714
|
+
apply_window_theme(m_window);
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
// Blocks while depleting the run loop of events.
|
|
3719
|
+
void deplete_run_loop_event_queue() {
|
|
3720
|
+
bool done{};
|
|
3721
|
+
dispatch([&] { done = true; });
|
|
3722
|
+
while (!done) {
|
|
3723
|
+
MSG msg;
|
|
3724
|
+
if (GetMessageW(&msg, nullptr, 0, 0) > 0) {
|
|
3725
|
+
TranslateMessage(&msg);
|
|
3726
|
+
DispatchMessageW(&msg);
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3731
|
+
// The app is expected to call CoInitializeEx before
|
|
3732
|
+
// CreateCoreWebView2EnvironmentWithOptions.
|
|
3733
|
+
// Source: https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl#createcorewebview2environmentwithoptions
|
|
3734
|
+
com_init_wrapper m_com_init;
|
|
3735
|
+
HWND m_window = nullptr;
|
|
3736
|
+
HWND m_widget = nullptr;
|
|
3737
|
+
HWND m_message_window = nullptr;
|
|
3738
|
+
POINT m_minsz = POINT{0, 0};
|
|
3739
|
+
POINT m_maxsz = POINT{0, 0};
|
|
3740
|
+
DWORD m_main_thread = GetCurrentThreadId();
|
|
3741
|
+
ICoreWebView2 *m_webview = nullptr;
|
|
3742
|
+
ICoreWebView2Controller *m_controller = nullptr;
|
|
3743
|
+
webview2_com_handler *m_com_handler = nullptr;
|
|
3744
|
+
mswebview2::loader m_webview2_loader;
|
|
3745
|
+
int m_dpi{};
|
|
3746
|
+
bool m_owns_window{};
|
|
3747
|
+
};
|
|
3748
|
+
|
|
3749
|
+
} // namespace detail
|
|
3750
|
+
|
|
3751
|
+
using browser_engine = detail::win32_edge_engine;
|
|
3752
|
+
|
|
3753
|
+
} // namespace webview
|
|
3754
|
+
|
|
3755
|
+
#endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */
|
|
3756
|
+
|
|
3757
|
+
namespace webview {
|
|
3758
|
+
using webview = browser_engine;
|
|
3759
|
+
} // namespace webview
|
|
3760
|
+
|
|
3761
|
+
WEBVIEW_API webview_t webview_create(int debug, void *wnd) {
|
|
3762
|
+
auto w = new webview::webview(debug, wnd);
|
|
3763
|
+
if (!w->window()) {
|
|
3764
|
+
delete w;
|
|
3765
|
+
return nullptr;
|
|
3766
|
+
}
|
|
3767
|
+
return w;
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
WEBVIEW_API void webview_destroy(webview_t w) {
|
|
3771
|
+
delete static_cast<webview::webview *>(w);
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
WEBVIEW_API void webview_run(webview_t w) {
|
|
3775
|
+
static_cast<webview::webview *>(w)->run();
|
|
3776
|
+
}
|
|
3777
|
+
|
|
3778
|
+
WEBVIEW_API void webview_terminate(webview_t w) {
|
|
3779
|
+
static_cast<webview::webview *>(w)->terminate();
|
|
3780
|
+
}
|
|
3781
|
+
|
|
3782
|
+
WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t, void *),
|
|
3783
|
+
void *arg) {
|
|
3784
|
+
static_cast<webview::webview *>(w)->dispatch([=]() { fn(w, arg); });
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
WEBVIEW_API void *webview_get_window(webview_t w) {
|
|
3788
|
+
return static_cast<webview::webview *>(w)->window();
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
WEBVIEW_API void *webview_get_native_handle(webview_t w,
|
|
3792
|
+
webview_native_handle_kind_t kind) {
|
|
3793
|
+
auto *w_ = static_cast<webview::webview *>(w);
|
|
3794
|
+
switch (kind) {
|
|
3795
|
+
case WEBVIEW_NATIVE_HANDLE_KIND_UI_WINDOW:
|
|
3796
|
+
return w_->window();
|
|
3797
|
+
case WEBVIEW_NATIVE_HANDLE_KIND_UI_WIDGET:
|
|
3798
|
+
return w_->widget();
|
|
3799
|
+
case WEBVIEW_NATIVE_HANDLE_KIND_BROWSER_CONTROLLER:
|
|
3800
|
+
return w_->browser_controller();
|
|
3801
|
+
default:
|
|
3802
|
+
return nullptr;
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
|
|
3806
|
+
WEBVIEW_API void webview_set_title(webview_t w, const char *title) {
|
|
3807
|
+
static_cast<webview::webview *>(w)->set_title(title);
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
WEBVIEW_API void webview_set_size(webview_t w, int width, int height,
|
|
3811
|
+
webview_hint_t hints) {
|
|
3812
|
+
static_cast<webview::webview *>(w)->set_size(width, height, hints);
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3815
|
+
WEBVIEW_API void webview_set_decorated(webview_t w, int decorated) {
|
|
3816
|
+
static_cast<webview::webview *>(w)->set_decorated(decorated != 0);
|
|
3817
|
+
}
|
|
3818
|
+
|
|
3819
|
+
WEBVIEW_API int webview_window_control(webview_t w, webview_window_action_t action) {
|
|
3820
|
+
return static_cast<webview::webview *>(w)->window_control(
|
|
3821
|
+
static_cast<int>(action));
|
|
3822
|
+
}
|
|
3823
|
+
|
|
3824
|
+
WEBVIEW_API int webview_window_begin_move_drag(webview_t w) {
|
|
3825
|
+
return static_cast<webview::webview *>(w)->begin_move_drag();
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3828
|
+
WEBVIEW_API void webview_navigate(webview_t w, const char *url) {
|
|
3829
|
+
static_cast<webview::webview *>(w)->navigate(url);
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
WEBVIEW_API void webview_set_html(webview_t w, const char *html) {
|
|
3833
|
+
static_cast<webview::webview *>(w)->set_html(html);
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
WEBVIEW_API void webview_init(webview_t w, const char *js) {
|
|
3837
|
+
static_cast<webview::webview *>(w)->init(js);
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3840
|
+
WEBVIEW_API void webview_eval(webview_t w, const char *js) {
|
|
3841
|
+
static_cast<webview::webview *>(w)->eval(js);
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
WEBVIEW_API void webview_bind(webview_t w, const char *name,
|
|
3845
|
+
void (*fn)(const char *seq, const char *req,
|
|
3846
|
+
void *arg),
|
|
3847
|
+
void *arg) {
|
|
3848
|
+
static_cast<webview::webview *>(w)->bind(
|
|
3849
|
+
name,
|
|
3850
|
+
[=](const std::string &seq, const std::string &req, void *arg) {
|
|
3851
|
+
fn(seq.c_str(), req.c_str(), arg);
|
|
3852
|
+
},
|
|
3853
|
+
arg);
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
WEBVIEW_API void webview_unbind(webview_t w, const char *name) {
|
|
3857
|
+
static_cast<webview::webview *>(w)->unbind(name);
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3860
|
+
WEBVIEW_API void webview_return(webview_t w, const char *seq, int status,
|
|
3861
|
+
const char *result) {
|
|
3862
|
+
static_cast<webview::webview *>(w)->resolve(seq, status, result);
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
WEBVIEW_API const webview_version_info_t *webview_version(void) {
|
|
3866
|
+
return &webview::detail::library_version_info;
|
|
3867
|
+
}
|
|
3868
|
+
|
|
3869
|
+
#endif /* WEBVIEW_HEADER */
|
|
3870
|
+
#endif /* __cplusplus */
|
|
3871
|
+
#endif /* WEBVIEW_H */
|