@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,1271 @@
|
|
|
1
|
+
import "../window.zc";
|
|
2
|
+
|
|
3
|
+
//> windows: link: -lcomdlg32
|
|
4
|
+
|
|
5
|
+
raw {
|
|
6
|
+
#ifdef _WIN32
|
|
7
|
+
#include <windows.h>
|
|
8
|
+
#include <stdio.h>
|
|
9
|
+
|
|
10
|
+
#ifndef ZAPP_WINDOW_ENTRY_DEFINED
|
|
11
|
+
#define ZAPP_WINDOW_ENTRY_DEFINED
|
|
12
|
+
typedef struct ZappWindowEntry {
|
|
13
|
+
HWND hwnd;
|
|
14
|
+
void* controller;
|
|
15
|
+
void* webview;
|
|
16
|
+
char ownerId[64];
|
|
17
|
+
BOOL in_use;
|
|
18
|
+
BOOL inspectable;
|
|
19
|
+
BOOL bridgeReady;
|
|
20
|
+
BOOL pendingFocusEvent;
|
|
21
|
+
BOOL closeGuarded;
|
|
22
|
+
BOOL forceClose;
|
|
23
|
+
WINDOWPLACEMENT prevPlacement;
|
|
24
|
+
} ZappWindowEntry;
|
|
25
|
+
#endif
|
|
26
|
+
|
|
27
|
+
extern const wchar_t* ZAPP_WINDOW_CLASS;
|
|
28
|
+
extern void zapp_windows_webview_create(HWND hwnd, BOOL inspectable);
|
|
29
|
+
extern ZappWindowEntry* zapp_window_entry_for_hwnd(HWND hwnd);
|
|
30
|
+
|
|
31
|
+
extern HMENU zapp_default_menu;
|
|
32
|
+
|
|
33
|
+
static HWND zapp_create_hwnd(const char* title, int width, int height, int x, int y,
|
|
34
|
+
_Bool resizable, _Bool closable, _Bool minimizable, _Bool maximizable,
|
|
35
|
+
_Bool borderless, _Bool alwaysOnTop) {
|
|
36
|
+
int titleLen = MultiByteToWideChar(CP_UTF8, 0, title, -1, NULL, 0);
|
|
37
|
+
wchar_t* wTitle = (wchar_t*)malloc(titleLen * sizeof(wchar_t));
|
|
38
|
+
MultiByteToWideChar(CP_UTF8, 0, title, -1, wTitle, titleLen);
|
|
39
|
+
|
|
40
|
+
UINT dpi = 96;
|
|
41
|
+
HMODULE user32 = GetModuleHandleW(L"user32.dll");
|
|
42
|
+
if (user32) {
|
|
43
|
+
typedef UINT (WINAPI *GetDpiForSystemFn)(void);
|
|
44
|
+
GetDpiForSystemFn getDpi = (GetDpiForSystemFn)GetProcAddress(user32, "GetDpiForSystem");
|
|
45
|
+
if (getDpi) dpi = getDpi();
|
|
46
|
+
}
|
|
47
|
+
int scaledW = MulDiv(width, dpi, 96);
|
|
48
|
+
int scaledH = MulDiv(height, dpi, 96);
|
|
49
|
+
int scaledX = (x != 0) ? MulDiv(x, dpi, 96) : 0;
|
|
50
|
+
int scaledY = (y != 0) ? MulDiv(y, dpi, 96) : 0;
|
|
51
|
+
|
|
52
|
+
// Build style from options
|
|
53
|
+
DWORD style = WS_CLIPCHILDREN;
|
|
54
|
+
DWORD exStyle = 0;
|
|
55
|
+
if (borderless) {
|
|
56
|
+
style |= WS_POPUP;
|
|
57
|
+
} else {
|
|
58
|
+
style |= WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
|
|
59
|
+
if (resizable) style |= WS_THICKFRAME;
|
|
60
|
+
if (minimizable) style |= WS_MINIMIZEBOX;
|
|
61
|
+
if (maximizable) style |= WS_MAXIMIZEBOX;
|
|
62
|
+
}
|
|
63
|
+
if (alwaysOnTop) exStyle |= WS_EX_TOPMOST;
|
|
64
|
+
|
|
65
|
+
RECT rc = { 0, 0, scaledW, scaledH };
|
|
66
|
+
AdjustWindowRectEx(&rc, style, zapp_default_menu ? TRUE : FALSE, exStyle);
|
|
67
|
+
int adjW = rc.right - rc.left;
|
|
68
|
+
int adjH = rc.bottom - rc.top;
|
|
69
|
+
|
|
70
|
+
int screenW = GetSystemMetrics(SM_CXSCREEN);
|
|
71
|
+
int screenH = GetSystemMetrics(SM_CYSCREEN);
|
|
72
|
+
int posX = (scaledX == 0) ? (screenW - adjW) / 2 : scaledX;
|
|
73
|
+
int posY = (scaledY == 0) ? (screenH - adjH) / 2 : scaledY;
|
|
74
|
+
|
|
75
|
+
HWND hwnd = CreateWindowExW(
|
|
76
|
+
exStyle,
|
|
77
|
+
ZAPP_WINDOW_CLASS,
|
|
78
|
+
wTitle,
|
|
79
|
+
style,
|
|
80
|
+
posX, posY, adjW, adjH,
|
|
81
|
+
NULL, borderless ? NULL : zapp_default_menu,
|
|
82
|
+
GetModuleHandleW(NULL),
|
|
83
|
+
NULL
|
|
84
|
+
);
|
|
85
|
+
free(wTitle);
|
|
86
|
+
return hwnd;
|
|
87
|
+
}
|
|
88
|
+
#endif
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fn windows_window_create(options: WindowOptions*) -> void* {
|
|
92
|
+
let handle: void* = NULL;
|
|
93
|
+
raw {
|
|
94
|
+
#ifdef _WIN32
|
|
95
|
+
extern void zapp_register_window_class(void);
|
|
96
|
+
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
|
97
|
+
zapp_register_window_class();
|
|
98
|
+
HWND hwnd = zapp_create_hwnd(
|
|
99
|
+
(const char*)options->title,
|
|
100
|
+
options->width, options->height,
|
|
101
|
+
options->x, options->y,
|
|
102
|
+
options->resizable, options->closable,
|
|
103
|
+
options->minimizable, options->maximizable,
|
|
104
|
+
options->borderless, options->alwaysOnTop
|
|
105
|
+
);
|
|
106
|
+
if (hwnd == NULL) {
|
|
107
|
+
fprintf(stderr, "[zapp] CreateWindowEx failed: %lu\n", GetLastError());
|
|
108
|
+
return NULL;
|
|
109
|
+
}
|
|
110
|
+
BOOL inspectable = options->webContentInspectable > 0 ? TRUE : FALSE;
|
|
111
|
+
zapp_windows_webview_create(hwnd, inspectable);
|
|
112
|
+
if (options->fullscreen) {
|
|
113
|
+
windows_window_set_fullscreen((void*)hwnd, true);
|
|
114
|
+
}
|
|
115
|
+
if (options->visible && !options->hidden) {
|
|
116
|
+
ShowWindow(hwnd, SW_SHOW);
|
|
117
|
+
UpdateWindow(hwnd);
|
|
118
|
+
}
|
|
119
|
+
handle = (void*)hwnd;
|
|
120
|
+
#endif
|
|
121
|
+
}
|
|
122
|
+
return handle;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
fn windows_window_destroy(window: void*) -> void {
|
|
126
|
+
raw {
|
|
127
|
+
#ifdef _WIN32
|
|
128
|
+
HWND hwnd = (HWND)window;
|
|
129
|
+
if (hwnd && IsWindow(hwnd)) DestroyWindow(hwnd);
|
|
130
|
+
#endif
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fn windows_window_show(window: void*) -> void {
|
|
135
|
+
raw {
|
|
136
|
+
#ifdef _WIN32
|
|
137
|
+
HWND hwnd = (HWND)window;
|
|
138
|
+
if (hwnd && IsWindow(hwnd)) { ShowWindow(hwnd, SW_SHOW); UpdateWindow(hwnd); }
|
|
139
|
+
#endif
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fn windows_window_hide(window: void*) -> void {
|
|
144
|
+
raw {
|
|
145
|
+
#ifdef _WIN32
|
|
146
|
+
HWND hwnd = (HWND)window;
|
|
147
|
+
if (hwnd && IsWindow(hwnd)) ShowWindow(hwnd, SW_HIDE);
|
|
148
|
+
#endif
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fn windows_window_minimize(window: void*) -> void {
|
|
153
|
+
raw {
|
|
154
|
+
#ifdef _WIN32
|
|
155
|
+
HWND hwnd = (HWND)window;
|
|
156
|
+
if (hwnd && IsWindow(hwnd)) ShowWindow(hwnd, SW_MINIMIZE);
|
|
157
|
+
#endif
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
fn windows_window_unminimize(window: void*) -> void {
|
|
162
|
+
raw {
|
|
163
|
+
#ifdef _WIN32
|
|
164
|
+
HWND hwnd = (HWND)window;
|
|
165
|
+
if (hwnd && IsWindow(hwnd)) ShowWindow(hwnd, SW_RESTORE);
|
|
166
|
+
#endif
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fn windows_window_maximize(window: void*) -> void {
|
|
171
|
+
raw {
|
|
172
|
+
#ifdef _WIN32
|
|
173
|
+
HWND hwnd = (HWND)window;
|
|
174
|
+
if (hwnd && IsWindow(hwnd)) ShowWindow(hwnd, SW_MAXIMIZE);
|
|
175
|
+
#endif
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
fn windows_window_unmaximize(window: void*) -> void {
|
|
180
|
+
raw {
|
|
181
|
+
#ifdef _WIN32
|
|
182
|
+
HWND hwnd = (HWND)window;
|
|
183
|
+
if (hwnd && IsWindow(hwnd) && IsZoomed(hwnd)) ShowWindow(hwnd, SW_RESTORE);
|
|
184
|
+
#endif
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
fn windows_window_toggle_minimize(window: void*) -> void {
|
|
189
|
+
raw {
|
|
190
|
+
#ifdef _WIN32
|
|
191
|
+
HWND hwnd = (HWND)window;
|
|
192
|
+
if (hwnd && IsWindow(hwnd)) ShowWindow(hwnd, IsIconic(hwnd) ? SW_RESTORE : SW_MINIMIZE);
|
|
193
|
+
#endif
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
fn windows_window_toggle_maximize(window: void*) -> void {
|
|
198
|
+
raw {
|
|
199
|
+
#ifdef _WIN32
|
|
200
|
+
HWND hwnd = (HWND)window;
|
|
201
|
+
if (hwnd && IsWindow(hwnd)) ShowWindow(hwnd, IsZoomed(hwnd) ? SW_RESTORE : SW_MAXIMIZE);
|
|
202
|
+
#endif
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
fn windows_window_set_title(window: void*, title: string) -> void {
|
|
207
|
+
raw {
|
|
208
|
+
#ifdef _WIN32
|
|
209
|
+
HWND hwnd = (HWND)window;
|
|
210
|
+
if (hwnd && IsWindow(hwnd) && title) {
|
|
211
|
+
int len = MultiByteToWideChar(CP_UTF8, 0, (const char*)title, -1, NULL, 0);
|
|
212
|
+
wchar_t* wTitle = (wchar_t*)malloc(len * sizeof(wchar_t));
|
|
213
|
+
MultiByteToWideChar(CP_UTF8, 0, (const char*)title, -1, wTitle, len);
|
|
214
|
+
SetWindowTextW(hwnd, wTitle);
|
|
215
|
+
free(wTitle);
|
|
216
|
+
}
|
|
217
|
+
#endif
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fn windows_window_set_size(window: void*, width: int, height: int) -> void {
|
|
222
|
+
raw {
|
|
223
|
+
#ifdef _WIN32
|
|
224
|
+
HWND hwnd = (HWND)window;
|
|
225
|
+
if (hwnd && IsWindow(hwnd)) {
|
|
226
|
+
// Convert client area dimensions to window frame dimensions
|
|
227
|
+
DWORD style = (DWORD)GetWindowLongW(hwnd, GWL_STYLE);
|
|
228
|
+
BOOL hasMenu = GetMenu(hwnd) != NULL;
|
|
229
|
+
RECT desired = { 0, 0, width, height };
|
|
230
|
+
AdjustWindowRectEx(&desired, style, hasMenu, 0);
|
|
231
|
+
RECT pos;
|
|
232
|
+
GetWindowRect(hwnd, &pos);
|
|
233
|
+
MoveWindow(hwnd, pos.left, pos.top,
|
|
234
|
+
desired.right - desired.left,
|
|
235
|
+
desired.bottom - desired.top, TRUE);
|
|
236
|
+
}
|
|
237
|
+
#endif
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
fn windows_window_set_position(window: void*, x: int, y: int) -> void {
|
|
242
|
+
raw {
|
|
243
|
+
#ifdef _WIN32
|
|
244
|
+
HWND hwnd = (HWND)window;
|
|
245
|
+
if (hwnd && IsWindow(hwnd)) {
|
|
246
|
+
RECT rc;
|
|
247
|
+
GetWindowRect(hwnd, &rc);
|
|
248
|
+
MoveWindow(hwnd, x, y, rc.right - rc.left, rc.bottom - rc.top, TRUE);
|
|
249
|
+
}
|
|
250
|
+
#endif
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
fn windows_window_set_fullscreen(window: void*, on: bool) -> void {
|
|
255
|
+
raw {
|
|
256
|
+
#ifdef _WIN32
|
|
257
|
+
HWND hwnd = (HWND)window;
|
|
258
|
+
if (!hwnd || !IsWindow(hwnd)) return;
|
|
259
|
+
extern int zapp_window_trigger_event_with_data(int id, int event_id, int w, int h, int x, int y);
|
|
260
|
+
extern void zapp_dispatch_window_event_to_bridge_win(const char* window_id, int event_id, int w, int h, int x, int y);
|
|
261
|
+
extern const char* zapp_win_id_for_hwnd(HWND hwnd);
|
|
262
|
+
extern int zapp_window_get_numeric_id(const char* window_id);
|
|
263
|
+
ZappWindowEntry* entry = zapp_window_entry_for_hwnd(hwnd);
|
|
264
|
+
LONG style = GetWindowLongW(hwnd, GWL_STYLE);
|
|
265
|
+
BOOL isFS = !(style & WS_OVERLAPPEDWINDOW);
|
|
266
|
+
if (on && !isFS) {
|
|
267
|
+
MONITORINFO mi = { sizeof(MONITORINFO) };
|
|
268
|
+
if (entry) {
|
|
269
|
+
entry->prevPlacement.length = sizeof(WINDOWPLACEMENT);
|
|
270
|
+
GetWindowPlacement(hwnd, &entry->prevPlacement);
|
|
271
|
+
}
|
|
272
|
+
GetMonitorInfoW(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST), &mi);
|
|
273
|
+
SetWindowLongW(hwnd, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW);
|
|
274
|
+
SetWindowPos(hwnd, HWND_TOP,
|
|
275
|
+
mi.rcMonitor.left, mi.rcMonitor.top,
|
|
276
|
+
mi.rcMonitor.right - mi.rcMonitor.left,
|
|
277
|
+
mi.rcMonitor.bottom - mi.rcMonitor.top,
|
|
278
|
+
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
|
|
279
|
+
// Fire FULLSCREEN event
|
|
280
|
+
const char* wid = zapp_win_id_for_hwnd(hwnd);
|
|
281
|
+
if (wid) {
|
|
282
|
+
int nid = zapp_window_get_numeric_id(wid);
|
|
283
|
+
if (nid >= 0) zapp_window_trigger_event_with_data(nid, 9, 0, 0, 0, 0);
|
|
284
|
+
if (entry && entry->bridgeReady) zapp_dispatch_window_event_to_bridge_win(wid, 9, 0, 0, 0, 0);
|
|
285
|
+
}
|
|
286
|
+
} else if (!on && isFS && entry) {
|
|
287
|
+
SetWindowLongW(hwnd, GWL_STYLE, style | WS_OVERLAPPEDWINDOW);
|
|
288
|
+
SetWindowPlacement(hwnd, &entry->prevPlacement);
|
|
289
|
+
SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
|
|
290
|
+
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
|
|
291
|
+
// Fire UNFULLSCREEN event
|
|
292
|
+
const char* wid = zapp_win_id_for_hwnd(hwnd);
|
|
293
|
+
if (wid) {
|
|
294
|
+
int nid = zapp_window_get_numeric_id(wid);
|
|
295
|
+
if (nid >= 0) zapp_window_trigger_event_with_data(nid, 10, 0, 0, 0, 0);
|
|
296
|
+
if (entry && entry->bridgeReady) zapp_dispatch_window_event_to_bridge_win(wid, 10, 0, 0, 0, 0);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
#endif
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
fn windows_window_set_always_on_top(window: void*, on: bool) -> void {
|
|
304
|
+
raw {
|
|
305
|
+
#ifdef _WIN32
|
|
306
|
+
HWND hwnd = (HWND)window;
|
|
307
|
+
if (hwnd && IsWindow(hwnd))
|
|
308
|
+
SetWindowPos(hwnd, on ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
|
309
|
+
#endif
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
raw {
|
|
314
|
+
#ifdef _WIN32
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
316
|
+
// Numeric ID registry (maps window ID strings to numeric IDs for on_ready)
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
#define ZAPP_MAX_NUMERIC_IDS 64
|
|
320
|
+
typedef struct { char window_id[64]; int numeric_id; int in_use; } ZappNumericIdEntry;
|
|
321
|
+
static ZappNumericIdEntry zapp_numeric_ids[ZAPP_MAX_NUMERIC_IDS] = {0};
|
|
322
|
+
|
|
323
|
+
static void zapp_window_register_numeric_id_str(const char* window_id, int numeric_id) {
|
|
324
|
+
if (!window_id) return;
|
|
325
|
+
for (int i = 0; i < ZAPP_MAX_NUMERIC_IDS; i++) {
|
|
326
|
+
if (!zapp_numeric_ids[i].in_use) {
|
|
327
|
+
strncpy(zapp_numeric_ids[i].window_id, window_id, 63);
|
|
328
|
+
zapp_numeric_ids[i].window_id[63] = '\0';
|
|
329
|
+
zapp_numeric_ids[i].numeric_id = numeric_id;
|
|
330
|
+
zapp_numeric_ids[i].in_use = 1;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
int zapp_window_get_numeric_id(const char* window_id) {
|
|
337
|
+
if (!window_id) return -1;
|
|
338
|
+
for (int i = 0; i < ZAPP_MAX_NUMERIC_IDS; i++) {
|
|
339
|
+
if (zapp_numeric_ids[i].in_use && strcmp(zapp_numeric_ids[i].window_id, window_id) == 0)
|
|
340
|
+
return zapp_numeric_ids[i].numeric_id;
|
|
341
|
+
}
|
|
342
|
+
return -1;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
// Window registry (JS-created windows by string ID)
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
|
|
349
|
+
#define ZAPP_WIN_REG_MAX 256
|
|
350
|
+
typedef struct { char id[64]; HWND hwnd; int in_use; } ZappWinRegEntry;
|
|
351
|
+
static ZappWinRegEntry zapp_win_reg[ZAPP_WIN_REG_MAX] = {0};
|
|
352
|
+
static int zapp_win_reg_next = 0;
|
|
353
|
+
|
|
354
|
+
static const char* zapp_win_register(HWND hwnd) {
|
|
355
|
+
for (int i = 0; i < ZAPP_WIN_REG_MAX; i++) {
|
|
356
|
+
if (!zapp_win_reg[i].in_use) {
|
|
357
|
+
snprintf(zapp_win_reg[i].id, sizeof(zapp_win_reg[i].id), "win-%d", zapp_win_reg_next++);
|
|
358
|
+
zapp_win_reg[i].hwnd = hwnd;
|
|
359
|
+
zapp_win_reg[i].in_use = 1;
|
|
360
|
+
return zapp_win_reg[i].id;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return NULL;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
static HWND zapp_win_lookup(const char* windowId) {
|
|
367
|
+
if (!windowId) return NULL;
|
|
368
|
+
for (int i = 0; i < ZAPP_WIN_REG_MAX; i++) {
|
|
369
|
+
if (zapp_win_reg[i].in_use && strcmp(zapp_win_reg[i].id, windowId) == 0)
|
|
370
|
+
return zapp_win_reg[i].hwnd;
|
|
371
|
+
}
|
|
372
|
+
return NULL;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
static void zapp_win_unregister(const char* windowId) {
|
|
376
|
+
if (!windowId) return;
|
|
377
|
+
for (int i = 0; i < ZAPP_WIN_REG_MAX; i++) {
|
|
378
|
+
if (zapp_win_reg[i].in_use && strcmp(zapp_win_reg[i].id, windowId) == 0) {
|
|
379
|
+
zapp_win_reg[i].in_use = 0;
|
|
380
|
+
zapp_win_reg[i].hwnd = NULL;
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
void zapp_win_unregister_by_hwnd(HWND hwnd) {
|
|
387
|
+
if (!hwnd) return;
|
|
388
|
+
for (int i = 0; i < ZAPP_WIN_REG_MAX; i++) {
|
|
389
|
+
if (zapp_win_reg[i].in_use && zapp_win_reg[i].hwnd == hwnd) {
|
|
390
|
+
zapp_win_reg[i].in_use = 0;
|
|
391
|
+
zapp_win_reg[i].hwnd = NULL;
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const char* zapp_win_id_for_hwnd(HWND hwnd) {
|
|
398
|
+
if (!hwnd) return NULL;
|
|
399
|
+
for (int i = 0; i < ZAPP_WIN_REG_MAX; i++) {
|
|
400
|
+
if (zapp_win_reg[i].in_use && zapp_win_reg[i].hwnd == hwnd)
|
|
401
|
+
return zapp_win_reg[i].id;
|
|
402
|
+
}
|
|
403
|
+
return NULL;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
// Dispatch window result to all webviews + backend
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
|
|
410
|
+
extern void zapp_windows_webview_eval_all(const char* js_utf8);
|
|
411
|
+
extern char* zapp_escape_js_string(const char* raw);
|
|
412
|
+
extern void zapp_backend_dispatch_window_result(const char* payload_json);
|
|
413
|
+
extern void windows_window_set_fullscreen(void* window, _Bool on);
|
|
414
|
+
|
|
415
|
+
static void zapp_dispatch_window_result_to_all(const char* payload_json) {
|
|
416
|
+
char* escaped = zapp_escape_js_string(payload_json);
|
|
417
|
+
size_t jsLen = strlen(escaped) + 256;
|
|
418
|
+
char* js = (char*)malloc(jsLen);
|
|
419
|
+
snprintf(js, jsLen,
|
|
420
|
+
"(function(){var b=globalThis[Symbol.for('zapp.bridge')];"
|
|
421
|
+
"if(b&&typeof b.dispatchWindowResult==='function'){b.dispatchWindowResult('%s');}})();",
|
|
422
|
+
escaped);
|
|
423
|
+
zapp_windows_webview_eval_all(js);
|
|
424
|
+
free(js);
|
|
425
|
+
free(escaped);
|
|
426
|
+
zapp_backend_dispatch_window_result(payload_json);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
#ifndef ZAPP_EVENT_WINDOW_READY
|
|
430
|
+
#define ZAPP_EVENT_WINDOW_READY 0
|
|
431
|
+
#endif
|
|
432
|
+
#ifndef ZAPP_EVENT_WINDOW_FOCUS
|
|
433
|
+
#define ZAPP_EVENT_WINDOW_FOCUS 1
|
|
434
|
+
#endif
|
|
435
|
+
#ifndef ZAPP_EVENT_WINDOW_BLUR
|
|
436
|
+
#define ZAPP_EVENT_WINDOW_BLUR 2
|
|
437
|
+
#endif
|
|
438
|
+
#ifndef ZAPP_EVENT_WINDOW_RESIZE
|
|
439
|
+
#define ZAPP_EVENT_WINDOW_RESIZE 3
|
|
440
|
+
#endif
|
|
441
|
+
#ifndef ZAPP_EVENT_WINDOW_MOVE
|
|
442
|
+
#define ZAPP_EVENT_WINDOW_MOVE 4
|
|
443
|
+
#endif
|
|
444
|
+
#ifndef ZAPP_EVENT_WINDOW_CLOSE
|
|
445
|
+
#define ZAPP_EVENT_WINDOW_CLOSE 5
|
|
446
|
+
#endif
|
|
447
|
+
#ifndef ZAPP_EVENT_WINDOW_MINIMIZE
|
|
448
|
+
#define ZAPP_EVENT_WINDOW_MINIMIZE 6
|
|
449
|
+
#endif
|
|
450
|
+
#ifndef ZAPP_EVENT_WINDOW_MAXIMIZE
|
|
451
|
+
#define ZAPP_EVENT_WINDOW_MAXIMIZE 7
|
|
452
|
+
#endif
|
|
453
|
+
#ifndef ZAPP_EVENT_WINDOW_RESTORE
|
|
454
|
+
#define ZAPP_EVENT_WINDOW_RESTORE 8
|
|
455
|
+
#endif
|
|
456
|
+
#ifndef ZAPP_EVENT_WINDOW_FULLSCREEN
|
|
457
|
+
#define ZAPP_EVENT_WINDOW_FULLSCREEN 9
|
|
458
|
+
#endif
|
|
459
|
+
#ifndef ZAPP_EVENT_WINDOW_UNFULLSCREEN
|
|
460
|
+
#define ZAPP_EVENT_WINDOW_UNFULLSCREEN 10
|
|
461
|
+
#endif
|
|
462
|
+
|
|
463
|
+
static const char* zapp_get_window_event_name_win(int event_id) {
|
|
464
|
+
switch (event_id) {
|
|
465
|
+
case ZAPP_EVENT_WINDOW_READY: return "ready";
|
|
466
|
+
case ZAPP_EVENT_WINDOW_FOCUS: return "focus";
|
|
467
|
+
case ZAPP_EVENT_WINDOW_BLUR: return "blur";
|
|
468
|
+
case ZAPP_EVENT_WINDOW_RESIZE: return "resize";
|
|
469
|
+
case ZAPP_EVENT_WINDOW_MOVE: return "move";
|
|
470
|
+
case ZAPP_EVENT_WINDOW_CLOSE: return "close";
|
|
471
|
+
case ZAPP_EVENT_WINDOW_MINIMIZE: return "minimize";
|
|
472
|
+
case ZAPP_EVENT_WINDOW_MAXIMIZE: return "maximize";
|
|
473
|
+
case ZAPP_EVENT_WINDOW_RESTORE: return "restore";
|
|
474
|
+
case ZAPP_EVENT_WINDOW_FULLSCREEN: return "fullscreen";
|
|
475
|
+
case ZAPP_EVENT_WINDOW_UNFULLSCREEN: return "unfullscreen";
|
|
476
|
+
default: return "unknown";
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
void zapp_dispatch_window_event_to_bridge_win(const char* window_id, int event_id, int width, int height, int x, int y) {
|
|
481
|
+
if (window_id == NULL) return;
|
|
482
|
+
const char* event_name = zapp_get_window_event_name_win(event_id);
|
|
483
|
+
// Only include size/position payload for events where it's meaningful
|
|
484
|
+
int hasPayload = (event_id == ZAPP_EVENT_WINDOW_RESIZE || event_id == ZAPP_EVENT_WINDOW_MOVE ||
|
|
485
|
+
event_id == ZAPP_EVENT_WINDOW_MAXIMIZE || event_id == ZAPP_EVENT_WINDOW_RESTORE);
|
|
486
|
+
char js[1024];
|
|
487
|
+
if (hasPayload) {
|
|
488
|
+
snprintf(js, sizeof(js),
|
|
489
|
+
"(function(){var b=globalThis[Symbol.for('zapp.bridge')];"
|
|
490
|
+
"if(b&&b.dispatchWindowEvent)b.dispatchWindowEvent('%s','%s',"
|
|
491
|
+
"'{\"width\":%d,\"height\":%d,\"x\":%d,\"y\":%d}');})();",
|
|
492
|
+
window_id, event_name, width, height, x, y);
|
|
493
|
+
} else {
|
|
494
|
+
snprintf(js, sizeof(js),
|
|
495
|
+
"(function(){var b=globalThis[Symbol.for('zapp.bridge')];"
|
|
496
|
+
"if(b&&b.dispatchWindowEvent)b.dispatchWindowEvent('%s','%s');})();",
|
|
497
|
+
window_id, event_name);
|
|
498
|
+
}
|
|
499
|
+
zapp_windows_webview_eval_all(js);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// ---------------------------------------------------------------------------
|
|
503
|
+
// app_handle_window_message — called from app.zc for "window" messages
|
|
504
|
+
// ---------------------------------------------------------------------------
|
|
505
|
+
|
|
506
|
+
extern void zapp_windows_webview_create(HWND hwnd, BOOL inspectable);
|
|
507
|
+
extern const char* zapp_build_initial_url(void);
|
|
508
|
+
extern void zapp_backend_dispatch_window_ready(const char* window_id);
|
|
509
|
+
extern void window_manager_trigger_on_ready(int window_id);
|
|
510
|
+
|
|
511
|
+
extern char* zapp_json_get_string(const char* json, const char* key);
|
|
512
|
+
extern int zapp_json_get_int(const char* json, const char* key, int default_val);
|
|
513
|
+
extern int zapp_json_get_bool(const char* json, const char* key, int default_val);
|
|
514
|
+
extern char* zapp_json_get_raw(const char* json, const char* key);
|
|
515
|
+
|
|
516
|
+
void app_handle_window_message(App* app, char* action, char* payload_json) {
|
|
517
|
+
if (action == NULL || payload_json == NULL) return;
|
|
518
|
+
const char* json = (const char*)payload_json;
|
|
519
|
+
|
|
520
|
+
if (strcmp(action, "ready") == 0) {
|
|
521
|
+
char* windowId = zapp_json_get_string(json, "windowId");
|
|
522
|
+
if (windowId && windowId[0] != '\0') {
|
|
523
|
+
zapp_backend_dispatch_window_ready(windowId);
|
|
524
|
+
int numericId = zapp_window_get_numeric_id(windowId);
|
|
525
|
+
if (numericId >= 0) {
|
|
526
|
+
window_manager_trigger_on_ready(numericId);
|
|
527
|
+
}
|
|
528
|
+
HWND hwnd = zapp_win_lookup(windowId);
|
|
529
|
+
if (hwnd) {
|
|
530
|
+
ZappWindowEntry* entry = zapp_window_entry_for_hwnd(hwnd);
|
|
531
|
+
if (entry) {
|
|
532
|
+
entry->bridgeReady = TRUE;
|
|
533
|
+
if (entry->pendingFocusEvent) {
|
|
534
|
+
entry->pendingFocusEvent = FALSE;
|
|
535
|
+
zapp_dispatch_window_event_to_bridge_win(windowId, ZAPP_EVENT_WINDOW_FOCUS, 0, 0, 0, 0);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (windowId) free(windowId);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (strcmp(action, "create") == 0) {
|
|
545
|
+
char* requestId = zapp_json_get_string(json, "requestId");
|
|
546
|
+
if (!requestId || requestId[0] == '\0') { free(requestId); return; }
|
|
547
|
+
|
|
548
|
+
char* optsRaw = zapp_json_get_raw(json, "options");
|
|
549
|
+
const char* opts = optsRaw ? optsRaw : "{}";
|
|
550
|
+
|
|
551
|
+
char* title = zapp_json_get_string(opts, "title");
|
|
552
|
+
int width = zapp_json_get_int(opts, "width", 800);
|
|
553
|
+
int height = zapp_json_get_int(opts, "height", 600);
|
|
554
|
+
int x = zapp_json_get_int(opts, "x", 0);
|
|
555
|
+
int y = zapp_json_get_int(opts, "y", 0);
|
|
556
|
+
int visible = zapp_json_get_bool(opts, "visible", 1);
|
|
557
|
+
|
|
558
|
+
HWND hwnd = zapp_create_hwnd(
|
|
559
|
+
title ? title : "Zapp Window",
|
|
560
|
+
width, height, x, y,
|
|
561
|
+
1, 1, 1, 1, 0, 0 // defaults: resizable, closable, minimizable, maximizable, not borderless, not alwaysOnTop
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
if (hwnd) {
|
|
565
|
+
extern bool app_get_bootstrap_web_content_inspectable(void);
|
|
566
|
+
zapp_windows_webview_create(hwnd, app_get_bootstrap_web_content_inspectable() ? TRUE : FALSE);
|
|
567
|
+
const char* windowId = zapp_win_register(hwnd);
|
|
568
|
+
|
|
569
|
+
char* url = zapp_json_get_string(opts, "url");
|
|
570
|
+
if (url && url[0]) {
|
|
571
|
+
extern void zapp_windows_webview_navigate(HWND hwnd, const char* url_utf8);
|
|
572
|
+
zapp_windows_webview_navigate(hwnd, url);
|
|
573
|
+
}
|
|
574
|
+
if (url) free(url);
|
|
575
|
+
|
|
576
|
+
if (visible) {
|
|
577
|
+
ShowWindow(hwnd, SW_SHOW);
|
|
578
|
+
UpdateWindow(hwnd);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
char response[512];
|
|
582
|
+
snprintf(response, sizeof(response),
|
|
583
|
+
"{\"requestId\":\"%s\",\"id\":\"%s\",\"ok\":true}",
|
|
584
|
+
requestId, windowId ? windowId : "");
|
|
585
|
+
zapp_dispatch_window_result_to_all(response);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
free(requestId);
|
|
589
|
+
free(title);
|
|
590
|
+
if (optsRaw) free(optsRaw);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
char* windowId = zapp_json_get_string(json, "windowId");
|
|
595
|
+
if (!windowId || windowId[0] == '\0') { free(windowId); return; }
|
|
596
|
+
|
|
597
|
+
HWND hwnd = zapp_win_lookup(windowId);
|
|
598
|
+
if (!hwnd || !IsWindow(hwnd)) { free(windowId); return; }
|
|
599
|
+
|
|
600
|
+
if (strcmp(action, "setCloseGuard") == 0) {
|
|
601
|
+
extern int zapp_json_get_bool(const char* json, const char* key, int default_val);
|
|
602
|
+
int guard = zapp_json_get_bool(json, "guard", 0);
|
|
603
|
+
ZappWindowEntry* entry = zapp_window_entry_for_hwnd(hwnd);
|
|
604
|
+
if (entry) entry->closeGuarded = guard ? TRUE : FALSE;
|
|
605
|
+
free(windowId);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (strcmp(action, "destroy") == 0) {
|
|
610
|
+
ZappWindowEntry* entry = zapp_window_entry_for_hwnd(hwnd);
|
|
611
|
+
if (entry) entry->forceClose = TRUE;
|
|
612
|
+
PostMessageW(hwnd, WM_CLOSE, 0, 0);
|
|
613
|
+
free(windowId);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (strcmp(action, "startDrag") == 0) {
|
|
618
|
+
ReleaseCapture();
|
|
619
|
+
SendMessageW(hwnd, WM_SYSCOMMAND, SC_MOVE | HTCAPTION, 0);
|
|
620
|
+
free(windowId);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (strcmp(action, "close") == 0) {
|
|
625
|
+
zapp_win_unregister(windowId);
|
|
626
|
+
DestroyWindow(hwnd);
|
|
627
|
+
} else if (strcmp(action, "show") == 0) {
|
|
628
|
+
ShowWindow(hwnd, SW_SHOW);
|
|
629
|
+
UpdateWindow(hwnd);
|
|
630
|
+
} else if (strcmp(action, "hide") == 0) {
|
|
631
|
+
ShowWindow(hwnd, SW_HIDE);
|
|
632
|
+
} else if (strcmp(action, "minimize") == 0) {
|
|
633
|
+
ShowWindow(hwnd, SW_MINIMIZE);
|
|
634
|
+
} else if (strcmp(action, "maximize") == 0) {
|
|
635
|
+
ShowWindow(hwnd, SW_MAXIMIZE);
|
|
636
|
+
} else if (strcmp(action, "unminimize") == 0) {
|
|
637
|
+
ShowWindow(hwnd, SW_RESTORE);
|
|
638
|
+
} else if (strcmp(action, "unmaximize") == 0) {
|
|
639
|
+
if (IsZoomed(hwnd)) ShowWindow(hwnd, SW_RESTORE);
|
|
640
|
+
} else if (strcmp(action, "toggle_minimize") == 0) {
|
|
641
|
+
ShowWindow(hwnd, IsIconic(hwnd) ? SW_RESTORE : SW_MINIMIZE);
|
|
642
|
+
} else if (strcmp(action, "toggle_maximize") == 0) {
|
|
643
|
+
ShowWindow(hwnd, IsZoomed(hwnd) ? SW_RESTORE : SW_MAXIMIZE);
|
|
644
|
+
} else if (strcmp(action, "set_title") == 0) {
|
|
645
|
+
char* t = zapp_json_get_string(json, "title");
|
|
646
|
+
if (t) {
|
|
647
|
+
int len = MultiByteToWideChar(CP_UTF8, 0, t, -1, NULL, 0);
|
|
648
|
+
wchar_t* wt = (wchar_t*)malloc(len * sizeof(wchar_t));
|
|
649
|
+
MultiByteToWideChar(CP_UTF8, 0, t, -1, wt, len);
|
|
650
|
+
SetWindowTextW(hwnd, wt);
|
|
651
|
+
free(wt);
|
|
652
|
+
free(t);
|
|
653
|
+
}
|
|
654
|
+
} else if (strcmp(action, "set_size") == 0) {
|
|
655
|
+
int w = zapp_json_get_int(json, "width", -1);
|
|
656
|
+
int h = zapp_json_get_int(json, "height", -1);
|
|
657
|
+
if (w > 0 && h > 0) {
|
|
658
|
+
RECT rc;
|
|
659
|
+
GetWindowRect(hwnd, &rc);
|
|
660
|
+
MoveWindow(hwnd, rc.left, rc.top, w, h, TRUE);
|
|
661
|
+
}
|
|
662
|
+
} else if (strcmp(action, "set_position") == 0) {
|
|
663
|
+
int px = zapp_json_get_int(json, "x", -1);
|
|
664
|
+
int py = zapp_json_get_int(json, "y", -1);
|
|
665
|
+
if (px >= 0 && py >= 0) {
|
|
666
|
+
RECT rc;
|
|
667
|
+
GetWindowRect(hwnd, &rc);
|
|
668
|
+
int w = rc.right - rc.left;
|
|
669
|
+
int h = rc.bottom - rc.top;
|
|
670
|
+
MoveWindow(hwnd, px, py, w, h, TRUE);
|
|
671
|
+
}
|
|
672
|
+
} else if (strcmp(action, "set_fullscreen") == 0) {
|
|
673
|
+
int on = zapp_json_get_bool(json, "on", 0);
|
|
674
|
+
windows_window_set_fullscreen((void*)hwnd, on);
|
|
675
|
+
} else if (strcmp(action, "set_always_on_top") == 0) {
|
|
676
|
+
int on = zapp_json_get_bool(json, "on", 0);
|
|
677
|
+
SetWindowPos(hwnd, on ? HWND_TOPMOST : HWND_NOTOPMOST,
|
|
678
|
+
0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
free(windowId);
|
|
682
|
+
}
|
|
683
|
+
#endif
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
raw {
|
|
687
|
+
#ifdef _WIN32
|
|
688
|
+
// --- Dialog handling (Windows) ---
|
|
689
|
+
extern char* zapp_json_get_string(const char* json, const char* key);
|
|
690
|
+
extern int zapp_json_get_bool(const char* json, const char* key, int default_val);
|
|
691
|
+
extern char* zapp_json_get_raw(const char* json, const char* key);
|
|
692
|
+
#include <commdlg.h>
|
|
693
|
+
#include <shlobj.h>
|
|
694
|
+
#include <shobjidl.h>
|
|
695
|
+
#include <commctrl.h>
|
|
696
|
+
|
|
697
|
+
static void zapp_dispatch_dialog_result(const char* payload_json) {
|
|
698
|
+
char* escaped = zapp_escape_js_string(payload_json);
|
|
699
|
+
size_t jsLen = strlen(escaped) + 256;
|
|
700
|
+
char* js = (char*)malloc(jsLen);
|
|
701
|
+
snprintf(js, jsLen,
|
|
702
|
+
"(function(){var b=globalThis[Symbol.for('zapp.bridge')];"
|
|
703
|
+
"if(b&&typeof b.dispatchDialogResult==='function'){b.dispatchDialogResult('%s');}})();",
|
|
704
|
+
escaped);
|
|
705
|
+
zapp_windows_webview_eval_all(js);
|
|
706
|
+
free(js);
|
|
707
|
+
free(escaped);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Helper: escape a UTF-8 string for JSON embedding (quotes + backslashes)
|
|
711
|
+
static void zapp_json_escape_path(const char* src, char* dst, size_t dstSize) {
|
|
712
|
+
size_t i = 0;
|
|
713
|
+
while (*src && i + 3 < dstSize) {
|
|
714
|
+
if (*src == '"') { dst[i++] = '\\'; dst[i++] = '"'; }
|
|
715
|
+
else if (*src == '\\') { dst[i++] = '\\'; dst[i++] = '\\'; }
|
|
716
|
+
else { dst[i++] = *src; }
|
|
717
|
+
src++;
|
|
718
|
+
}
|
|
719
|
+
dst[i] = '\0';
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// ---------------------------------------------------------------------------
|
|
723
|
+
// openFile: directory selection via IFileDialog COM
|
|
724
|
+
// ---------------------------------------------------------------------------
|
|
725
|
+
static int zapp_open_folder_dialog(const char* titleUtf8, const char* defaultPathUtf8,
|
|
726
|
+
char* requestId) {
|
|
727
|
+
IFileDialog* pfd = NULL;
|
|
728
|
+
HRESULT hr = CoCreateInstance(&CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER,
|
|
729
|
+
&IID_IFileOpenDialog, (void**)&pfd);
|
|
730
|
+
if (FAILED(hr) || !pfd) return 0;
|
|
731
|
+
|
|
732
|
+
DWORD opts = 0;
|
|
733
|
+
pfd->lpVtbl->GetOptions(pfd, &opts);
|
|
734
|
+
pfd->lpVtbl->SetOptions(pfd, opts | FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM);
|
|
735
|
+
|
|
736
|
+
if (titleUtf8 && titleUtf8[0]) {
|
|
737
|
+
int len = MultiByteToWideChar(CP_UTF8, 0, titleUtf8, -1, NULL, 0);
|
|
738
|
+
wchar_t* wt = (wchar_t*)malloc(len * sizeof(wchar_t));
|
|
739
|
+
MultiByteToWideChar(CP_UTF8, 0, titleUtf8, -1, wt, len);
|
|
740
|
+
pfd->lpVtbl->SetTitle(pfd, wt);
|
|
741
|
+
free(wt);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (defaultPathUtf8 && defaultPathUtf8[0]) {
|
|
745
|
+
int len = MultiByteToWideChar(CP_UTF8, 0, defaultPathUtf8, -1, NULL, 0);
|
|
746
|
+
wchar_t* wp = (wchar_t*)malloc(len * sizeof(wchar_t));
|
|
747
|
+
MultiByteToWideChar(CP_UTF8, 0, defaultPathUtf8, -1, wp, len);
|
|
748
|
+
IShellItem* psi = NULL;
|
|
749
|
+
SHCreateItemFromParsingName(wp, NULL, &IID_IShellItem, (void**)&psi);
|
|
750
|
+
if (psi) {
|
|
751
|
+
pfd->lpVtbl->SetFolder(pfd, psi);
|
|
752
|
+
psi->lpVtbl->Release(psi);
|
|
753
|
+
}
|
|
754
|
+
free(wp);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
hr = pfd->lpVtbl->Show(pfd, NULL);
|
|
758
|
+
if (SUCCEEDED(hr)) {
|
|
759
|
+
IShellItem* psi = NULL;
|
|
760
|
+
pfd->lpVtbl->GetResult(pfd, &psi);
|
|
761
|
+
if (psi) {
|
|
762
|
+
PWSTR wPath = NULL;
|
|
763
|
+
psi->lpVtbl->GetDisplayName(psi, SIGDN_FILESYSPATH, &wPath);
|
|
764
|
+
if (wPath) {
|
|
765
|
+
char utf8[MAX_PATH * 4];
|
|
766
|
+
WideCharToMultiByte(CP_UTF8, 0, wPath, -1, utf8, sizeof(utf8), NULL, NULL);
|
|
767
|
+
CoTaskMemFree(wPath);
|
|
768
|
+
char escaped[MAX_PATH * 8];
|
|
769
|
+
zapp_json_escape_path(utf8, escaped, sizeof(escaped));
|
|
770
|
+
char response[MAX_PATH * 8 + 256];
|
|
771
|
+
snprintf(response, sizeof(response),
|
|
772
|
+
"{\"requestId\":\"%s\",\"ok\":true,\"paths\":[\"%s\"]}",
|
|
773
|
+
requestId, escaped);
|
|
774
|
+
zapp_dispatch_dialog_result(response);
|
|
775
|
+
}
|
|
776
|
+
psi->lpVtbl->Release(psi);
|
|
777
|
+
}
|
|
778
|
+
} else {
|
|
779
|
+
char response[256];
|
|
780
|
+
snprintf(response, sizeof(response),
|
|
781
|
+
"{\"requestId\":\"%s\",\"ok\":false,\"cancelled\":true}",
|
|
782
|
+
requestId);
|
|
783
|
+
zapp_dispatch_dialog_result(response);
|
|
784
|
+
}
|
|
785
|
+
pfd->lpVtbl->Release(pfd);
|
|
786
|
+
return 1;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// ---------------------------------------------------------------------------
|
|
790
|
+
// Build lpstrFilter from JSON filters array
|
|
791
|
+
// Format: "Description\0*.ext1;*.ext2\0...\0\0"
|
|
792
|
+
// ---------------------------------------------------------------------------
|
|
793
|
+
static wchar_t* zapp_build_filter_string(const char* json) {
|
|
794
|
+
// Parse the filters array from the JSON payload
|
|
795
|
+
char* filtersRaw = zapp_json_get_raw(json, "filters");
|
|
796
|
+
if (!filtersRaw || filtersRaw[0] != '[') { free(filtersRaw); return NULL; }
|
|
797
|
+
|
|
798
|
+
// Simple parser for [{"name":"X","extensions":["a","b"]}, ...]
|
|
799
|
+
// We build: Name\0*.a;*.b\0Name2\0*.c\0\0
|
|
800
|
+
static wchar_t buf[4096];
|
|
801
|
+
int pos = 0;
|
|
802
|
+
memset(buf, 0, sizeof(buf));
|
|
803
|
+
|
|
804
|
+
const char* p = filtersRaw + 1; // skip '['
|
|
805
|
+
while (*p) {
|
|
806
|
+
// Find next object
|
|
807
|
+
while (*p && *p != '{') p++;
|
|
808
|
+
if (!*p) break;
|
|
809
|
+
|
|
810
|
+
// Extract "name" value
|
|
811
|
+
const char* nameKey = strstr(p, "\"name\"");
|
|
812
|
+
char name[256] = "Files";
|
|
813
|
+
if (nameKey) {
|
|
814
|
+
nameKey = strchr(nameKey + 6, '"');
|
|
815
|
+
if (nameKey) {
|
|
816
|
+
nameKey++;
|
|
817
|
+
int ni = 0;
|
|
818
|
+
while (*nameKey && *nameKey != '"' && ni < 255) name[ni++] = *nameKey++;
|
|
819
|
+
name[ni] = '\0';
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Extract "extensions" array
|
|
824
|
+
const char* extKey = strstr(p, "\"extensions\"");
|
|
825
|
+
char extBuf[1024] = "";
|
|
826
|
+
int extPos = 0;
|
|
827
|
+
if (extKey) {
|
|
828
|
+
const char* arrStart = strchr(extKey + 12, '[');
|
|
829
|
+
if (arrStart) {
|
|
830
|
+
arrStart++;
|
|
831
|
+
while (*arrStart && *arrStart != ']') {
|
|
832
|
+
if (*arrStart == '"') {
|
|
833
|
+
arrStart++;
|
|
834
|
+
if (extPos > 0 && extPos < 1020) extBuf[extPos++] = ';';
|
|
835
|
+
extBuf[extPos++] = '*';
|
|
836
|
+
extBuf[extPos++] = '.';
|
|
837
|
+
while (*arrStart && *arrStart != '"' && extPos < 1020)
|
|
838
|
+
extBuf[extPos++] = *arrStart++;
|
|
839
|
+
if (*arrStart == '"') arrStart++;
|
|
840
|
+
} else {
|
|
841
|
+
arrStart++;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
extBuf[extPos] = '\0';
|
|
847
|
+
|
|
848
|
+
if (extPos > 0) {
|
|
849
|
+
// Write name
|
|
850
|
+
int nLen = MultiByteToWideChar(CP_UTF8, 0, name, -1, buf + pos, 4096 - pos);
|
|
851
|
+
pos += nLen; // nLen includes null terminator, which acts as separator
|
|
852
|
+
// Write extensions pattern
|
|
853
|
+
int eLen = MultiByteToWideChar(CP_UTF8, 0, extBuf, -1, buf + pos, 4096 - pos);
|
|
854
|
+
pos += eLen;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Skip to end of this object
|
|
858
|
+
int depth = 1;
|
|
859
|
+
p++;
|
|
860
|
+
while (*p && depth > 0) {
|
|
861
|
+
if (*p == '{') depth++;
|
|
862
|
+
else if (*p == '}') depth--;
|
|
863
|
+
p++;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
free(filtersRaw);
|
|
868
|
+
if (pos == 0) return NULL;
|
|
869
|
+
buf[pos] = L'\0'; // double-null terminate
|
|
870
|
+
return buf;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
void app_handle_dialog(App* app, char* action, char* payload_json) {
|
|
874
|
+
(void)app;
|
|
875
|
+
if (action == NULL || payload_json == NULL) return;
|
|
876
|
+
const char* json = (const char*)payload_json;
|
|
877
|
+
|
|
878
|
+
char* requestId = zapp_json_get_string(json, "requestId");
|
|
879
|
+
if (!requestId || requestId[0] == '\0') { free(requestId); return; }
|
|
880
|
+
|
|
881
|
+
if (strcmp(action, "openFile") == 0) {
|
|
882
|
+
int directory = zapp_json_get_bool(json, "directory", 0);
|
|
883
|
+
char* title = zapp_json_get_string(json, "title");
|
|
884
|
+
char* defaultPath = zapp_json_get_string(json, "defaultPath");
|
|
885
|
+
|
|
886
|
+
// --- Directory selection: use IFileDialog COM ---
|
|
887
|
+
if (directory) {
|
|
888
|
+
zapp_open_folder_dialog(title, defaultPath, requestId);
|
|
889
|
+
free(requestId);
|
|
890
|
+
if (title) free(title);
|
|
891
|
+
if (defaultPath) free(defaultPath);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// --- File selection ---
|
|
896
|
+
int multiple = zapp_json_get_bool(json, "multiple", 0);
|
|
897
|
+
|
|
898
|
+
OPENFILENAMEW ofn;
|
|
899
|
+
wchar_t szFile[MAX_PATH * 100] = {0};
|
|
900
|
+
ZeroMemory(&ofn, sizeof(ofn));
|
|
901
|
+
ofn.lStructSize = sizeof(ofn);
|
|
902
|
+
ofn.hwndOwner = NULL;
|
|
903
|
+
ofn.lpstrFile = szFile;
|
|
904
|
+
ofn.nMaxFile = sizeof(szFile) / sizeof(szFile[0]);
|
|
905
|
+
ofn.Flags = OFN_FILEMUSTEXIST | OFN_EXPLORER;
|
|
906
|
+
if (multiple) ofn.Flags |= OFN_ALLOWMULTISELECT;
|
|
907
|
+
|
|
908
|
+
// File type filters
|
|
909
|
+
wchar_t* filterStr = zapp_build_filter_string(json);
|
|
910
|
+
if (filterStr) {
|
|
911
|
+
ofn.lpstrFilter = filterStr;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Title
|
|
915
|
+
wchar_t wTitle[256] = {0};
|
|
916
|
+
if (title && title[0]) {
|
|
917
|
+
MultiByteToWideChar(CP_UTF8, 0, title, -1, wTitle, 256);
|
|
918
|
+
ofn.lpstrTitle = wTitle;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Default path
|
|
922
|
+
wchar_t wDefaultPath[MAX_PATH] = {0};
|
|
923
|
+
if (defaultPath && defaultPath[0]) {
|
|
924
|
+
MultiByteToWideChar(CP_UTF8, 0, defaultPath, -1, wDefaultPath, MAX_PATH);
|
|
925
|
+
ofn.lpstrInitialDir = wDefaultPath;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
if (GetOpenFileNameW(&ofn)) {
|
|
929
|
+
if (multiple) {
|
|
930
|
+
// Multi-select: buffer is dir\0file1\0file2\0\0
|
|
931
|
+
// Single-select: buffer is full-path\0\0
|
|
932
|
+
// If szFile[wcslen(szFile)+1] != 0, it's multi-select format
|
|
933
|
+
size_t dirLen = wcslen(szFile);
|
|
934
|
+
if (szFile[dirLen + 1] != L'\0') {
|
|
935
|
+
// Multi-select: parse dir + filenames
|
|
936
|
+
char response[MAX_PATH * 100 + 256];
|
|
937
|
+
int rpos = snprintf(response, sizeof(response),
|
|
938
|
+
"{\"requestId\":\"%s\",\"ok\":true,\"paths\":[", requestId);
|
|
939
|
+
const wchar_t* dir = szFile;
|
|
940
|
+
const wchar_t* fname = szFile + dirLen + 1;
|
|
941
|
+
int first = 1;
|
|
942
|
+
while (*fname) {
|
|
943
|
+
// Build full path: dir\fname
|
|
944
|
+
wchar_t fullPath[MAX_PATH * 2];
|
|
945
|
+
size_t dl = wcslen(dir);
|
|
946
|
+
// Add backslash separator if dir doesn't end with one
|
|
947
|
+
if (dl > 0 && dir[dl - 1] != L'\\' && dir[dl - 1] != L'/') {
|
|
948
|
+
swprintf(fullPath, sizeof(fullPath)/sizeof(fullPath[0]),
|
|
949
|
+
L"%s\\%s", dir, fname);
|
|
950
|
+
} else {
|
|
951
|
+
swprintf(fullPath, sizeof(fullPath)/sizeof(fullPath[0]),
|
|
952
|
+
L"%s%s", dir, fname);
|
|
953
|
+
}
|
|
954
|
+
char utf8[MAX_PATH * 4];
|
|
955
|
+
WideCharToMultiByte(CP_UTF8, 0, fullPath, -1, utf8, sizeof(utf8), NULL, NULL);
|
|
956
|
+
char escaped[MAX_PATH * 8];
|
|
957
|
+
zapp_json_escape_path(utf8, escaped, sizeof(escaped));
|
|
958
|
+
rpos += snprintf(response + rpos, sizeof(response) - rpos,
|
|
959
|
+
"%s\"%s\"", first ? "" : ",", escaped);
|
|
960
|
+
first = 0;
|
|
961
|
+
fname += wcslen(fname) + 1;
|
|
962
|
+
}
|
|
963
|
+
snprintf(response + rpos, sizeof(response) - rpos, "]}");
|
|
964
|
+
zapp_dispatch_dialog_result(response);
|
|
965
|
+
} else {
|
|
966
|
+
// Single file selected even with multi-select enabled
|
|
967
|
+
char utf8Path[MAX_PATH * 4];
|
|
968
|
+
WideCharToMultiByte(CP_UTF8, 0, szFile, -1, utf8Path, sizeof(utf8Path), NULL, NULL);
|
|
969
|
+
char escapedPath[MAX_PATH * 8];
|
|
970
|
+
zapp_json_escape_path(utf8Path, escapedPath, sizeof(escapedPath));
|
|
971
|
+
char response[MAX_PATH * 8 + 256];
|
|
972
|
+
snprintf(response, sizeof(response),
|
|
973
|
+
"{\"requestId\":\"%s\",\"ok\":true,\"paths\":[\"%s\"]}",
|
|
974
|
+
requestId, escapedPath);
|
|
975
|
+
zapp_dispatch_dialog_result(response);
|
|
976
|
+
}
|
|
977
|
+
} else {
|
|
978
|
+
// Single-select
|
|
979
|
+
char utf8Path[MAX_PATH * 4];
|
|
980
|
+
WideCharToMultiByte(CP_UTF8, 0, szFile, -1, utf8Path, sizeof(utf8Path), NULL, NULL);
|
|
981
|
+
char escapedPath[MAX_PATH * 8];
|
|
982
|
+
zapp_json_escape_path(utf8Path, escapedPath, sizeof(escapedPath));
|
|
983
|
+
char response[MAX_PATH * 8 + 256];
|
|
984
|
+
snprintf(response, sizeof(response),
|
|
985
|
+
"{\"requestId\":\"%s\",\"ok\":true,\"paths\":[\"%s\"]}",
|
|
986
|
+
requestId, escapedPath);
|
|
987
|
+
zapp_dispatch_dialog_result(response);
|
|
988
|
+
}
|
|
989
|
+
} else {
|
|
990
|
+
char response[256];
|
|
991
|
+
snprintf(response, sizeof(response),
|
|
992
|
+
"{\"requestId\":\"%s\",\"ok\":false,\"cancelled\":true}",
|
|
993
|
+
requestId);
|
|
994
|
+
zapp_dispatch_dialog_result(response);
|
|
995
|
+
}
|
|
996
|
+
free(requestId);
|
|
997
|
+
if (title) free(title);
|
|
998
|
+
if (defaultPath) free(defaultPath);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if (strcmp(action, "saveFile") == 0) {
|
|
1003
|
+
OPENFILENAMEW ofn;
|
|
1004
|
+
wchar_t szFile[MAX_PATH] = {0};
|
|
1005
|
+
ZeroMemory(&ofn, sizeof(ofn));
|
|
1006
|
+
ofn.lStructSize = sizeof(ofn);
|
|
1007
|
+
ofn.hwndOwner = NULL;
|
|
1008
|
+
ofn.lpstrFile = szFile;
|
|
1009
|
+
ofn.nMaxFile = sizeof(szFile) / sizeof(szFile[0]);
|
|
1010
|
+
ofn.Flags = OFN_OVERWRITEPROMPT | OFN_EXPLORER;
|
|
1011
|
+
|
|
1012
|
+
char* defaultName = zapp_json_get_string(json, "defaultName");
|
|
1013
|
+
if (defaultName && defaultName[0]) {
|
|
1014
|
+
MultiByteToWideChar(CP_UTF8, 0, defaultName, -1, szFile, MAX_PATH);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
char* title = zapp_json_get_string(json, "title");
|
|
1018
|
+
wchar_t wTitle[256] = {0};
|
|
1019
|
+
if (title && title[0]) {
|
|
1020
|
+
MultiByteToWideChar(CP_UTF8, 0, title, -1, wTitle, 256);
|
|
1021
|
+
ofn.lpstrTitle = wTitle;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
char* defaultPath = zapp_json_get_string(json, "defaultPath");
|
|
1025
|
+
wchar_t wDefaultPath[MAX_PATH] = {0};
|
|
1026
|
+
if (defaultPath && defaultPath[0]) {
|
|
1027
|
+
MultiByteToWideChar(CP_UTF8, 0, defaultPath, -1, wDefaultPath, MAX_PATH);
|
|
1028
|
+
ofn.lpstrInitialDir = wDefaultPath;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
if (GetSaveFileNameW(&ofn)) {
|
|
1032
|
+
char utf8Path[MAX_PATH * 4];
|
|
1033
|
+
WideCharToMultiByte(CP_UTF8, 0, szFile, -1, utf8Path, sizeof(utf8Path), NULL, NULL);
|
|
1034
|
+
char escapedPath[MAX_PATH * 8];
|
|
1035
|
+
zapp_json_escape_path(utf8Path, escapedPath, sizeof(escapedPath));
|
|
1036
|
+
char response[MAX_PATH * 8 + 256];
|
|
1037
|
+
snprintf(response, sizeof(response),
|
|
1038
|
+
"{\"requestId\":\"%s\",\"ok\":true,\"path\":\"%s\"}",
|
|
1039
|
+
requestId, escapedPath);
|
|
1040
|
+
zapp_dispatch_dialog_result(response);
|
|
1041
|
+
} else {
|
|
1042
|
+
char response[256];
|
|
1043
|
+
snprintf(response, sizeof(response),
|
|
1044
|
+
"{\"requestId\":\"%s\",\"ok\":false,\"cancelled\":true}",
|
|
1045
|
+
requestId);
|
|
1046
|
+
zapp_dispatch_dialog_result(response);
|
|
1047
|
+
}
|
|
1048
|
+
free(requestId);
|
|
1049
|
+
if (defaultName) free(defaultName);
|
|
1050
|
+
if (title) free(title);
|
|
1051
|
+
if (defaultPath) free(defaultPath);
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
if (strcmp(action, "message") == 0) {
|
|
1056
|
+
char* title = zapp_json_get_string(json, "title");
|
|
1057
|
+
char* message = zapp_json_get_string(json, "message");
|
|
1058
|
+
char* kind = zapp_json_get_string(json, "kind");
|
|
1059
|
+
char* buttonsRaw = zapp_json_get_raw(json, "buttons");
|
|
1060
|
+
|
|
1061
|
+
// Parse button labels from JSON array
|
|
1062
|
+
// Simple parser: find each quoted string in the array
|
|
1063
|
+
wchar_t* buttonLabels[32];
|
|
1064
|
+
int buttonCount = 0;
|
|
1065
|
+
if (buttonsRaw && buttonsRaw[0] == '[') {
|
|
1066
|
+
const char* p = buttonsRaw + 1;
|
|
1067
|
+
while (*p && buttonCount < 32) {
|
|
1068
|
+
// Skip whitespace and commas
|
|
1069
|
+
while (*p == ' ' || *p == ',' || *p == '\t' || *p == '\n' || *p == '\r') p++;
|
|
1070
|
+
if (*p == ']' || *p == '\0') break;
|
|
1071
|
+
if (*p == '"') {
|
|
1072
|
+
p++; // skip opening quote
|
|
1073
|
+
const char* start = p;
|
|
1074
|
+
while (*p && *p != '"') {
|
|
1075
|
+
if (*p == '\\' && *(p+1)) p++; // skip escaped char
|
|
1076
|
+
p++;
|
|
1077
|
+
}
|
|
1078
|
+
// Extract the string between start and p
|
|
1079
|
+
int len = (int)(p - start);
|
|
1080
|
+
char* label = (char*)malloc(len + 1);
|
|
1081
|
+
memcpy(label, start, len);
|
|
1082
|
+
label[len] = '\0';
|
|
1083
|
+
// Convert to wide
|
|
1084
|
+
int wlen = MultiByteToWideChar(CP_UTF8, 0, label, -1, NULL, 0);
|
|
1085
|
+
buttonLabels[buttonCount] = (wchar_t*)malloc(wlen * sizeof(wchar_t));
|
|
1086
|
+
MultiByteToWideChar(CP_UTF8, 0, label, -1, buttonLabels[buttonCount], wlen);
|
|
1087
|
+
free(label);
|
|
1088
|
+
buttonCount++;
|
|
1089
|
+
if (*p == '"') p++; // skip closing quote
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// Fallback: if no buttons, add "OK"
|
|
1095
|
+
if (buttonCount == 0) {
|
|
1096
|
+
buttonLabels[0] = _wcsdup(L"OK");
|
|
1097
|
+
buttonCount = 1;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Convert title and message to wide
|
|
1101
|
+
wchar_t wTitle[256] = {0};
|
|
1102
|
+
wchar_t wMessage[4096] = {0};
|
|
1103
|
+
if (title) MultiByteToWideChar(CP_UTF8, 0, title, -1, wTitle, 256);
|
|
1104
|
+
if (message) MultiByteToWideChar(CP_UTF8, 0, message, -1, wMessage, 4096);
|
|
1105
|
+
|
|
1106
|
+
// Build TASKDIALOG_BUTTON array with IDs 1000 + index
|
|
1107
|
+
TASKDIALOG_BUTTON* tdButtons = (TASKDIALOG_BUTTON*)calloc(buttonCount, sizeof(TASKDIALOG_BUTTON));
|
|
1108
|
+
for (int i = 0; i < buttonCount; i++) {
|
|
1109
|
+
tdButtons[i].nButtonID = 1000 + i;
|
|
1110
|
+
tdButtons[i].pszButtonText = buttonLabels[i];
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
TASKDIALOGCONFIG config = {0};
|
|
1114
|
+
config.cbSize = sizeof(config);
|
|
1115
|
+
config.hwndParent = NULL;
|
|
1116
|
+
config.dwFlags = 0;
|
|
1117
|
+
config.pszWindowTitle = wTitle;
|
|
1118
|
+
config.pszContent = wMessage;
|
|
1119
|
+
config.cButtons = buttonCount;
|
|
1120
|
+
config.pButtons = tdButtons;
|
|
1121
|
+
config.nDefaultButton = 1000;
|
|
1122
|
+
|
|
1123
|
+
// Set icon based on kind
|
|
1124
|
+
if (kind) {
|
|
1125
|
+
if (strcmp(kind, "warning") == 0) {
|
|
1126
|
+
config.pszMainIcon = TD_WARNING_ICON;
|
|
1127
|
+
} else if (strcmp(kind, "critical") == 0) {
|
|
1128
|
+
config.pszMainIcon = TD_ERROR_ICON;
|
|
1129
|
+
} else {
|
|
1130
|
+
config.pszMainIcon = TD_INFORMATION_ICON;
|
|
1131
|
+
}
|
|
1132
|
+
} else {
|
|
1133
|
+
config.pszMainIcon = TD_INFORMATION_ICON;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
int pressedButton = 0;
|
|
1137
|
+
HRESULT hr = TaskDialogIndirect(&config, &pressedButton, NULL, NULL);
|
|
1138
|
+
|
|
1139
|
+
int buttonIndex = 0;
|
|
1140
|
+
if (SUCCEEDED(hr)) {
|
|
1141
|
+
buttonIndex = pressedButton - 1000;
|
|
1142
|
+
if (buttonIndex < 0 || buttonIndex >= buttonCount) buttonIndex = 0;
|
|
1143
|
+
} else {
|
|
1144
|
+
// Fallback to MessageBoxW if TaskDialogIndirect fails
|
|
1145
|
+
UINT mbType = MB_OK | MB_ICONINFORMATION;
|
|
1146
|
+
if (kind) {
|
|
1147
|
+
if (strcmp(kind, "warning") == 0) mbType = MB_OK | MB_ICONWARNING;
|
|
1148
|
+
else if (strcmp(kind, "critical") == 0) mbType = MB_OK | MB_ICONERROR;
|
|
1149
|
+
}
|
|
1150
|
+
MessageBoxW(NULL, wMessage, wTitle, mbType);
|
|
1151
|
+
buttonIndex = 0;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// Clean up
|
|
1155
|
+
for (int i = 0; i < buttonCount; i++) free(buttonLabels[i]);
|
|
1156
|
+
free(tdButtons);
|
|
1157
|
+
|
|
1158
|
+
char response[256];
|
|
1159
|
+
snprintf(response, sizeof(response),
|
|
1160
|
+
"{\"requestId\":\"%s\",\"ok\":true,\"button\":%d}",
|
|
1161
|
+
requestId, buttonIndex);
|
|
1162
|
+
zapp_dispatch_dialog_result(response);
|
|
1163
|
+
|
|
1164
|
+
free(requestId);
|
|
1165
|
+
if (title) free(title);
|
|
1166
|
+
if (message) free(message);
|
|
1167
|
+
if (kind) free(kind);
|
|
1168
|
+
if (buttonsRaw) free(buttonsRaw);
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
free(requestId);
|
|
1173
|
+
}
|
|
1174
|
+
#endif
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
fn windows_window_get_size(window: void*) -> WindowSize {
|
|
1178
|
+
let s = WindowSize{ width: 0, height: 0 };
|
|
1179
|
+
raw {
|
|
1180
|
+
#ifdef _WIN32
|
|
1181
|
+
HWND hwnd = (HWND)window;
|
|
1182
|
+
if (hwnd && IsWindow(hwnd)) {
|
|
1183
|
+
RECT rc;
|
|
1184
|
+
GetClientRect(hwnd, &rc);
|
|
1185
|
+
s.width = rc.right - rc.left;
|
|
1186
|
+
s.height = rc.bottom - rc.top;
|
|
1187
|
+
}
|
|
1188
|
+
#endif
|
|
1189
|
+
}
|
|
1190
|
+
return s;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
fn windows_window_get_position(window: void*) -> WindowPosition {
|
|
1194
|
+
let p = WindowPosition{ x: 0, y: 0 };
|
|
1195
|
+
raw {
|
|
1196
|
+
#ifdef _WIN32
|
|
1197
|
+
HWND hwnd = (HWND)window;
|
|
1198
|
+
if (hwnd && IsWindow(hwnd)) {
|
|
1199
|
+
RECT rc;
|
|
1200
|
+
GetWindowRect(hwnd, &rc);
|
|
1201
|
+
p.x = rc.left;
|
|
1202
|
+
p.y = rc.top;
|
|
1203
|
+
}
|
|
1204
|
+
#endif
|
|
1205
|
+
}
|
|
1206
|
+
return p;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
fn windows_window_is_minimized(window: void*) -> bool {
|
|
1210
|
+
raw {
|
|
1211
|
+
#ifdef _WIN32
|
|
1212
|
+
HWND hwnd = (HWND)window;
|
|
1213
|
+
if (hwnd && IsWindow(hwnd)) return IsIconic(hwnd) ? true : false;
|
|
1214
|
+
#endif
|
|
1215
|
+
}
|
|
1216
|
+
return false;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
fn windows_window_is_maximized(window: void*) -> bool {
|
|
1220
|
+
raw {
|
|
1221
|
+
#ifdef _WIN32
|
|
1222
|
+
HWND hwnd = (HWND)window;
|
|
1223
|
+
if (hwnd && IsWindow(hwnd)) return IsZoomed(hwnd) ? true : false;
|
|
1224
|
+
#endif
|
|
1225
|
+
}
|
|
1226
|
+
return false;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
fn windows_window_is_fullscreen(window: void*) -> bool {
|
|
1230
|
+
raw {
|
|
1231
|
+
#ifdef _WIN32
|
|
1232
|
+
HWND hwnd = (HWND)window;
|
|
1233
|
+
if (hwnd && IsWindow(hwnd)) {
|
|
1234
|
+
LONG style = GetWindowLongW(hwnd, GWL_STYLE);
|
|
1235
|
+
return !(style & WS_OVERLAPPEDWINDOW) ? true : false;
|
|
1236
|
+
}
|
|
1237
|
+
#endif
|
|
1238
|
+
}
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
fn windows_window_force_close(window: void*) -> void {
|
|
1243
|
+
raw {
|
|
1244
|
+
#ifdef _WIN32
|
|
1245
|
+
HWND hwnd = (HWND)window;
|
|
1246
|
+
if (hwnd && IsWindow(hwnd)) {
|
|
1247
|
+
ZappWindowEntry* entry = zapp_window_entry_for_hwnd(hwnd);
|
|
1248
|
+
if (entry) entry->forceClose = TRUE;
|
|
1249
|
+
PostMessageW(hwnd, WM_CLOSE, 0, 0);
|
|
1250
|
+
}
|
|
1251
|
+
#endif
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
fn windows_window_register_numeric_id(window: void*, numeric_id: int) -> void {
|
|
1256
|
+
raw {
|
|
1257
|
+
#ifdef _WIN32
|
|
1258
|
+
if (window == NULL) return;
|
|
1259
|
+
HWND hwnd = (HWND)window;
|
|
1260
|
+
const char* existing = zapp_win_id_for_hwnd(hwnd);
|
|
1261
|
+
if (existing) {
|
|
1262
|
+
zapp_window_register_numeric_id_str(existing, numeric_id);
|
|
1263
|
+
} else {
|
|
1264
|
+
const char* regId = zapp_win_register(hwnd);
|
|
1265
|
+
if (regId) {
|
|
1266
|
+
zapp_window_register_numeric_id_str(regId, numeric_id);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
#endif
|
|
1270
|
+
}
|
|
1271
|
+
}
|