@zappdev/cli 0.1.0
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 +55 -0
- package/dist/zapp-cli.js +9471 -0
- package/native/src/app/app.zc +490 -0
- package/native/src/event/event.zc +24 -0
- package/native/src/event/events.zc +70 -0
- package/native/src/platform/darwin/backend.zc +923 -0
- package/native/src/platform/darwin/backend_bootstrap.zc +9 -0
- package/native/src/platform/darwin/bootstrap.zc +9 -0
- package/native/src/platform/darwin/engine_jsc.zc +86 -0
- package/native/src/platform/darwin/engine_qjs.zc +92 -0
- package/native/src/platform/darwin/platform.zc +156 -0
- package/native/src/platform/darwin/webview.zc +550 -0
- package/native/src/platform/darwin/webview_bootstrap.zc +9 -0
- package/native/src/platform/darwin/window.zc +1223 -0
- package/native/src/platform/darwin/worker/common.zc +223 -0
- package/native/src/platform/darwin/worker/core/base64_core.zc +29 -0
- package/native/src/platform/darwin/worker/core/crypto_core.zc +19 -0
- package/native/src/platform/darwin/worker/core/encoding_core.zc +32 -0
- package/native/src/platform/darwin/worker/core/fetch_core.zc +145 -0
- package/native/src/platform/darwin/worker/core/url_core.zc +69 -0
- package/native/src/platform/darwin/worker/core/websocket_core.zc +179 -0
- package/native/src/platform/darwin/worker/dispatch.zc +55 -0
- package/native/src/platform/darwin/worker/jsc/base64_jsc.zc +39 -0
- package/native/src/platform/darwin/worker/jsc/crypto_jsc.zc +49 -0
- package/native/src/platform/darwin/worker/jsc/encoding_jsc.zc +86 -0
- package/native/src/platform/darwin/worker/jsc/fetch_jsc.zc +149 -0
- package/native/src/platform/darwin/worker/jsc/url_jsc.zc +54 -0
- package/native/src/platform/darwin/worker/jsc/websocket_jsc.zc +127 -0
- package/native/src/platform/darwin/worker/jsc.zc +670 -0
- package/native/src/platform/darwin/worker/mod.zc +30 -0
- package/native/src/platform/darwin/worker/qjs/fetch_qjs.zc +233 -0
- package/native/src/platform/darwin/worker/qjs/qjs_macros.zc +23 -0
- package/native/src/platform/darwin/worker/qjs/websocket_qjs.zc +223 -0
- package/native/src/platform/darwin/worker/qjs.zc +1053 -0
- package/native/src/platform/darwin/worker/timers.zc +149 -0
- package/native/src/platform/darwin/worker/timers_qjs.zc +209 -0
- package/native/src/platform/platform.zc +64 -0
- package/native/src/platform/shared/log.zc +156 -0
- package/native/src/platform/shared/worker/qjs/base64_qjs.zc +38 -0
- package/native/src/platform/shared/worker/qjs/crypto_qjs.zc +44 -0
- package/native/src/platform/shared/worker/qjs/encoding_qjs.zc +95 -0
- package/native/src/platform/shared/worker/qjs/url_qjs.zc +65 -0
- package/native/src/platform/shared/worker_registry.zc +206 -0
- package/native/src/platform/window.zc +446 -0
- package/native/src/platform/windows/backend.zc +452 -0
- package/native/src/platform/windows/backend_bootstrap.zc +9 -0
- package/native/src/platform/windows/bootstrap.zc +9 -0
- package/native/src/platform/windows/engine_qjs.zc +60 -0
- package/native/src/platform/windows/platform.zc +387 -0
- package/native/src/platform/windows/webview.zc +1175 -0
- package/native/src/platform/windows/webview_bootstrap.zc +9 -0
- package/native/src/platform/windows/window.zc +1271 -0
- package/native/src/platform/windows/worker/common.zc +409 -0
- package/native/src/platform/windows/worker/core/base64_core.zc +52 -0
- package/native/src/platform/windows/worker/core/crypto_core.zc +34 -0
- package/native/src/platform/windows/worker/core/encoding_core.zc +60 -0
- package/native/src/platform/windows/worker/core/fetch_core.zc +274 -0
- package/native/src/platform/windows/worker/core/url_core.zc +216 -0
- package/native/src/platform/windows/worker/core/websocket_core.zc +343 -0
- package/native/src/platform/windows/worker/dispatch.zc +34 -0
- package/native/src/platform/windows/worker/mod.zc +46 -0
- package/native/src/platform/windows/worker/qjs/fetch_qjs.zc +255 -0
- package/native/src/platform/windows/worker/qjs/websocket_qjs.zc +263 -0
- package/native/src/platform/windows/worker/qjs.zc +1049 -0
- package/native/src/platform/windows/worker/timers_qjs.zc +288 -0
- package/native/src/platform/worker.zc +8 -0
- package/native/src/service/service.zc +228 -0
- package/native/vendor/quickjs-ng/.gitattributes +4 -0
- package/native/vendor/quickjs-ng/.github/dependabot.yml +7 -0
- package/native/vendor/quickjs-ng/.github/workflows/ci.yml +812 -0
- package/native/vendor/quickjs-ng/.github/workflows/docs.yml +49 -0
- package/native/vendor/quickjs-ng/.github/workflows/release.yml +162 -0
- package/native/vendor/quickjs-ng/.github/workflows/test-docs.yml +23 -0
- package/native/vendor/quickjs-ng/.github/workflows/tsan.yml +32 -0
- package/native/vendor/quickjs-ng/.github/workflows/valgrind.yml +33 -0
- package/native/vendor/quickjs-ng/.gitmodules +5 -0
- package/native/vendor/quickjs-ng/CMakeLists.txt +553 -0
- package/native/vendor/quickjs-ng/LICENSE +24 -0
- package/native/vendor/quickjs-ng/Makefile +149 -0
- package/native/vendor/quickjs-ng/amalgam.js +53 -0
- package/native/vendor/quickjs-ng/api-test.c +927 -0
- package/native/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
- package/native/vendor/quickjs-ng/builtin-array-fromasync.js +36 -0
- package/native/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
- package/native/vendor/quickjs-ng/builtin-iterator-zip-keyed.js +194 -0
- package/native/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
- package/native/vendor/quickjs-ng/builtin-iterator-zip.js +210 -0
- package/native/vendor/quickjs-ng/ctest.c +17 -0
- package/native/vendor/quickjs-ng/cutils.h +2013 -0
- package/native/vendor/quickjs-ng/cxxtest.cc +2 -0
- package/native/vendor/quickjs-ng/dtoa.c +1619 -0
- package/native/vendor/quickjs-ng/dtoa.h +87 -0
- package/native/vendor/quickjs-ng/examples/fib.c +67 -0
- package/native/vendor/quickjs-ng/examples/fib_module.js +10 -0
- package/native/vendor/quickjs-ng/examples/hello.js +1 -0
- package/native/vendor/quickjs-ng/examples/hello_module.js +6 -0
- package/native/vendor/quickjs-ng/examples/meson.build +17 -0
- package/native/vendor/quickjs-ng/examples/pi_bigint.js +118 -0
- package/native/vendor/quickjs-ng/examples/point.c +154 -0
- package/native/vendor/quickjs-ng/examples/test_fib.js +8 -0
- package/native/vendor/quickjs-ng/examples/test_point.js +43 -0
- package/native/vendor/quickjs-ng/fuzz.c +51 -0
- package/native/vendor/quickjs-ng/gen/function_source.c +81 -0
- package/native/vendor/quickjs-ng/gen/hello.c +53 -0
- package/native/vendor/quickjs-ng/gen/hello_module.c +106 -0
- package/native/vendor/quickjs-ng/gen/repl.c +3053 -0
- package/native/vendor/quickjs-ng/gen/standalone.c +324 -0
- package/native/vendor/quickjs-ng/gen/test_fib.c +81 -0
- package/native/vendor/quickjs-ng/libregexp-opcode.h +58 -0
- package/native/vendor/quickjs-ng/libregexp.c +2687 -0
- package/native/vendor/quickjs-ng/libregexp.h +98 -0
- package/native/vendor/quickjs-ng/libunicode-table.h +4707 -0
- package/native/vendor/quickjs-ng/libunicode.c +1746 -0
- package/native/vendor/quickjs-ng/libunicode.h +126 -0
- package/native/vendor/quickjs-ng/list.h +107 -0
- package/native/vendor/quickjs-ng/lre-test.c +73 -0
- package/native/vendor/quickjs-ng/meson.build +684 -0
- package/native/vendor/quickjs-ng/meson_options.txt +6 -0
- package/native/vendor/quickjs-ng/qjs-wasi-reactor.c +208 -0
- package/native/vendor/quickjs-ng/qjs.c +748 -0
- package/native/vendor/quickjs-ng/qjsc.c +673 -0
- package/native/vendor/quickjs-ng/quickjs-atom.h +267 -0
- package/native/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
- package/native/vendor/quickjs-ng/quickjs-libc.c +4986 -0
- package/native/vendor/quickjs-ng/quickjs-libc.h +79 -0
- package/native/vendor/quickjs-ng/quickjs-opcode.h +369 -0
- package/native/vendor/quickjs-ng/quickjs.c +60259 -0
- package/native/vendor/quickjs-ng/quickjs.h +1419 -0
- package/native/vendor/quickjs-ng/repl.js +1927 -0
- package/native/vendor/quickjs-ng/run-test262.c +2417 -0
- package/native/vendor/quickjs-ng/standalone.js +129 -0
- package/native/vendor/quickjs-ng/tests/assert.js +49 -0
- package/native/vendor/quickjs-ng/tests/bug1221.js +16 -0
- package/native/vendor/quickjs-ng/tests/bug1296.js +12 -0
- package/native/vendor/quickjs-ng/tests/bug1297.js +22 -0
- package/native/vendor/quickjs-ng/tests/bug1301.js +21 -0
- package/native/vendor/quickjs-ng/tests/bug1302.js +24 -0
- package/native/vendor/quickjs-ng/tests/bug1305.js +26 -0
- package/native/vendor/quickjs-ng/tests/bug1318.js +54 -0
- package/native/vendor/quickjs-ng/tests/bug1352.js +8 -0
- package/native/vendor/quickjs-ng/tests/bug1354.js +6 -0
- package/native/vendor/quickjs-ng/tests/bug1355.js +58 -0
- package/native/vendor/quickjs-ng/tests/bug1368.js +9 -0
- package/native/vendor/quickjs-ng/tests/bug39/1.js +6 -0
- package/native/vendor/quickjs-ng/tests/bug39/2.js +6 -0
- package/native/vendor/quickjs-ng/tests/bug39/3.js +7 -0
- package/native/vendor/quickjs-ng/tests/bug488-upstream.js +7 -0
- package/native/vendor/quickjs-ng/tests/bug633/0.js +7 -0
- package/native/vendor/quickjs-ng/tests/bug633/1.js +4 -0
- package/native/vendor/quickjs-ng/tests/bug633/2.js +4 -0
- package/native/vendor/quickjs-ng/tests/bug633/3.js +4 -0
- package/native/vendor/quickjs-ng/tests/bug645/0.js +4 -0
- package/native/vendor/quickjs-ng/tests/bug645/1.js +9 -0
- package/native/vendor/quickjs-ng/tests/bug645/2.js +7 -0
- package/native/vendor/quickjs-ng/tests/bug648.js +13 -0
- package/native/vendor/quickjs-ng/tests/bug652.js +4 -0
- package/native/vendor/quickjs-ng/tests/bug741.js +19 -0
- package/native/vendor/quickjs-ng/tests/bug775.js +7 -0
- package/native/vendor/quickjs-ng/tests/bug776.js +7 -0
- package/native/vendor/quickjs-ng/tests/bug832.js +2 -0
- package/native/vendor/quickjs-ng/tests/bug858.js +26 -0
- package/native/vendor/quickjs-ng/tests/bug904.js +6 -0
- package/native/vendor/quickjs-ng/tests/bug988.js +7 -0
- package/native/vendor/quickjs-ng/tests/bug999.js +3 -0
- package/native/vendor/quickjs-ng/tests/destructured-export.js +8 -0
- package/native/vendor/quickjs-ng/tests/detect_module/0.js +1 -0
- package/native/vendor/quickjs-ng/tests/detect_module/1.js +2 -0
- package/native/vendor/quickjs-ng/tests/detect_module/2.js +1 -0
- package/native/vendor/quickjs-ng/tests/detect_module/3.js +8 -0
- package/native/vendor/quickjs-ng/tests/detect_module/4.js +3 -0
- package/native/vendor/quickjs-ng/tests/empty.js +0 -0
- package/native/vendor/quickjs-ng/tests/fixture_cyclic_import.js +2 -0
- package/native/vendor/quickjs-ng/tests/fixture_string_exports.js +12 -0
- package/native/vendor/quickjs-ng/tests/function_source.js +14 -0
- package/native/vendor/quickjs-ng/tests/microbench.js +1267 -0
- package/native/vendor/quickjs-ng/tests/null_or_undefined.js +38 -0
- package/native/vendor/quickjs-ng/tests/str-pad-leak.js +5 -0
- package/native/vendor/quickjs-ng/tests/test_bigint.js +107 -0
- package/native/vendor/quickjs-ng/tests/test_bjson.js +366 -0
- package/native/vendor/quickjs-ng/tests/test_builtin.js +1314 -0
- package/native/vendor/quickjs-ng/tests/test_closure.js +220 -0
- package/native/vendor/quickjs-ng/tests/test_cyclic_import.js +12 -0
- package/native/vendor/quickjs-ng/tests/test_domexception.js +35 -0
- package/native/vendor/quickjs-ng/tests/test_language.js +755 -0
- package/native/vendor/quickjs-ng/tests/test_loop.js +367 -0
- package/native/vendor/quickjs-ng/tests/test_queue_microtask.js +39 -0
- package/native/vendor/quickjs-ng/tests/test_std.js +340 -0
- package/native/vendor/quickjs-ng/tests/test_string_exports.js +25 -0
- package/native/vendor/quickjs-ng/tests/test_worker.js +43 -0
- package/native/vendor/quickjs-ng/tests/test_worker_module.js +30 -0
- package/native/vendor/quickjs-ng/tests.conf +14 -0
- package/native/vendor/quickjs-ng/unicode_download.sh +19 -0
- package/native/vendor/quickjs-ng/unicode_gen.c +3108 -0
- package/native/vendor/quickjs-ng/unicode_gen_def.h +310 -0
- package/native/vendor/quickjs-ng/update-version.sh +32 -0
- package/native/vendor/webview2/include/WebView2.h +60636 -0
- package/native/vendor/webview2/include/WebView2EnvironmentOptions.h +406 -0
- package/package.json +33 -0
- package/src/backend.ts +139 -0
- package/src/build-config.ts +87 -0
- package/src/build.ts +276 -0
- package/src/common.ts +195 -0
- package/src/config.ts +89 -0
- package/src/dev.ts +164 -0
- package/src/generate.ts +200 -0
- package/src/icons.ts +116 -0
- package/src/init.ts +190 -0
- package/src/package.ts +150 -0
- package/src/zapp-cli.ts +263 -0
|
@@ -0,0 +1,1175 @@
|
|
|
1
|
+
import "./window.zc";
|
|
2
|
+
import "./webview_bootstrap.zc";
|
|
3
|
+
|
|
4
|
+
//> windows: link: -lshlwapi -lversion
|
|
5
|
+
//> windows: cflags: -DCINTERFACE -DCOBJMACROS
|
|
6
|
+
|
|
7
|
+
raw {
|
|
8
|
+
#ifdef _WIN32
|
|
9
|
+
#include <windows.h>
|
|
10
|
+
#include <stdlib.h>
|
|
11
|
+
#include <stdio.h>
|
|
12
|
+
#include <string.h>
|
|
13
|
+
#include <shlwapi.h>
|
|
14
|
+
#include <shlobj.h>
|
|
15
|
+
#include "WebView2.h"
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Self-contained WebView2 loader
|
|
19
|
+
//
|
|
20
|
+
// Finds the WebView2 runtime via registry and loads EmbeddedBrowserWebView.dll
|
|
21
|
+
// directly, eliminating the need for WebView2Loader.dll or the MSVC-only
|
|
22
|
+
// WebView2LoaderStatic.lib. Based on OpenWebView2Loader by jchv.
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
// Undocumented internal entry point exported by EmbeddedBrowserWebView.dll
|
|
26
|
+
typedef HRESULT (STDMETHODCALLTYPE *CreateWebViewEnvInternalFn)(
|
|
27
|
+
int unknown,
|
|
28
|
+
int runtimeType, // 0 = installed, 1 = redistributable
|
|
29
|
+
PCWSTR userDataDir,
|
|
30
|
+
IUnknown* options,
|
|
31
|
+
ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* handler
|
|
32
|
+
);
|
|
33
|
+
static CreateWebViewEnvInternalFn zapp_CreateEnvInternal = NULL;
|
|
34
|
+
|
|
35
|
+
// Stable channel GUID for EdgeUpdate registry
|
|
36
|
+
static const wchar_t* ZAPP_WV2_STABLE_GUID =
|
|
37
|
+
L"{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}";
|
|
38
|
+
|
|
39
|
+
#if defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64)
|
|
40
|
+
static const wchar_t* ZAPP_WV2_EMBEDDED_DLL_SUBPATH =
|
|
41
|
+
L"\\EBWebView\\x64\\EmbeddedBrowserWebView.dll";
|
|
42
|
+
#elif defined(__aarch64__) || defined(_M_ARM64)
|
|
43
|
+
static const wchar_t* ZAPP_WV2_EMBEDDED_DLL_SUBPATH =
|
|
44
|
+
L"\\EBWebView\\arm64\\EmbeddedBrowserWebView.dll";
|
|
45
|
+
#else
|
|
46
|
+
static const wchar_t* ZAPP_WV2_EMBEDDED_DLL_SUBPATH =
|
|
47
|
+
L"\\EBWebView\\x86\\EmbeddedBrowserWebView.dll";
|
|
48
|
+
#endif
|
|
49
|
+
|
|
50
|
+
// Try to find the WebView2 client DLL path from a registry root + subkey.
|
|
51
|
+
// Returns 1 and writes the full DLL path into outPath on success.
|
|
52
|
+
static int zapp_wv2_find_in_registry(HKEY root, const wchar_t* subkey,
|
|
53
|
+
wchar_t* outPath, DWORD outPathSize) {
|
|
54
|
+
HKEY hKey = NULL;
|
|
55
|
+
DWORD cbData = outPathSize * sizeof(wchar_t);
|
|
56
|
+
wchar_t runtimePath[MAX_PATH];
|
|
57
|
+
|
|
58
|
+
if (RegOpenKeyExW(root, subkey, 0,
|
|
59
|
+
KEY_READ | KEY_WOW64_32KEY, &hKey) != ERROR_SUCCESS) {
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
cbData = sizeof(runtimePath);
|
|
63
|
+
LSTATUS status = RegQueryValueExW(hKey, L"EBWebView", NULL, NULL,
|
|
64
|
+
(LPBYTE)runtimePath, &cbData);
|
|
65
|
+
RegCloseKey(hKey);
|
|
66
|
+
if (status != ERROR_SUCCESS) return 0;
|
|
67
|
+
|
|
68
|
+
// runtimePath now contains something like:
|
|
69
|
+
// C:\Program Files (x86)\Microsoft\EdgeWebView\Application\131.0.2903.86
|
|
70
|
+
// Append the architecture-specific DLL subpath
|
|
71
|
+
_snwprintf(outPath, outPathSize, L"%s%s",
|
|
72
|
+
runtimePath, ZAPP_WV2_EMBEDDED_DLL_SUBPATH);
|
|
73
|
+
outPath[outPathSize - 1] = 0;
|
|
74
|
+
|
|
75
|
+
return GetFileAttributesW(outPath) != INVALID_FILE_ATTRIBUTES;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static int zapp_webview2_loader_init(void) {
|
|
79
|
+
if (zapp_CreateEnvInternal) return 1;
|
|
80
|
+
|
|
81
|
+
wchar_t dllPath[MAX_PATH * 2];
|
|
82
|
+
wchar_t regKey[256];
|
|
83
|
+
|
|
84
|
+
// Build the registry subkey for the stable channel
|
|
85
|
+
_snwprintf(regKey, 256,
|
|
86
|
+
L"Software\\Microsoft\\EdgeUpdate\\ClientState\\%s",
|
|
87
|
+
ZAPP_WV2_STABLE_GUID);
|
|
88
|
+
regKey[255] = 0;
|
|
89
|
+
|
|
90
|
+
// Search order: HKCU (per-user install), then HKLM (system install)
|
|
91
|
+
int found = zapp_wv2_find_in_registry(HKEY_CURRENT_USER, regKey,
|
|
92
|
+
dllPath, MAX_PATH * 2)
|
|
93
|
+
|| zapp_wv2_find_in_registry(HKEY_LOCAL_MACHINE, regKey,
|
|
94
|
+
dllPath, MAX_PATH * 2);
|
|
95
|
+
|
|
96
|
+
if (!found) {
|
|
97
|
+
fprintf(stderr, "[zapp] WebView2 runtime not found. "
|
|
98
|
+
"Install Microsoft Edge WebView2 Runtime.\n");
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
HMODULE hmod = LoadLibraryW(dllPath);
|
|
103
|
+
if (!hmod) {
|
|
104
|
+
fprintf(stderr, "[zapp] Failed to load %ls (error %lu)\n",
|
|
105
|
+
dllPath, GetLastError());
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
zapp_CreateEnvInternal = (CreateWebViewEnvInternalFn)
|
|
110
|
+
GetProcAddress(hmod, "CreateWebViewEnvironmentWithOptionsInternal");
|
|
111
|
+
if (!zapp_CreateEnvInternal) {
|
|
112
|
+
fprintf(stderr, "[zapp] CreateWebViewEnvironmentWithOptionsInternal "
|
|
113
|
+
"not found in runtime DLL.\n");
|
|
114
|
+
FreeLibrary(hmod);
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
return 1;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// Forward declarations
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
extern void* app_get_active(void);
|
|
125
|
+
extern char* app_get_bootstrap_name(void);
|
|
126
|
+
extern bool app_get_bootstrap_application_should_terminate_after_last_window_closed(void);
|
|
127
|
+
extern bool app_get_bootstrap_web_content_inspectable(void);
|
|
128
|
+
extern int app_get_bootstrap_max_workers(void);
|
|
129
|
+
extern void zapp_handle_message(void* app_ptr, char* msg_c);
|
|
130
|
+
extern char* service_get_bindings_manifest_json(void);
|
|
131
|
+
extern const char* zapp_build_initial_url(void);
|
|
132
|
+
extern const char* zapp_windows_webview_bootstrap_script(void);
|
|
133
|
+
extern const char* zapp_win_id_for_hwnd(HWND hwnd);
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Per-window WebView2 state
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
#define ZAPP_MAX_WINDOWS 64
|
|
140
|
+
#define WM_ZAPP_EVAL_JS (WM_APP + 1)
|
|
141
|
+
|
|
142
|
+
static ZappWindowEntry zapp_windows[ZAPP_MAX_WINDOWS] = {0};
|
|
143
|
+
|
|
144
|
+
ZappWindowEntry* zapp_window_entry_for_hwnd(HWND hwnd) {
|
|
145
|
+
for (int i = 0; i < ZAPP_MAX_WINDOWS; i++) {
|
|
146
|
+
if (zapp_windows[i].in_use && zapp_windows[i].hwnd == hwnd)
|
|
147
|
+
return &zapp_windows[i];
|
|
148
|
+
}
|
|
149
|
+
return NULL;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
static ZappWindowEntry* zapp_window_entry_alloc(HWND hwnd) {
|
|
153
|
+
for (int i = 0; i < ZAPP_MAX_WINDOWS; i++) {
|
|
154
|
+
if (!zapp_windows[i].in_use) {
|
|
155
|
+
memset(&zapp_windows[i], 0, sizeof(ZappWindowEntry));
|
|
156
|
+
zapp_windows[i].hwnd = hwnd;
|
|
157
|
+
zapp_windows[i].in_use = TRUE;
|
|
158
|
+
sprintf(zapp_windows[i].ownerId, "owner-%p", (void*)hwnd);
|
|
159
|
+
return &zapp_windows[i];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return NULL;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Helpers
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
static wchar_t* zapp_utf8_to_wide(const char* utf8) {
|
|
170
|
+
if (!utf8) return NULL;
|
|
171
|
+
int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
|
|
172
|
+
wchar_t* wide = (wchar_t*)malloc(len * sizeof(wchar_t));
|
|
173
|
+
MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, len);
|
|
174
|
+
return wide;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
static char* zapp_wide_to_utf8(const wchar_t* wide) {
|
|
178
|
+
if (!wide) return NULL;
|
|
179
|
+
int len = WideCharToMultiByte(CP_UTF8, 0, wide, -1, NULL, 0, NULL, NULL);
|
|
180
|
+
char* utf8 = (char*)malloc(len);
|
|
181
|
+
WideCharToMultiByte(CP_UTF8, 0, wide, -1, utf8, len, NULL, NULL);
|
|
182
|
+
return utf8;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
char* zapp_escape_js_string(const char* raw) {
|
|
186
|
+
if (!raw) return _strdup("");
|
|
187
|
+
size_t len = strlen(raw);
|
|
188
|
+
size_t cap = len * 2 + 1;
|
|
189
|
+
char* out = (char*)malloc(cap);
|
|
190
|
+
size_t j = 0;
|
|
191
|
+
for (size_t i = 0; i < len && j < cap - 2; i++) {
|
|
192
|
+
char c = raw[i];
|
|
193
|
+
if (c == '\\') { out[j++] = '\\'; out[j++] = '\\'; }
|
|
194
|
+
else if (c == '\'') { out[j++] = '\\'; out[j++] = '\''; }
|
|
195
|
+
else if (c == '\n') { out[j++] = '\\'; out[j++] = 'n'; }
|
|
196
|
+
else if (c == '\r') { out[j++] = '\\'; out[j++] = 'r'; }
|
|
197
|
+
else { out[j++] = c; }
|
|
198
|
+
}
|
|
199
|
+
out[j] = '\0';
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// COM callback: WebMessageReceived
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
typedef struct {
|
|
208
|
+
ICoreWebView2WebMessageReceivedEventHandlerVtbl* lpVtbl;
|
|
209
|
+
ULONG refCount;
|
|
210
|
+
} ZappMessageHandler;
|
|
211
|
+
|
|
212
|
+
static HRESULT STDMETHODCALLTYPE zmh_QueryInterface(ICoreWebView2WebMessageReceivedEventHandler* This, REFIID riid, void** ppv) {
|
|
213
|
+
(void)riid;
|
|
214
|
+
*ppv = This;
|
|
215
|
+
This->lpVtbl->AddRef(This);
|
|
216
|
+
return S_OK;
|
|
217
|
+
}
|
|
218
|
+
static ULONG STDMETHODCALLTYPE zmh_AddRef(ICoreWebView2WebMessageReceivedEventHandler* This) {
|
|
219
|
+
ZappMessageHandler* self = (ZappMessageHandler*)This;
|
|
220
|
+
return ++self->refCount;
|
|
221
|
+
}
|
|
222
|
+
static ULONG STDMETHODCALLTYPE zmh_Release(ICoreWebView2WebMessageReceivedEventHandler* This) {
|
|
223
|
+
ZappMessageHandler* self = (ZappMessageHandler*)This;
|
|
224
|
+
if (--self->refCount == 0) { free(self); return 0; }
|
|
225
|
+
return self->refCount;
|
|
226
|
+
}
|
|
227
|
+
static HRESULT STDMETHODCALLTYPE zmh_Invoke(
|
|
228
|
+
ICoreWebView2WebMessageReceivedEventHandler* This,
|
|
229
|
+
ICoreWebView2* sender,
|
|
230
|
+
ICoreWebView2WebMessageReceivedEventArgs* args
|
|
231
|
+
) {
|
|
232
|
+
(void)This; (void)sender;
|
|
233
|
+
wchar_t* msgW = NULL;
|
|
234
|
+
HRESULT hr = ICoreWebView2WebMessageReceivedEventArgs_TryGetWebMessageAsString(args, &msgW);
|
|
235
|
+
if (SUCCEEDED(hr) && msgW) {
|
|
236
|
+
char* msgUtf8 = zapp_wide_to_utf8(msgW);
|
|
237
|
+
CoTaskMemFree(msgW);
|
|
238
|
+
if (msgUtf8) {
|
|
239
|
+
void* app_ptr = app_get_active();
|
|
240
|
+
if (app_ptr) {
|
|
241
|
+
zapp_handle_message(app_ptr, msgUtf8);
|
|
242
|
+
}
|
|
243
|
+
free(msgUtf8);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return S_OK;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static ICoreWebView2WebMessageReceivedEventHandlerVtbl zmh_vtbl = {
|
|
250
|
+
zmh_QueryInterface, zmh_AddRef, zmh_Release, zmh_Invoke
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
static ZappMessageHandler* zapp_create_message_handler(void) {
|
|
254
|
+
ZappMessageHandler* h = (ZappMessageHandler*)calloc(1, sizeof(ZappMessageHandler));
|
|
255
|
+
h->lpVtbl = &zmh_vtbl;
|
|
256
|
+
h->refCount = 1;
|
|
257
|
+
return h;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// COM callback: NavigationCompleted (visibility toggle workaround)
|
|
262
|
+
// https://github.com/MicrosoftEdge/WebView2Feedback/issues/1077
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
typedef struct {
|
|
266
|
+
ICoreWebView2NavigationCompletedEventHandlerVtbl* lpVtbl;
|
|
267
|
+
ULONG refCount;
|
|
268
|
+
ICoreWebView2Controller* controller;
|
|
269
|
+
BOOL firstNavDone;
|
|
270
|
+
} ZappNavHandler;
|
|
271
|
+
|
|
272
|
+
static HRESULT STDMETHODCALLTYPE znh_QueryInterface(ICoreWebView2NavigationCompletedEventHandler* This, REFIID riid, void** ppv) {
|
|
273
|
+
(void)riid;
|
|
274
|
+
*ppv = This;
|
|
275
|
+
This->lpVtbl->AddRef(This);
|
|
276
|
+
return S_OK;
|
|
277
|
+
}
|
|
278
|
+
static ULONG STDMETHODCALLTYPE znh_AddRef(ICoreWebView2NavigationCompletedEventHandler* This) {
|
|
279
|
+
return ++((ZappNavHandler*)This)->refCount;
|
|
280
|
+
}
|
|
281
|
+
static ULONG STDMETHODCALLTYPE znh_Release(ICoreWebView2NavigationCompletedEventHandler* This) {
|
|
282
|
+
ZappNavHandler* self = (ZappNavHandler*)This;
|
|
283
|
+
if (--self->refCount == 0) { free(self); return 0; }
|
|
284
|
+
return self->refCount;
|
|
285
|
+
}
|
|
286
|
+
static HRESULT STDMETHODCALLTYPE znh_Invoke(
|
|
287
|
+
ICoreWebView2NavigationCompletedEventHandler* This,
|
|
288
|
+
ICoreWebView2* sender,
|
|
289
|
+
ICoreWebView2NavigationCompletedEventArgs* args
|
|
290
|
+
) {
|
|
291
|
+
(void)sender; (void)args;
|
|
292
|
+
ZappNavHandler* self = (ZappNavHandler*)This;
|
|
293
|
+
if (!self->firstNavDone) {
|
|
294
|
+
self->firstNavDone = TRUE;
|
|
295
|
+
// Workaround: toggle visibility to force WebView2 to composite
|
|
296
|
+
// https://github.com/MicrosoftEdge/WebView2Feedback/issues/1077
|
|
297
|
+
ICoreWebView2Controller_put_IsVisible(self->controller, FALSE);
|
|
298
|
+
ICoreWebView2Controller_put_IsVisible(self->controller, TRUE);
|
|
299
|
+
ICoreWebView2Controller_MoveFocus(self->controller, COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC);
|
|
300
|
+
}
|
|
301
|
+
return S_OK;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
static ICoreWebView2NavigationCompletedEventHandlerVtbl znh_vtbl = {
|
|
305
|
+
znh_QueryInterface, znh_AddRef, znh_Release, znh_Invoke
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
309
|
+
// zapp:// protocol: embedded/file asset serving
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
static ICoreWebView2Environment* zapp_wv2_env = NULL;
|
|
313
|
+
|
|
314
|
+
#ifndef ZAPP_EMBEDDED_ASSET_STRUCT_DEFINED
|
|
315
|
+
#define ZAPP_EMBEDDED_ASSET_STRUCT_DEFINED 1
|
|
316
|
+
struct ZappEmbeddedAsset {
|
|
317
|
+
char* path;
|
|
318
|
+
unsigned char* data;
|
|
319
|
+
int len;
|
|
320
|
+
int is_brotli;
|
|
321
|
+
int uncompressed_len;
|
|
322
|
+
};
|
|
323
|
+
#endif
|
|
324
|
+
|
|
325
|
+
extern struct ZappEmbeddedAsset zapp_embedded_assets[];
|
|
326
|
+
extern int zapp_embedded_assets_count;
|
|
327
|
+
extern int zapp_build_use_embedded_assets(void);
|
|
328
|
+
extern const char* zapp_build_asset_root(void);
|
|
329
|
+
|
|
330
|
+
static const char* zapp_mime_for_path(const char* path) {
|
|
331
|
+
if (!path) return "application/octet-stream";
|
|
332
|
+
const char* dot = strrchr(path, '.');
|
|
333
|
+
if (!dot) return "application/octet-stream";
|
|
334
|
+
if (_stricmp(dot, ".html") == 0 || _stricmp(dot, ".htm") == 0) return "text/html; charset=utf-8";
|
|
335
|
+
if (_stricmp(dot, ".js") == 0 || _stricmp(dot, ".mjs") == 0) return "application/javascript; charset=utf-8";
|
|
336
|
+
if (_stricmp(dot, ".css") == 0) return "text/css; charset=utf-8";
|
|
337
|
+
if (_stricmp(dot, ".json") == 0) return "application/json; charset=utf-8";
|
|
338
|
+
if (_stricmp(dot, ".svg") == 0) return "image/svg+xml";
|
|
339
|
+
if (_stricmp(dot, ".png") == 0) return "image/png";
|
|
340
|
+
if (_stricmp(dot, ".jpg") == 0 || _stricmp(dot, ".jpeg") == 0) return "image/jpeg";
|
|
341
|
+
if (_stricmp(dot, ".gif") == 0) return "image/gif";
|
|
342
|
+
if (_stricmp(dot, ".ico") == 0) return "image/x-icon";
|
|
343
|
+
if (_stricmp(dot, ".woff2") == 0) return "font/woff2";
|
|
344
|
+
if (_stricmp(dot, ".woff") == 0) return "font/woff";
|
|
345
|
+
if (_stricmp(dot, ".ttf") == 0) return "font/ttf";
|
|
346
|
+
if (_stricmp(dot, ".map") == 0) return "application/json; charset=utf-8";
|
|
347
|
+
if (_stricmp(dot, ".wasm") == 0) return "application/wasm";
|
|
348
|
+
return "application/octet-stream";
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
#define ZAPP_LOCALHOST_PREFIX "https://app.localhost/"
|
|
352
|
+
#define ZAPP_LOCALHOST_PREFIX_LEN 22
|
|
353
|
+
|
|
354
|
+
static char* zapp_url_to_rel_path(const char* url) {
|
|
355
|
+
if (!url) return _strdup("index.html");
|
|
356
|
+
|
|
357
|
+
const char* rel = NULL;
|
|
358
|
+
|
|
359
|
+
if (strncmp(url, ZAPP_LOCALHOST_PREFIX, ZAPP_LOCALHOST_PREFIX_LEN) == 0) {
|
|
360
|
+
rel = url + ZAPP_LOCALHOST_PREFIX_LEN;
|
|
361
|
+
} else if (strncmp(url, "zapp://", 7) == 0) {
|
|
362
|
+
const char* after = url + 7;
|
|
363
|
+
const char* slash = strchr(after, '/');
|
|
364
|
+
rel = slash ? slash + 1 : after;
|
|
365
|
+
} else {
|
|
366
|
+
return _strdup("index.html");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
char pathBuf[2048];
|
|
370
|
+
strncpy(pathBuf, rel, sizeof(pathBuf) - 1);
|
|
371
|
+
pathBuf[sizeof(pathBuf) - 1] = '\0';
|
|
372
|
+
char* q = strchr(pathBuf, '?');
|
|
373
|
+
if (q) *q = '\0';
|
|
374
|
+
q = strchr(pathBuf, '#');
|
|
375
|
+
if (q) *q = '\0';
|
|
376
|
+
|
|
377
|
+
while (pathBuf[0] == '/') memmove(pathBuf, pathBuf + 1, strlen(pathBuf));
|
|
378
|
+
if (pathBuf[0] == '\0') return _strdup("index.html");
|
|
379
|
+
|
|
380
|
+
return _strdup(pathBuf);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
static void zapp_send_resource_response(
|
|
384
|
+
ICoreWebView2WebResourceRequestedEventArgs* args,
|
|
385
|
+
const unsigned char* data, int dataLen,
|
|
386
|
+
const char* mime, int statusCode, int is_brotli
|
|
387
|
+
) {
|
|
388
|
+
if (!zapp_wv2_env) return;
|
|
389
|
+
|
|
390
|
+
IStream* stream = SHCreateMemStream(data, (UINT)dataLen);
|
|
391
|
+
if (!stream) stream = SHCreateMemStream((const BYTE*)"", 0);
|
|
392
|
+
|
|
393
|
+
wchar_t wMime[256];
|
|
394
|
+
MultiByteToWideChar(CP_UTF8, 0, mime, -1, wMime, 256);
|
|
395
|
+
|
|
396
|
+
wchar_t headers[1024];
|
|
397
|
+
if (is_brotli) {
|
|
398
|
+
_snwprintf(headers, 1024,
|
|
399
|
+
L"Content-Type: %s\r\n"
|
|
400
|
+
L"Content-Encoding: br\r\n"
|
|
401
|
+
L"Cross-Origin-Opener-Policy: same-origin\r\n"
|
|
402
|
+
L"Cross-Origin-Embedder-Policy: require-corp\r\n"
|
|
403
|
+
L"Cross-Origin-Resource-Policy: same-origin\r\n"
|
|
404
|
+
L"Cache-Control: no-cache",
|
|
405
|
+
wMime);
|
|
406
|
+
} else {
|
|
407
|
+
_snwprintf(headers, 1024,
|
|
408
|
+
L"Content-Type: %s\r\n"
|
|
409
|
+
L"Cross-Origin-Opener-Policy: same-origin\r\n"
|
|
410
|
+
L"Cross-Origin-Embedder-Policy: require-corp\r\n"
|
|
411
|
+
L"Cross-Origin-Resource-Policy: same-origin\r\n"
|
|
412
|
+
L"Cache-Control: no-cache",
|
|
413
|
+
wMime);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
ICoreWebView2WebResourceResponse* response = NULL;
|
|
417
|
+
const wchar_t* reason = (statusCode == 200) ? L"OK" : L"Not Found";
|
|
418
|
+
HRESULT hr = ICoreWebView2Environment_CreateWebResourceResponse(
|
|
419
|
+
zapp_wv2_env, stream, statusCode, reason, headers, &response
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
if (SUCCEEDED(hr) && response) {
|
|
423
|
+
ICoreWebView2WebResourceRequestedEventArgs_put_Response(args, response);
|
|
424
|
+
ICoreWebView2WebResourceResponse_Release(response);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
IStream_Release(stream);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// COM handler: WebResourceRequested
|
|
431
|
+
typedef struct {
|
|
432
|
+
ICoreWebView2WebResourceRequestedEventHandlerVtbl* lpVtbl;
|
|
433
|
+
ULONG refCount;
|
|
434
|
+
} ZappResourceHandler;
|
|
435
|
+
|
|
436
|
+
static HRESULT STDMETHODCALLTYPE zrh_QueryInterface(ICoreWebView2WebResourceRequestedEventHandler* This, REFIID riid, void** ppv) {
|
|
437
|
+
(void)riid; *ppv = This; This->lpVtbl->AddRef(This); return S_OK;
|
|
438
|
+
}
|
|
439
|
+
static ULONG STDMETHODCALLTYPE zrh_AddRef(ICoreWebView2WebResourceRequestedEventHandler* This) {
|
|
440
|
+
return ++((ZappResourceHandler*)This)->refCount;
|
|
441
|
+
}
|
|
442
|
+
static ULONG STDMETHODCALLTYPE zrh_Release(ICoreWebView2WebResourceRequestedEventHandler* This) {
|
|
443
|
+
ZappResourceHandler* self = (ZappResourceHandler*)This;
|
|
444
|
+
if (--self->refCount == 0) { free(self); return 0; }
|
|
445
|
+
return self->refCount;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
static HRESULT STDMETHODCALLTYPE zrh_Invoke(
|
|
449
|
+
ICoreWebView2WebResourceRequestedEventHandler* This,
|
|
450
|
+
ICoreWebView2* sender,
|
|
451
|
+
ICoreWebView2WebResourceRequestedEventArgs* args
|
|
452
|
+
) {
|
|
453
|
+
(void)This; (void)sender;
|
|
454
|
+
|
|
455
|
+
ICoreWebView2WebResourceRequest* request = NULL;
|
|
456
|
+
ICoreWebView2WebResourceRequestedEventArgs_get_Request(args, &request);
|
|
457
|
+
if (!request) return S_OK;
|
|
458
|
+
|
|
459
|
+
LPWSTR uriW = NULL;
|
|
460
|
+
ICoreWebView2WebResourceRequest_get_Uri(request, &uriW);
|
|
461
|
+
ICoreWebView2WebResourceRequest_Release(request);
|
|
462
|
+
if (!uriW) return S_OK;
|
|
463
|
+
|
|
464
|
+
char* uri = zapp_wide_to_utf8(uriW);
|
|
465
|
+
CoTaskMemFree(uriW);
|
|
466
|
+
if (!uri) return S_OK;
|
|
467
|
+
|
|
468
|
+
if (strncmp(uri, ZAPP_LOCALHOST_PREFIX, ZAPP_LOCALHOST_PREFIX_LEN) != 0 &&
|
|
469
|
+
strncmp(uri, "zapp://", 7) != 0) {
|
|
470
|
+
free(uri);
|
|
471
|
+
return S_OK;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
char* rel = zapp_url_to_rel_path(uri);
|
|
475
|
+
free(uri);
|
|
476
|
+
|
|
477
|
+
char lookupPath[512];
|
|
478
|
+
snprintf(lookupPath, sizeof(lookupPath), "/%s", rel);
|
|
479
|
+
|
|
480
|
+
const char* mime = zapp_mime_for_path(rel);
|
|
481
|
+
|
|
482
|
+
// Embedded assets
|
|
483
|
+
if (zapp_build_use_embedded_assets() && zapp_embedded_assets_count > 0) {
|
|
484
|
+
for (int i = 0; i < zapp_embedded_assets_count; i++) {
|
|
485
|
+
if (strcmp(zapp_embedded_assets[i].path, lookupPath) == 0) {
|
|
486
|
+
zapp_send_resource_response(args,
|
|
487
|
+
zapp_embedded_assets[i].data,
|
|
488
|
+
zapp_embedded_assets[i].len,
|
|
489
|
+
mime, 200,
|
|
490
|
+
zapp_embedded_assets[i].is_brotli);
|
|
491
|
+
free(rel);
|
|
492
|
+
return S_OK;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
// SPA fallback for embedded mode
|
|
496
|
+
int isAsset = (strncmp(rel, "assets/", 7) == 0) || (strncmp(rel, "zapp-workers/", 13) == 0);
|
|
497
|
+
if (!isAsset) {
|
|
498
|
+
for (int i = 0; i < zapp_embedded_assets_count; i++) {
|
|
499
|
+
if (strcmp(zapp_embedded_assets[i].path, "/index.html") == 0) {
|
|
500
|
+
zapp_send_resource_response(args,
|
|
501
|
+
zapp_embedded_assets[i].data,
|
|
502
|
+
zapp_embedded_assets[i].len,
|
|
503
|
+
"text/html; charset=utf-8", 200,
|
|
504
|
+
zapp_embedded_assets[i].is_brotli);
|
|
505
|
+
free(rel);
|
|
506
|
+
return S_OK;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// File-based serving
|
|
513
|
+
const char* assetRoot = zapp_build_asset_root();
|
|
514
|
+
if (assetRoot && assetRoot[0] != '\0') {
|
|
515
|
+
char fullPath[1024];
|
|
516
|
+
snprintf(fullPath, sizeof(fullPath), "%s/%s", assetRoot, rel);
|
|
517
|
+
for (char* p = fullPath; *p; p++) { if (*p == '/') *p = '\\'; }
|
|
518
|
+
|
|
519
|
+
FILE* f = fopen(fullPath, "rb");
|
|
520
|
+
if (!f) {
|
|
521
|
+
int isAsset = (strncmp(rel, "assets/", 7) == 0) || (strncmp(rel, "zapp-workers/", 13) == 0);
|
|
522
|
+
if (!isAsset) {
|
|
523
|
+
snprintf(fullPath, sizeof(fullPath), "%s\\index.html", assetRoot);
|
|
524
|
+
for (char* p = fullPath; *p; p++) { if (*p == '/') *p = '\\'; }
|
|
525
|
+
f = fopen(fullPath, "rb");
|
|
526
|
+
if (f) mime = "text/html; charset=utf-8";
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (f) {
|
|
531
|
+
fseek(f, 0, SEEK_END);
|
|
532
|
+
long len = ftell(f);
|
|
533
|
+
fseek(f, 0, SEEK_SET);
|
|
534
|
+
if (len > 0) {
|
|
535
|
+
unsigned char* buf = (unsigned char*)malloc((size_t)len);
|
|
536
|
+
fread(buf, 1, (size_t)len, f);
|
|
537
|
+
fclose(f);
|
|
538
|
+
zapp_send_resource_response(args, buf, (int)len, mime, 200, 0);
|
|
539
|
+
free(buf);
|
|
540
|
+
free(rel);
|
|
541
|
+
return S_OK;
|
|
542
|
+
}
|
|
543
|
+
fclose(f);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
zapp_send_resource_response(args,
|
|
548
|
+
(const unsigned char*)"Not Found", 9,
|
|
549
|
+
"text/plain; charset=utf-8", 404, 0);
|
|
550
|
+
free(rel);
|
|
551
|
+
return S_OK;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
static ICoreWebView2WebResourceRequestedEventHandlerVtbl zrh_vtbl = {
|
|
555
|
+
zrh_QueryInterface, zrh_AddRef, zrh_Release, zrh_Invoke
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
// ---------------------------------------------------------------------------
|
|
559
|
+
// WebView2 configuration (called once controller is ready)
|
|
560
|
+
// ---------------------------------------------------------------------------
|
|
561
|
+
|
|
562
|
+
static void zapp_configure_webview(HWND hwnd, ICoreWebView2Controller* controller, ICoreWebView2* webview) {
|
|
563
|
+
ZappWindowEntry* entry = zapp_window_entry_for_hwnd(hwnd);
|
|
564
|
+
if (!entry) return;
|
|
565
|
+
entry->controller = controller;
|
|
566
|
+
entry->webview = webview;
|
|
567
|
+
|
|
568
|
+
ICoreWebView2Controller_put_IsVisible(controller, TRUE);
|
|
569
|
+
|
|
570
|
+
// Apply webContentInspectable (dev tools) setting
|
|
571
|
+
ICoreWebView2Settings* settings = NULL;
|
|
572
|
+
if (SUCCEEDED(ICoreWebView2_get_Settings(webview, &settings)) && settings) {
|
|
573
|
+
ICoreWebView2Settings_put_AreDevToolsEnabled(settings, entry->inspectable);
|
|
574
|
+
ICoreWebView2Settings_Release(settings);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
RECT bounds;
|
|
578
|
+
GetClientRect(hwnd, &bounds);
|
|
579
|
+
ICoreWebView2Controller_put_Bounds(controller, bounds);
|
|
580
|
+
ICoreWebView2Controller_NotifyParentWindowPositionChanged(controller);
|
|
581
|
+
|
|
582
|
+
// Register message handler
|
|
583
|
+
EventRegistrationToken token;
|
|
584
|
+
ZappMessageHandler* msgHandler = zapp_create_message_handler();
|
|
585
|
+
ICoreWebView2_add_WebMessageReceived(webview, (ICoreWebView2WebMessageReceivedEventHandler*)msgHandler, &token);
|
|
586
|
+
|
|
587
|
+
// Set transparent background so there's no white flash before content loads
|
|
588
|
+
{
|
|
589
|
+
ICoreWebView2Controller2* ctrl2 = NULL;
|
|
590
|
+
HRESULT bghr = ICoreWebView2Controller_QueryInterface(
|
|
591
|
+
controller, &IID_ICoreWebView2Controller2, (void**)&ctrl2);
|
|
592
|
+
if (SUCCEEDED(bghr) && ctrl2) {
|
|
593
|
+
COREWEBVIEW2_COLOR transparent = {0, 0, 0, 0};
|
|
594
|
+
ICoreWebView2Controller2_put_DefaultBackgroundColor(ctrl2, transparent);
|
|
595
|
+
ICoreWebView2Controller2_Release(ctrl2);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Register NavigationCompleted handler (IsVisible toggle workaround)
|
|
600
|
+
ZappNavHandler* navHandler = (ZappNavHandler*)calloc(1, sizeof(ZappNavHandler));
|
|
601
|
+
navHandler->lpVtbl = &znh_vtbl;
|
|
602
|
+
navHandler->refCount = 1;
|
|
603
|
+
navHandler->controller = controller;
|
|
604
|
+
navHandler->firstNavDone = FALSE;
|
|
605
|
+
EventRegistrationToken navToken;
|
|
606
|
+
ICoreWebView2_add_NavigationCompleted(webview, (ICoreWebView2NavigationCompletedEventHandler*)navHandler, &navToken);
|
|
607
|
+
|
|
608
|
+
// Inject bootstrap config script
|
|
609
|
+
const char* bootstrapName = app_get_bootstrap_name();
|
|
610
|
+
char* escapedName = zapp_escape_js_string(bootstrapName ? bootstrapName : "Zapp App");
|
|
611
|
+
BOOL shouldTerminate = app_get_bootstrap_application_should_terminate_after_last_window_closed();
|
|
612
|
+
BOOL inspectable = app_get_bootstrap_web_content_inspectable();
|
|
613
|
+
int maxWorkers = app_get_bootstrap_max_workers();
|
|
614
|
+
|
|
615
|
+
char configScript[2048];
|
|
616
|
+
snprintf(configScript, sizeof(configScript),
|
|
617
|
+
"(function(){globalThis[Symbol.for('zapp.bootstrapConfig')]="
|
|
618
|
+
"{name:'%s',applicationShouldTerminateAfterLastWindowClosed:%s,"
|
|
619
|
+
"webContentInspectable:%s,maxWorkers:%d};})();",
|
|
620
|
+
escapedName,
|
|
621
|
+
shouldTerminate ? "true" : "false",
|
|
622
|
+
inspectable ? "true" : "false",
|
|
623
|
+
maxWorkers
|
|
624
|
+
);
|
|
625
|
+
free(escapedName);
|
|
626
|
+
|
|
627
|
+
wchar_t* wConfigScript = zapp_utf8_to_wide(configScript);
|
|
628
|
+
ICoreWebView2_AddScriptToExecuteOnDocumentCreated(webview, wConfigScript, NULL);
|
|
629
|
+
free(wConfigScript);
|
|
630
|
+
|
|
631
|
+
// Inject bindings manifest
|
|
632
|
+
const char* bindingsRaw = service_get_bindings_manifest_json();
|
|
633
|
+
char* escapedBindings = zapp_escape_js_string(bindingsRaw);
|
|
634
|
+
size_t bindScriptLen = strlen(escapedBindings) + 256;
|
|
635
|
+
char* bindScript = (char*)malloc(bindScriptLen);
|
|
636
|
+
snprintf(bindScript, bindScriptLen,
|
|
637
|
+
"(function(){globalThis[Symbol.for('zapp.bindingsManifest')]='%s';})();",
|
|
638
|
+
escapedBindings
|
|
639
|
+
);
|
|
640
|
+
free(escapedBindings);
|
|
641
|
+
wchar_t* wBindScript = zapp_utf8_to_wide(bindScript);
|
|
642
|
+
ICoreWebView2_AddScriptToExecuteOnDocumentCreated(webview, wBindScript, NULL);
|
|
643
|
+
free(wBindScript);
|
|
644
|
+
free(bindScript);
|
|
645
|
+
|
|
646
|
+
// Inject owner ID
|
|
647
|
+
char ownerScript[256];
|
|
648
|
+
snprintf(ownerScript, sizeof(ownerScript),
|
|
649
|
+
"(function(){globalThis[Symbol.for('zapp.ownerId')]='%s';})();",
|
|
650
|
+
entry->ownerId
|
|
651
|
+
);
|
|
652
|
+
wchar_t* wOwnerScript = zapp_utf8_to_wide(ownerScript);
|
|
653
|
+
ICoreWebView2_AddScriptToExecuteOnDocumentCreated(webview, wOwnerScript, NULL);
|
|
654
|
+
free(wOwnerScript);
|
|
655
|
+
|
|
656
|
+
// Inject window ID (used by fireReady to identify which window is ready)
|
|
657
|
+
const char* registeredId = zapp_win_id_for_hwnd(hwnd);
|
|
658
|
+
char windowIdScript[256];
|
|
659
|
+
snprintf(windowIdScript, sizeof(windowIdScript),
|
|
660
|
+
"(function(){globalThis[Symbol.for('zapp.windowId')]='%s';})();",
|
|
661
|
+
registeredId ? registeredId : "unknown"
|
|
662
|
+
);
|
|
663
|
+
wchar_t* wWindowIdScript = zapp_utf8_to_wide(windowIdScript);
|
|
664
|
+
ICoreWebView2_AddScriptToExecuteOnDocumentCreated(webview, wWindowIdScript, NULL);
|
|
665
|
+
free(wWindowIdScript);
|
|
666
|
+
|
|
667
|
+
// Inject webview bootstrap script
|
|
668
|
+
const char* bootstrap = zapp_windows_webview_bootstrap_script();
|
|
669
|
+
if (bootstrap && bootstrap[0] != '\0') {
|
|
670
|
+
wchar_t* wBootstrap = zapp_utf8_to_wide(bootstrap);
|
|
671
|
+
ICoreWebView2_AddScriptToExecuteOnDocumentCreated(webview, wBootstrap, NULL);
|
|
672
|
+
free(wBootstrap);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Register app.localhost virtual host so WebView2 recognises the origin.
|
|
676
|
+
// For file-based builds this serves files directly; for embedded builds
|
|
677
|
+
// our WebResourceRequested handler below intercepts and overrides.
|
|
678
|
+
{
|
|
679
|
+
ICoreWebView2_3* wv3 = NULL;
|
|
680
|
+
HRESULT qihr = ICoreWebView2_QueryInterface(webview, &IID_ICoreWebView2_3, (void**)&wv3);
|
|
681
|
+
if (SUCCEEDED(qihr) && wv3) {
|
|
682
|
+
const char* assetRoot = zapp_build_asset_root();
|
|
683
|
+
const wchar_t* folderW = L".";
|
|
684
|
+
wchar_t* allocFolder = NULL;
|
|
685
|
+
if (assetRoot && assetRoot[0] != '\0') {
|
|
686
|
+
allocFolder = zapp_utf8_to_wide(assetRoot);
|
|
687
|
+
folderW = allocFolder;
|
|
688
|
+
}
|
|
689
|
+
ICoreWebView2_3_SetVirtualHostNameToFolderMapping(
|
|
690
|
+
wv3, L"app.localhost", folderW,
|
|
691
|
+
COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_ALLOW);
|
|
692
|
+
if (allocFolder) free(allocFolder);
|
|
693
|
+
ICoreWebView2_3_Release(wv3);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Intercept requests for embedded/custom asset serving
|
|
698
|
+
ICoreWebView2_AddWebResourceRequestedFilter(webview, L"https://app.localhost/*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL);
|
|
699
|
+
ZappResourceHandler* resHandler = (ZappResourceHandler*)calloc(1, sizeof(ZappResourceHandler));
|
|
700
|
+
resHandler->lpVtbl = &zrh_vtbl;
|
|
701
|
+
resHandler->refCount = 1;
|
|
702
|
+
EventRegistrationToken resToken;
|
|
703
|
+
ICoreWebView2_add_WebResourceRequested(webview, (ICoreWebView2WebResourceRequestedEventHandler*)resHandler, &resToken);
|
|
704
|
+
|
|
705
|
+
// Navigate to initial URL
|
|
706
|
+
const char* initialUrl = zapp_build_initial_url();
|
|
707
|
+
const char* url = (initialUrl && initialUrl[0] != '\0') ? initialUrl : "https://app.localhost/index.html";
|
|
708
|
+
wchar_t* wUrl = zapp_utf8_to_wide(url);
|
|
709
|
+
ICoreWebView2_Navigate(webview, wUrl);
|
|
710
|
+
free(wUrl);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// ---------------------------------------------------------------------------
|
|
714
|
+
// COM callback: ControllerCompleted
|
|
715
|
+
// ---------------------------------------------------------------------------
|
|
716
|
+
|
|
717
|
+
typedef struct {
|
|
718
|
+
ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerVtbl* lpVtbl;
|
|
719
|
+
ULONG refCount;
|
|
720
|
+
HWND hwnd;
|
|
721
|
+
} ZappControllerHandler;
|
|
722
|
+
|
|
723
|
+
static HRESULT STDMETHODCALLTYPE zch_QueryInterface(ICoreWebView2CreateCoreWebView2ControllerCompletedHandler* This, REFIID riid, void** ppv) {
|
|
724
|
+
(void)riid; *ppv = This; This->lpVtbl->AddRef(This); return S_OK;
|
|
725
|
+
}
|
|
726
|
+
static ULONG STDMETHODCALLTYPE zch_AddRef(ICoreWebView2CreateCoreWebView2ControllerCompletedHandler* This) {
|
|
727
|
+
return ++((ZappControllerHandler*)This)->refCount;
|
|
728
|
+
}
|
|
729
|
+
static ULONG STDMETHODCALLTYPE zch_Release(ICoreWebView2CreateCoreWebView2ControllerCompletedHandler* This) {
|
|
730
|
+
ZappControllerHandler* self = (ZappControllerHandler*)This;
|
|
731
|
+
if (--self->refCount == 0) { free(self); return 0; }
|
|
732
|
+
return self->refCount;
|
|
733
|
+
}
|
|
734
|
+
static HRESULT STDMETHODCALLTYPE zch_Invoke(
|
|
735
|
+
ICoreWebView2CreateCoreWebView2ControllerCompletedHandler* This,
|
|
736
|
+
HRESULT errorCode,
|
|
737
|
+
ICoreWebView2Controller* controller
|
|
738
|
+
) {
|
|
739
|
+
ZappControllerHandler* self = (ZappControllerHandler*)This;
|
|
740
|
+
if (FAILED(errorCode) || !controller) {
|
|
741
|
+
fprintf(stderr, "[zapp] WebView2 controller creation failed: 0x%08lX\n", errorCode);
|
|
742
|
+
return S_OK;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// The controller is an [in] parameter - caller releases after callback.
|
|
746
|
+
// We must AddRef to keep it alive beyond this scope.
|
|
747
|
+
ICoreWebView2Controller_AddRef(controller);
|
|
748
|
+
|
|
749
|
+
ICoreWebView2* webview = NULL;
|
|
750
|
+
ICoreWebView2Controller_get_CoreWebView2(controller, &webview);
|
|
751
|
+
if (!webview) {
|
|
752
|
+
fprintf(stderr, "[zapp] Failed to get CoreWebView2 from controller\n");
|
|
753
|
+
ICoreWebView2Controller_Release(controller);
|
|
754
|
+
return S_OK;
|
|
755
|
+
}
|
|
756
|
+
// get_CoreWebView2 returns an AddRef'd pointer per COM [out] convention
|
|
757
|
+
|
|
758
|
+
zapp_configure_webview(self->hwnd, controller, webview);
|
|
759
|
+
return S_OK;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
static ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerVtbl zch_vtbl = {
|
|
763
|
+
zch_QueryInterface, zch_AddRef, zch_Release, zch_Invoke
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
// ---------------------------------------------------------------------------
|
|
767
|
+
// COM callback: EnvironmentCompleted
|
|
768
|
+
// ---------------------------------------------------------------------------
|
|
769
|
+
|
|
770
|
+
typedef struct {
|
|
771
|
+
ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerVtbl* lpVtbl;
|
|
772
|
+
ULONG refCount;
|
|
773
|
+
HWND hwnd;
|
|
774
|
+
} ZappEnvironmentHandler;
|
|
775
|
+
|
|
776
|
+
static HRESULT STDMETHODCALLTYPE zeh_QueryInterface(ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* This, REFIID riid, void** ppv) {
|
|
777
|
+
(void)riid; *ppv = This; This->lpVtbl->AddRef(This); return S_OK;
|
|
778
|
+
}
|
|
779
|
+
static ULONG STDMETHODCALLTYPE zeh_AddRef(ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* This) {
|
|
780
|
+
return ++((ZappEnvironmentHandler*)This)->refCount;
|
|
781
|
+
}
|
|
782
|
+
static ULONG STDMETHODCALLTYPE zeh_Release(ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* This) {
|
|
783
|
+
ZappEnvironmentHandler* self = (ZappEnvironmentHandler*)This;
|
|
784
|
+
if (--self->refCount == 0) { free(self); return 0; }
|
|
785
|
+
return self->refCount;
|
|
786
|
+
}
|
|
787
|
+
static HRESULT STDMETHODCALLTYPE zeh_Invoke(
|
|
788
|
+
ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler* This,
|
|
789
|
+
HRESULT errorCode,
|
|
790
|
+
ICoreWebView2Environment* env
|
|
791
|
+
) {
|
|
792
|
+
ZappEnvironmentHandler* self = (ZappEnvironmentHandler*)This;
|
|
793
|
+
if (FAILED(errorCode) || !env) {
|
|
794
|
+
fprintf(stderr, "[zapp] WebView2 environment creation failed: 0x%08lX\n", errorCode);
|
|
795
|
+
return S_OK;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (!zapp_wv2_env) {
|
|
799
|
+
zapp_wv2_env = env;
|
|
800
|
+
ICoreWebView2Environment_AddRef(env);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
ZappControllerHandler* ch = (ZappControllerHandler*)calloc(1, sizeof(ZappControllerHandler));
|
|
804
|
+
ch->lpVtbl = &zch_vtbl;
|
|
805
|
+
ch->refCount = 1;
|
|
806
|
+
ch->hwnd = self->hwnd;
|
|
807
|
+
|
|
808
|
+
ICoreWebView2Environment_CreateCoreWebView2Controller(
|
|
809
|
+
env, self->hwnd,
|
|
810
|
+
(ICoreWebView2CreateCoreWebView2ControllerCompletedHandler*)ch
|
|
811
|
+
);
|
|
812
|
+
return S_OK;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
static ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerVtbl zeh_vtbl = {
|
|
816
|
+
zeh_QueryInterface, zeh_AddRef, zeh_Release, zeh_Invoke
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
// ---------------------------------------------------------------------------
|
|
820
|
+
// Public API
|
|
821
|
+
// ---------------------------------------------------------------------------
|
|
822
|
+
|
|
823
|
+
static wchar_t* zapp_webview2_user_data_path(void) {
|
|
824
|
+
const char* appName = app_get_bootstrap_name();
|
|
825
|
+
if (!appName || appName[0] == '\0') appName = "ZappApp";
|
|
826
|
+
|
|
827
|
+
char path[MAX_PATH];
|
|
828
|
+
if (FAILED(SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, path))) {
|
|
829
|
+
return NULL;
|
|
830
|
+
}
|
|
831
|
+
strncat(path, "\\", sizeof(path) - strlen(path) - 1);
|
|
832
|
+
strncat(path, appName, sizeof(path) - strlen(path) - 1);
|
|
833
|
+
strncat(path, "\\WebView2", sizeof(path) - strlen(path) - 1);
|
|
834
|
+
|
|
835
|
+
SHCreateDirectoryExA(NULL, path, NULL);
|
|
836
|
+
return zapp_utf8_to_wide(path);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
void zapp_windows_webview_create(HWND hwnd, BOOL inspectable) {
|
|
840
|
+
ZappWindowEntry* entry = zapp_window_entry_alloc(hwnd);
|
|
841
|
+
if (!entry) {
|
|
842
|
+
fprintf(stderr, "[zapp] Max windows reached\n");
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
entry->inspectable = inspectable;
|
|
846
|
+
|
|
847
|
+
ZappEnvironmentHandler* eh = (ZappEnvironmentHandler*)calloc(1, sizeof(ZappEnvironmentHandler));
|
|
848
|
+
eh->lpVtbl = &zeh_vtbl;
|
|
849
|
+
eh->refCount = 1;
|
|
850
|
+
eh->hwnd = hwnd;
|
|
851
|
+
|
|
852
|
+
if (!zapp_webview2_loader_init()) {
|
|
853
|
+
fprintf(stderr, "[zapp] Failed to initialize WebView2 loader\n");
|
|
854
|
+
free(eh);
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
wchar_t* userDataDir = zapp_webview2_user_data_path();
|
|
859
|
+
HRESULT hr = zapp_CreateEnvInternal(
|
|
860
|
+
1, 0, userDataDir, NULL,
|
|
861
|
+
(ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*)eh
|
|
862
|
+
);
|
|
863
|
+
free(userDataDir);
|
|
864
|
+
if (FAILED(hr)) {
|
|
865
|
+
fprintf(stderr, "[zapp] WebView2 environment creation failed: 0x%08lX\n", hr);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
void zapp_windows_webview_resize(HWND hwnd) {
|
|
870
|
+
ZappWindowEntry* entry = zapp_window_entry_for_hwnd(hwnd);
|
|
871
|
+
if (entry && entry->controller) {
|
|
872
|
+
ICoreWebView2Controller* ctrl = (ICoreWebView2Controller*)entry->controller;
|
|
873
|
+
RECT bounds;
|
|
874
|
+
GetClientRect(hwnd, &bounds);
|
|
875
|
+
ICoreWebView2Controller_put_Bounds(ctrl, bounds);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
void zapp_windows_webview_notify_position(HWND hwnd) {
|
|
880
|
+
ZappWindowEntry* entry = zapp_window_entry_for_hwnd(hwnd);
|
|
881
|
+
if (entry && entry->controller) {
|
|
882
|
+
ICoreWebView2Controller_NotifyParentWindowPositionChanged(
|
|
883
|
+
(ICoreWebView2Controller*)entry->controller);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
extern void zapp_windows_reset_owner_workers(const char* owner_id);
|
|
888
|
+
extern void zapp_win_unregister_by_hwnd(HWND hwnd);
|
|
889
|
+
|
|
890
|
+
void zapp_windows_webview_detach(HWND hwnd) {
|
|
891
|
+
ZappWindowEntry* entry = zapp_window_entry_for_hwnd(hwnd);
|
|
892
|
+
if (entry) {
|
|
893
|
+
if (entry->ownerId[0]) {
|
|
894
|
+
zapp_windows_reset_owner_workers(entry->ownerId);
|
|
895
|
+
}
|
|
896
|
+
zapp_win_unregister_by_hwnd(hwnd);
|
|
897
|
+
if (entry->controller) {
|
|
898
|
+
ICoreWebView2Controller* ctrl = (ICoreWebView2Controller*)entry->controller;
|
|
899
|
+
ICoreWebView2Controller_Close(ctrl);
|
|
900
|
+
ICoreWebView2Controller_Release(ctrl);
|
|
901
|
+
}
|
|
902
|
+
if (entry->webview) {
|
|
903
|
+
ICoreWebView2_Release((ICoreWebView2*)entry->webview);
|
|
904
|
+
}
|
|
905
|
+
entry->in_use = FALSE;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
HWND zapp_get_first_hwnd(void) {
|
|
910
|
+
for (int i = 0; i < ZAPP_MAX_WINDOWS; i++) {
|
|
911
|
+
if (zapp_windows[i].in_use && zapp_windows[i].hwnd)
|
|
912
|
+
return zapp_windows[i].hwnd;
|
|
913
|
+
}
|
|
914
|
+
return NULL;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
void zapp_windows_webview_navigate(HWND hwnd, const char* url_utf8) {
|
|
918
|
+
ZappWindowEntry* entry = zapp_window_entry_for_hwnd(hwnd);
|
|
919
|
+
if (entry && entry->webview && url_utf8) {
|
|
920
|
+
ICoreWebView2* webview = (ICoreWebView2*)entry->webview;
|
|
921
|
+
const char* initialUrl = zapp_build_initial_url();
|
|
922
|
+
char resolved[2048];
|
|
923
|
+
if (strncmp(url_utf8, "http://", 7) == 0 || strncmp(url_utf8, "https://", 8) == 0 || strncmp(url_utf8, "zapp://", 7) == 0) {
|
|
924
|
+
strncpy(resolved, url_utf8, sizeof(resolved) - 1);
|
|
925
|
+
} else if (initialUrl && strncmp(initialUrl, "http", 4) == 0) {
|
|
926
|
+
snprintf(resolved, sizeof(resolved), "%s%s%s",
|
|
927
|
+
initialUrl,
|
|
928
|
+
url_utf8[0] == '/' ? "" : "/",
|
|
929
|
+
url_utf8);
|
|
930
|
+
} else {
|
|
931
|
+
snprintf(resolved, sizeof(resolved), "https://app.localhost%s%s",
|
|
932
|
+
url_utf8[0] == '/' ? "" : "/",
|
|
933
|
+
url_utf8);
|
|
934
|
+
}
|
|
935
|
+
resolved[sizeof(resolved) - 1] = '\0';
|
|
936
|
+
wchar_t* wUrl = zapp_utf8_to_wide(resolved);
|
|
937
|
+
ICoreWebView2_Navigate(webview, wUrl);
|
|
938
|
+
free(wUrl);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
void zapp_windows_webview_eval(HWND hwnd, const wchar_t* js) {
|
|
943
|
+
ZappWindowEntry* entry = zapp_window_entry_for_hwnd(hwnd);
|
|
944
|
+
if (entry && entry->webview) {
|
|
945
|
+
ICoreWebView2_ExecuteScript((ICoreWebView2*)entry->webview, js, NULL);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
void zapp_windows_webview_eval_all_ui(wchar_t* wJs) {
|
|
950
|
+
if (!wJs) return;
|
|
951
|
+
for (int i = 0; i < ZAPP_MAX_WINDOWS; i++) {
|
|
952
|
+
if (zapp_windows[i].in_use && zapp_windows[i].webview) {
|
|
953
|
+
ICoreWebView2_ExecuteScript(
|
|
954
|
+
(ICoreWebView2*)zapp_windows[i].webview, wJs, NULL);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
free(wJs);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// Thread-safe: posts JS to the UI message loop so ExecuteScript
|
|
961
|
+
// runs on the thread that owns the WebView2 COM objects.
|
|
962
|
+
void zapp_windows_webview_eval_all(const char* js_utf8) {
|
|
963
|
+
wchar_t* wJs = zapp_utf8_to_wide(js_utf8);
|
|
964
|
+
if (!wJs) return;
|
|
965
|
+
// Find any in-use window to post the message to
|
|
966
|
+
for (int i = 0; i < ZAPP_MAX_WINDOWS; i++) {
|
|
967
|
+
if (zapp_windows[i].in_use && zapp_windows[i].hwnd) {
|
|
968
|
+
PostMessageW(zapp_windows[i].hwnd, WM_ZAPP_EVAL_JS, 0, (LPARAM)wJs);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
free(wJs);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// --- Invoke result dispatch (moved from app.zc) ---
|
|
976
|
+
void zapp_dispatch_invoke_result(char* request_id, char* payload_json) {
|
|
977
|
+
(void)request_id;
|
|
978
|
+
char* escaped = zapp_escape_js_string((const char*)payload_json);
|
|
979
|
+
size_t jsLen = strlen(escaped) + 256;
|
|
980
|
+
char* js = (char*)malloc(jsLen);
|
|
981
|
+
snprintf(js, jsLen,
|
|
982
|
+
"(function(){var b=globalThis[Symbol.for('zapp.bridge')];"
|
|
983
|
+
"if(b&&typeof b.dispatchInvokeResult==='function'){b.dispatchInvokeResult('%s');}})();",
|
|
984
|
+
escaped
|
|
985
|
+
);
|
|
986
|
+
zapp_windows_webview_eval_all(js);
|
|
987
|
+
free(js);
|
|
988
|
+
free(escaped);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// --- App actions (moved from app.zc) ---
|
|
992
|
+
extern const wchar_t* ZAPP_WINDOW_CLASS;
|
|
993
|
+
|
|
994
|
+
void app_handle_app_action(App* app, char* action) {
|
|
995
|
+
(void)app;
|
|
996
|
+
if (action == NULL) return;
|
|
997
|
+
if (strcmp((const char*)action, "quit") == 0) {
|
|
998
|
+
PostQuitMessage(0);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
if (strcmp((const char*)action, "hide") == 0) {
|
|
1002
|
+
HWND hwnd = NULL;
|
|
1003
|
+
while ((hwnd = FindWindowExW(NULL, hwnd, ZAPP_WINDOW_CLASS, NULL)) != NULL) {
|
|
1004
|
+
if (IsWindowVisible(hwnd)) ShowWindow(hwnd, SW_MINIMIZE);
|
|
1005
|
+
}
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
if (strcmp((const char*)action, "show") == 0) {
|
|
1009
|
+
HWND hwnd = NULL;
|
|
1010
|
+
HWND first = NULL;
|
|
1011
|
+
while ((hwnd = FindWindowExW(NULL, hwnd, ZAPP_WINDOW_CLASS, NULL)) != NULL) {
|
|
1012
|
+
if (IsIconic(hwnd)) ShowWindow(hwnd, SW_RESTORE);
|
|
1013
|
+
else ShowWindow(hwnd, SW_SHOW);
|
|
1014
|
+
if (!first) first = hwnd;
|
|
1015
|
+
}
|
|
1016
|
+
if (first) SetForegroundWindow(first);
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// --- Open external URL (moved from app.zc) ---
|
|
1022
|
+
void app_open_external(App* app, char* payload) {
|
|
1023
|
+
(void)app;
|
|
1024
|
+
if (payload == NULL) return;
|
|
1025
|
+
extern char* zapp_json_get_string(const char* json, const char* key);
|
|
1026
|
+
char* url = zapp_json_get_string((const char*)payload, "url");
|
|
1027
|
+
if (url && url[0] != '\0') {
|
|
1028
|
+
int len = MultiByteToWideChar(CP_UTF8, 0, url, -1, NULL, 0);
|
|
1029
|
+
wchar_t* wUrl = (wchar_t*)malloc(len * sizeof(wchar_t));
|
|
1030
|
+
MultiByteToWideChar(CP_UTF8, 0, url, -1, wUrl, len);
|
|
1031
|
+
ShellExecuteW(NULL, L"open", wUrl, NULL, NULL, SW_SHOWNORMAL);
|
|
1032
|
+
free(wUrl);
|
|
1033
|
+
}
|
|
1034
|
+
if (url) free(url);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// --- Sync bridge (moved from app.zc) ---
|
|
1038
|
+
#define ZAPP_MAX_SYNC_WAITS 256
|
|
1039
|
+
#define ZAPP_SYNC_TIMER_MAP_SIZE 512
|
|
1040
|
+
|
|
1041
|
+
typedef struct {
|
|
1042
|
+
int in_use;
|
|
1043
|
+
char requestId[128];
|
|
1044
|
+
char key[128];
|
|
1045
|
+
char targetWorkerId[256];
|
|
1046
|
+
UINT_PTR timerId;
|
|
1047
|
+
} ZappSyncWait;
|
|
1048
|
+
|
|
1049
|
+
static ZappSyncWait zapp_sync_waits[ZAPP_MAX_SYNC_WAITS] = {0};
|
|
1050
|
+
static int zapp_sync_timer_to_slot[ZAPP_SYNC_TIMER_MAP_SIZE] = {0};
|
|
1051
|
+
static int zapp_sync_initialized = 0;
|
|
1052
|
+
|
|
1053
|
+
static void zapp_sync_ensure_state(void) {
|
|
1054
|
+
if (!zapp_sync_initialized) {
|
|
1055
|
+
memset(zapp_sync_waits, 0, sizeof(zapp_sync_waits));
|
|
1056
|
+
memset(zapp_sync_timer_to_slot, -1, sizeof(zapp_sync_timer_to_slot));
|
|
1057
|
+
zapp_sync_initialized = 1;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
extern void zapp_windows_worker_dispatch_sync_result(const char* worker_id, const char* payload_json);
|
|
1062
|
+
|
|
1063
|
+
static void zapp_sync_dispatch_result(ZappSyncWait* w, const char* status, int ok) {
|
|
1064
|
+
if (!w || !w->in_use) return;
|
|
1065
|
+
char payload[512];
|
|
1066
|
+
snprintf(payload, sizeof(payload),
|
|
1067
|
+
"{\"id\":\"%s\",\"ok\":%s,\"status\":\"%s\"}",
|
|
1068
|
+
w->requestId, ok ? "true" : "false", status);
|
|
1069
|
+
if (w->timerId) {
|
|
1070
|
+
KillTimer(NULL, w->timerId);
|
|
1071
|
+
w->timerId = 0;
|
|
1072
|
+
}
|
|
1073
|
+
if (w->targetWorkerId[0] != '\0') {
|
|
1074
|
+
zapp_windows_worker_dispatch_sync_result(w->targetWorkerId, payload);
|
|
1075
|
+
} else {
|
|
1076
|
+
char* escaped = zapp_escape_js_string(payload);
|
|
1077
|
+
size_t jsLen = strlen(escaped) + 256;
|
|
1078
|
+
char* js = (char*)malloc(jsLen);
|
|
1079
|
+
snprintf(js, jsLen,
|
|
1080
|
+
"(function(){var b=globalThis[Symbol.for('zapp.bridge')];"
|
|
1081
|
+
"if(b&&typeof b.dispatchSyncResult==='function'){b.dispatchSyncResult('%s');}})();",
|
|
1082
|
+
escaped);
|
|
1083
|
+
zapp_windows_webview_eval_all(js);
|
|
1084
|
+
free(js);
|
|
1085
|
+
free(escaped);
|
|
1086
|
+
}
|
|
1087
|
+
w->in_use = 0;
|
|
1088
|
+
w->requestId[0] = '\0';
|
|
1089
|
+
w->key[0] = '\0';
|
|
1090
|
+
w->targetWorkerId[0] = '\0';
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
static void CALLBACK zapp_sync_timeout_proc(HWND hwnd, UINT msg, UINT_PTR timerId, DWORD tick) {
|
|
1094
|
+
(void)hwnd; (void)msg; (void)tick;
|
|
1095
|
+
KillTimer(NULL, timerId);
|
|
1096
|
+
int slot = zapp_sync_timer_to_slot[timerId % ZAPP_SYNC_TIMER_MAP_SIZE];
|
|
1097
|
+
if (slot >= 0 && slot < ZAPP_MAX_SYNC_WAITS && zapp_sync_waits[slot].in_use
|
|
1098
|
+
&& zapp_sync_waits[slot].timerId == timerId) {
|
|
1099
|
+
zapp_sync_waits[slot].timerId = 0;
|
|
1100
|
+
zapp_sync_dispatch_result(&zapp_sync_waits[slot], "timed-out", 1);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
extern char* zapp_json_get_string(const char* json, const char* key);
|
|
1105
|
+
extern int zapp_json_get_int(const char* json, const char* key, int default_val);
|
|
1106
|
+
|
|
1107
|
+
void platform_sync_handle_bridge(char* action, char* payload_json) {
|
|
1108
|
+
if (action == NULL || payload_json == NULL) return;
|
|
1109
|
+
zapp_sync_ensure_state();
|
|
1110
|
+
|
|
1111
|
+
if (strcmp((const char*)action, "wait") == 0) {
|
|
1112
|
+
char* requestId = zapp_json_get_string((const char*)payload_json, "id");
|
|
1113
|
+
char* key = zapp_json_get_string((const char*)payload_json, "key");
|
|
1114
|
+
char* targetWorkerId = zapp_json_get_string((const char*)payload_json, "targetWorkerId");
|
|
1115
|
+
int timeoutMs = zapp_json_get_int((const char*)payload_json, "timeoutMs", 30000);
|
|
1116
|
+
if (!requestId || !key || requestId[0] == '\0' || key[0] == '\0') {
|
|
1117
|
+
free(requestId); free(key); free(targetWorkerId);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if (timeoutMs < 1) timeoutMs = 1;
|
|
1121
|
+
if (timeoutMs > 300000) timeoutMs = 300000;
|
|
1122
|
+
int slot = -1;
|
|
1123
|
+
for (int i = 0; i < ZAPP_MAX_SYNC_WAITS; i++) {
|
|
1124
|
+
if (!zapp_sync_waits[i].in_use) { slot = i; break; }
|
|
1125
|
+
}
|
|
1126
|
+
if (slot < 0) {
|
|
1127
|
+
free(requestId); free(key); free(targetWorkerId);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
ZappSyncWait* w = &zapp_sync_waits[slot];
|
|
1131
|
+
w->in_use = 1;
|
|
1132
|
+
strncpy(w->requestId, requestId, sizeof(w->requestId) - 1);
|
|
1133
|
+
strncpy(w->key, key, sizeof(w->key) - 1);
|
|
1134
|
+
if (targetWorkerId) strncpy(w->targetWorkerId, targetWorkerId, sizeof(w->targetWorkerId) - 1);
|
|
1135
|
+
else w->targetWorkerId[0] = '\0';
|
|
1136
|
+
w->timerId = SetTimer(NULL, 0, (UINT)timeoutMs, zapp_sync_timeout_proc);
|
|
1137
|
+
if (w->timerId) {
|
|
1138
|
+
zapp_sync_timer_to_slot[w->timerId % ZAPP_SYNC_TIMER_MAP_SIZE] = slot;
|
|
1139
|
+
}
|
|
1140
|
+
free(requestId); free(key); free(targetWorkerId);
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
if (strcmp((const char*)action, "notify") == 0) {
|
|
1145
|
+
char* key = zapp_json_get_string((const char*)payload_json, "key");
|
|
1146
|
+
int count = zapp_json_get_int((const char*)payload_json, "count", 1);
|
|
1147
|
+
if (!key || key[0] == '\0') { free(key); return; }
|
|
1148
|
+
if (count < 1) count = 1;
|
|
1149
|
+
if (count > 65535) count = 65535;
|
|
1150
|
+
int delivered = 0;
|
|
1151
|
+
for (int i = 0; i < ZAPP_MAX_SYNC_WAITS && delivered < count; i++) {
|
|
1152
|
+
if (zapp_sync_waits[i].in_use && strcmp(zapp_sync_waits[i].key, key) == 0) {
|
|
1153
|
+
zapp_sync_dispatch_result(&zapp_sync_waits[i], "notified", 1);
|
|
1154
|
+
delivered++;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
free(key);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
if (strcmp((const char*)action, "cancel") == 0) {
|
|
1162
|
+
char* requestId = zapp_json_get_string((const char*)payload_json, "id");
|
|
1163
|
+
if (!requestId || requestId[0] == '\0') { free(requestId); return; }
|
|
1164
|
+
for (int i = 0; i < ZAPP_MAX_SYNC_WAITS; i++) {
|
|
1165
|
+
if (zapp_sync_waits[i].in_use && strcmp(zapp_sync_waits[i].requestId, requestId) == 0) {
|
|
1166
|
+
zapp_sync_dispatch_result(&zapp_sync_waits[i], "cancelled", 1);
|
|
1167
|
+
break;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
free(requestId);
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
#endif
|
|
1175
|
+
}
|