@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.
Files changed (209) hide show
  1. package/README.md +55 -0
  2. package/dist/zapp-cli.js +9471 -0
  3. package/native/src/app/app.zc +490 -0
  4. package/native/src/event/event.zc +24 -0
  5. package/native/src/event/events.zc +70 -0
  6. package/native/src/platform/darwin/backend.zc +923 -0
  7. package/native/src/platform/darwin/backend_bootstrap.zc +9 -0
  8. package/native/src/platform/darwin/bootstrap.zc +9 -0
  9. package/native/src/platform/darwin/engine_jsc.zc +86 -0
  10. package/native/src/platform/darwin/engine_qjs.zc +92 -0
  11. package/native/src/platform/darwin/platform.zc +156 -0
  12. package/native/src/platform/darwin/webview.zc +550 -0
  13. package/native/src/platform/darwin/webview_bootstrap.zc +9 -0
  14. package/native/src/platform/darwin/window.zc +1223 -0
  15. package/native/src/platform/darwin/worker/common.zc +223 -0
  16. package/native/src/platform/darwin/worker/core/base64_core.zc +29 -0
  17. package/native/src/platform/darwin/worker/core/crypto_core.zc +19 -0
  18. package/native/src/platform/darwin/worker/core/encoding_core.zc +32 -0
  19. package/native/src/platform/darwin/worker/core/fetch_core.zc +145 -0
  20. package/native/src/platform/darwin/worker/core/url_core.zc +69 -0
  21. package/native/src/platform/darwin/worker/core/websocket_core.zc +179 -0
  22. package/native/src/platform/darwin/worker/dispatch.zc +55 -0
  23. package/native/src/platform/darwin/worker/jsc/base64_jsc.zc +39 -0
  24. package/native/src/platform/darwin/worker/jsc/crypto_jsc.zc +49 -0
  25. package/native/src/platform/darwin/worker/jsc/encoding_jsc.zc +86 -0
  26. package/native/src/platform/darwin/worker/jsc/fetch_jsc.zc +149 -0
  27. package/native/src/platform/darwin/worker/jsc/url_jsc.zc +54 -0
  28. package/native/src/platform/darwin/worker/jsc/websocket_jsc.zc +127 -0
  29. package/native/src/platform/darwin/worker/jsc.zc +670 -0
  30. package/native/src/platform/darwin/worker/mod.zc +30 -0
  31. package/native/src/platform/darwin/worker/qjs/fetch_qjs.zc +233 -0
  32. package/native/src/platform/darwin/worker/qjs/qjs_macros.zc +23 -0
  33. package/native/src/platform/darwin/worker/qjs/websocket_qjs.zc +223 -0
  34. package/native/src/platform/darwin/worker/qjs.zc +1053 -0
  35. package/native/src/platform/darwin/worker/timers.zc +149 -0
  36. package/native/src/platform/darwin/worker/timers_qjs.zc +209 -0
  37. package/native/src/platform/platform.zc +64 -0
  38. package/native/src/platform/shared/log.zc +156 -0
  39. package/native/src/platform/shared/worker/qjs/base64_qjs.zc +38 -0
  40. package/native/src/platform/shared/worker/qjs/crypto_qjs.zc +44 -0
  41. package/native/src/platform/shared/worker/qjs/encoding_qjs.zc +95 -0
  42. package/native/src/platform/shared/worker/qjs/url_qjs.zc +65 -0
  43. package/native/src/platform/shared/worker_registry.zc +206 -0
  44. package/native/src/platform/window.zc +446 -0
  45. package/native/src/platform/windows/backend.zc +452 -0
  46. package/native/src/platform/windows/backend_bootstrap.zc +9 -0
  47. package/native/src/platform/windows/bootstrap.zc +9 -0
  48. package/native/src/platform/windows/engine_qjs.zc +60 -0
  49. package/native/src/platform/windows/platform.zc +387 -0
  50. package/native/src/platform/windows/webview.zc +1175 -0
  51. package/native/src/platform/windows/webview_bootstrap.zc +9 -0
  52. package/native/src/platform/windows/window.zc +1271 -0
  53. package/native/src/platform/windows/worker/common.zc +409 -0
  54. package/native/src/platform/windows/worker/core/base64_core.zc +52 -0
  55. package/native/src/platform/windows/worker/core/crypto_core.zc +34 -0
  56. package/native/src/platform/windows/worker/core/encoding_core.zc +60 -0
  57. package/native/src/platform/windows/worker/core/fetch_core.zc +274 -0
  58. package/native/src/platform/windows/worker/core/url_core.zc +216 -0
  59. package/native/src/platform/windows/worker/core/websocket_core.zc +343 -0
  60. package/native/src/platform/windows/worker/dispatch.zc +34 -0
  61. package/native/src/platform/windows/worker/mod.zc +46 -0
  62. package/native/src/platform/windows/worker/qjs/fetch_qjs.zc +255 -0
  63. package/native/src/platform/windows/worker/qjs/websocket_qjs.zc +263 -0
  64. package/native/src/platform/windows/worker/qjs.zc +1049 -0
  65. package/native/src/platform/windows/worker/timers_qjs.zc +288 -0
  66. package/native/src/platform/worker.zc +8 -0
  67. package/native/src/service/service.zc +228 -0
  68. package/native/vendor/quickjs-ng/.gitattributes +4 -0
  69. package/native/vendor/quickjs-ng/.github/dependabot.yml +7 -0
  70. package/native/vendor/quickjs-ng/.github/workflows/ci.yml +812 -0
  71. package/native/vendor/quickjs-ng/.github/workflows/docs.yml +49 -0
  72. package/native/vendor/quickjs-ng/.github/workflows/release.yml +162 -0
  73. package/native/vendor/quickjs-ng/.github/workflows/test-docs.yml +23 -0
  74. package/native/vendor/quickjs-ng/.github/workflows/tsan.yml +32 -0
  75. package/native/vendor/quickjs-ng/.github/workflows/valgrind.yml +33 -0
  76. package/native/vendor/quickjs-ng/.gitmodules +5 -0
  77. package/native/vendor/quickjs-ng/CMakeLists.txt +553 -0
  78. package/native/vendor/quickjs-ng/LICENSE +24 -0
  79. package/native/vendor/quickjs-ng/Makefile +149 -0
  80. package/native/vendor/quickjs-ng/amalgam.js +53 -0
  81. package/native/vendor/quickjs-ng/api-test.c +927 -0
  82. package/native/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
  83. package/native/vendor/quickjs-ng/builtin-array-fromasync.js +36 -0
  84. package/native/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
  85. package/native/vendor/quickjs-ng/builtin-iterator-zip-keyed.js +194 -0
  86. package/native/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
  87. package/native/vendor/quickjs-ng/builtin-iterator-zip.js +210 -0
  88. package/native/vendor/quickjs-ng/ctest.c +17 -0
  89. package/native/vendor/quickjs-ng/cutils.h +2013 -0
  90. package/native/vendor/quickjs-ng/cxxtest.cc +2 -0
  91. package/native/vendor/quickjs-ng/dtoa.c +1619 -0
  92. package/native/vendor/quickjs-ng/dtoa.h +87 -0
  93. package/native/vendor/quickjs-ng/examples/fib.c +67 -0
  94. package/native/vendor/quickjs-ng/examples/fib_module.js +10 -0
  95. package/native/vendor/quickjs-ng/examples/hello.js +1 -0
  96. package/native/vendor/quickjs-ng/examples/hello_module.js +6 -0
  97. package/native/vendor/quickjs-ng/examples/meson.build +17 -0
  98. package/native/vendor/quickjs-ng/examples/pi_bigint.js +118 -0
  99. package/native/vendor/quickjs-ng/examples/point.c +154 -0
  100. package/native/vendor/quickjs-ng/examples/test_fib.js +8 -0
  101. package/native/vendor/quickjs-ng/examples/test_point.js +43 -0
  102. package/native/vendor/quickjs-ng/fuzz.c +51 -0
  103. package/native/vendor/quickjs-ng/gen/function_source.c +81 -0
  104. package/native/vendor/quickjs-ng/gen/hello.c +53 -0
  105. package/native/vendor/quickjs-ng/gen/hello_module.c +106 -0
  106. package/native/vendor/quickjs-ng/gen/repl.c +3053 -0
  107. package/native/vendor/quickjs-ng/gen/standalone.c +324 -0
  108. package/native/vendor/quickjs-ng/gen/test_fib.c +81 -0
  109. package/native/vendor/quickjs-ng/libregexp-opcode.h +58 -0
  110. package/native/vendor/quickjs-ng/libregexp.c +2687 -0
  111. package/native/vendor/quickjs-ng/libregexp.h +98 -0
  112. package/native/vendor/quickjs-ng/libunicode-table.h +4707 -0
  113. package/native/vendor/quickjs-ng/libunicode.c +1746 -0
  114. package/native/vendor/quickjs-ng/libunicode.h +126 -0
  115. package/native/vendor/quickjs-ng/list.h +107 -0
  116. package/native/vendor/quickjs-ng/lre-test.c +73 -0
  117. package/native/vendor/quickjs-ng/meson.build +684 -0
  118. package/native/vendor/quickjs-ng/meson_options.txt +6 -0
  119. package/native/vendor/quickjs-ng/qjs-wasi-reactor.c +208 -0
  120. package/native/vendor/quickjs-ng/qjs.c +748 -0
  121. package/native/vendor/quickjs-ng/qjsc.c +673 -0
  122. package/native/vendor/quickjs-ng/quickjs-atom.h +267 -0
  123. package/native/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
  124. package/native/vendor/quickjs-ng/quickjs-libc.c +4986 -0
  125. package/native/vendor/quickjs-ng/quickjs-libc.h +79 -0
  126. package/native/vendor/quickjs-ng/quickjs-opcode.h +369 -0
  127. package/native/vendor/quickjs-ng/quickjs.c +60259 -0
  128. package/native/vendor/quickjs-ng/quickjs.h +1419 -0
  129. package/native/vendor/quickjs-ng/repl.js +1927 -0
  130. package/native/vendor/quickjs-ng/run-test262.c +2417 -0
  131. package/native/vendor/quickjs-ng/standalone.js +129 -0
  132. package/native/vendor/quickjs-ng/tests/assert.js +49 -0
  133. package/native/vendor/quickjs-ng/tests/bug1221.js +16 -0
  134. package/native/vendor/quickjs-ng/tests/bug1296.js +12 -0
  135. package/native/vendor/quickjs-ng/tests/bug1297.js +22 -0
  136. package/native/vendor/quickjs-ng/tests/bug1301.js +21 -0
  137. package/native/vendor/quickjs-ng/tests/bug1302.js +24 -0
  138. package/native/vendor/quickjs-ng/tests/bug1305.js +26 -0
  139. package/native/vendor/quickjs-ng/tests/bug1318.js +54 -0
  140. package/native/vendor/quickjs-ng/tests/bug1352.js +8 -0
  141. package/native/vendor/quickjs-ng/tests/bug1354.js +6 -0
  142. package/native/vendor/quickjs-ng/tests/bug1355.js +58 -0
  143. package/native/vendor/quickjs-ng/tests/bug1368.js +9 -0
  144. package/native/vendor/quickjs-ng/tests/bug39/1.js +6 -0
  145. package/native/vendor/quickjs-ng/tests/bug39/2.js +6 -0
  146. package/native/vendor/quickjs-ng/tests/bug39/3.js +7 -0
  147. package/native/vendor/quickjs-ng/tests/bug488-upstream.js +7 -0
  148. package/native/vendor/quickjs-ng/tests/bug633/0.js +7 -0
  149. package/native/vendor/quickjs-ng/tests/bug633/1.js +4 -0
  150. package/native/vendor/quickjs-ng/tests/bug633/2.js +4 -0
  151. package/native/vendor/quickjs-ng/tests/bug633/3.js +4 -0
  152. package/native/vendor/quickjs-ng/tests/bug645/0.js +4 -0
  153. package/native/vendor/quickjs-ng/tests/bug645/1.js +9 -0
  154. package/native/vendor/quickjs-ng/tests/bug645/2.js +7 -0
  155. package/native/vendor/quickjs-ng/tests/bug648.js +13 -0
  156. package/native/vendor/quickjs-ng/tests/bug652.js +4 -0
  157. package/native/vendor/quickjs-ng/tests/bug741.js +19 -0
  158. package/native/vendor/quickjs-ng/tests/bug775.js +7 -0
  159. package/native/vendor/quickjs-ng/tests/bug776.js +7 -0
  160. package/native/vendor/quickjs-ng/tests/bug832.js +2 -0
  161. package/native/vendor/quickjs-ng/tests/bug858.js +26 -0
  162. package/native/vendor/quickjs-ng/tests/bug904.js +6 -0
  163. package/native/vendor/quickjs-ng/tests/bug988.js +7 -0
  164. package/native/vendor/quickjs-ng/tests/bug999.js +3 -0
  165. package/native/vendor/quickjs-ng/tests/destructured-export.js +8 -0
  166. package/native/vendor/quickjs-ng/tests/detect_module/0.js +1 -0
  167. package/native/vendor/quickjs-ng/tests/detect_module/1.js +2 -0
  168. package/native/vendor/quickjs-ng/tests/detect_module/2.js +1 -0
  169. package/native/vendor/quickjs-ng/tests/detect_module/3.js +8 -0
  170. package/native/vendor/quickjs-ng/tests/detect_module/4.js +3 -0
  171. package/native/vendor/quickjs-ng/tests/empty.js +0 -0
  172. package/native/vendor/quickjs-ng/tests/fixture_cyclic_import.js +2 -0
  173. package/native/vendor/quickjs-ng/tests/fixture_string_exports.js +12 -0
  174. package/native/vendor/quickjs-ng/tests/function_source.js +14 -0
  175. package/native/vendor/quickjs-ng/tests/microbench.js +1267 -0
  176. package/native/vendor/quickjs-ng/tests/null_or_undefined.js +38 -0
  177. package/native/vendor/quickjs-ng/tests/str-pad-leak.js +5 -0
  178. package/native/vendor/quickjs-ng/tests/test_bigint.js +107 -0
  179. package/native/vendor/quickjs-ng/tests/test_bjson.js +366 -0
  180. package/native/vendor/quickjs-ng/tests/test_builtin.js +1314 -0
  181. package/native/vendor/quickjs-ng/tests/test_closure.js +220 -0
  182. package/native/vendor/quickjs-ng/tests/test_cyclic_import.js +12 -0
  183. package/native/vendor/quickjs-ng/tests/test_domexception.js +35 -0
  184. package/native/vendor/quickjs-ng/tests/test_language.js +755 -0
  185. package/native/vendor/quickjs-ng/tests/test_loop.js +367 -0
  186. package/native/vendor/quickjs-ng/tests/test_queue_microtask.js +39 -0
  187. package/native/vendor/quickjs-ng/tests/test_std.js +340 -0
  188. package/native/vendor/quickjs-ng/tests/test_string_exports.js +25 -0
  189. package/native/vendor/quickjs-ng/tests/test_worker.js +43 -0
  190. package/native/vendor/quickjs-ng/tests/test_worker_module.js +30 -0
  191. package/native/vendor/quickjs-ng/tests.conf +14 -0
  192. package/native/vendor/quickjs-ng/unicode_download.sh +19 -0
  193. package/native/vendor/quickjs-ng/unicode_gen.c +3108 -0
  194. package/native/vendor/quickjs-ng/unicode_gen_def.h +310 -0
  195. package/native/vendor/quickjs-ng/update-version.sh +32 -0
  196. package/native/vendor/webview2/include/WebView2.h +60636 -0
  197. package/native/vendor/webview2/include/WebView2EnvironmentOptions.h +406 -0
  198. package/package.json +33 -0
  199. package/src/backend.ts +139 -0
  200. package/src/build-config.ts +87 -0
  201. package/src/build.ts +276 -0
  202. package/src/common.ts +195 -0
  203. package/src/config.ts +89 -0
  204. package/src/dev.ts +164 -0
  205. package/src/generate.ts +200 -0
  206. package/src/icons.ts +116 -0
  207. package/src/init.ts +190 -0
  208. package/src/package.ts +150 -0
  209. 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
+ }