windowpp 0.1.1

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 (88) hide show
  1. package/bin/windowpp.js +86 -0
  2. package/cmake/embed_assets.py +144 -0
  3. package/framework/CMakeLists.txt +176 -0
  4. package/framework/include/windowpp/windowpp.h +704 -0
  5. package/framework/src/AppData/API/AppData.ts +137 -0
  6. package/framework/src/AppData/appdata_bridge.h +138 -0
  7. package/framework/src/AppData/appdata_manager.cpp +126 -0
  8. package/framework/src/AppData/appdata_manager.h +3 -0
  9. package/framework/src/FileSystem/API/FileSystem.ts +389 -0
  10. package/framework/src/FileSystem/Linux/filesearch.cpp +148 -0
  11. package/framework/src/FileSystem/Linux/readfile.cpp +79 -0
  12. package/framework/src/FileSystem/Linux/savefile.cpp +333 -0
  13. package/framework/src/FileSystem/MacOS/filesearch.cpp +149 -0
  14. package/framework/src/FileSystem/MacOS/readfile.cpp +80 -0
  15. package/framework/src/FileSystem/MacOS/savefile.cpp +264 -0
  16. package/framework/src/FileSystem/Windows/filesearch.cpp +195 -0
  17. package/framework/src/FileSystem/Windows/readfile.cpp +122 -0
  18. package/framework/src/FileSystem/Windows/savefile.cpp +290 -0
  19. package/framework/src/FileSystem/file_index_service.cpp +262 -0
  20. package/framework/src/FileSystem/file_index_service.h +55 -0
  21. package/framework/src/FileSystem/filesystem_bridge.h +243 -0
  22. package/framework/src/FileSystem/filesystem_handler.h +93 -0
  23. package/framework/src/FileSystem/filesystem_json.h +241 -0
  24. package/framework/src/FileSystem/filesystem_search_service.cpp +414 -0
  25. package/framework/src/FileSystem/filesystem_search_service.h +94 -0
  26. package/framework/src/Input/API/Input.ts +161 -0
  27. package/framework/src/Input/Linux/linux_key_utils.h +135 -0
  28. package/framework/src/Input/MacOS/macos_key_utils.h +137 -0
  29. package/framework/src/Input/Windows/win32_key_utils.h +199 -0
  30. package/framework/src/Input/input_bridge.h +192 -0
  31. package/framework/src/Input/input_service.cpp +584 -0
  32. package/framework/src/Input/input_service.h +21 -0
  33. package/framework/src/application.cpp +29 -0
  34. package/framework/src/common/hit_test.cpp +40 -0
  35. package/framework/src/common/image_loader.cpp +24 -0
  36. package/framework/src/common/paths.cpp +75 -0
  37. package/framework/src/filedrop/filedrop.cpp +316 -0
  38. package/framework/src/filedrop/filedrop.css +421 -0
  39. package/framework/src/filedrop/filedrop.hpp +92 -0
  40. package/framework/src/filedrop/filedrop.ts +183 -0
  41. package/framework/src/platform/API/App.ts +156 -0
  42. package/framework/src/platform/API/Window.ts +249 -0
  43. package/framework/src/platform/linux/app_linux.cpp +256 -0
  44. package/framework/src/platform/linux/app_linux.h +64 -0
  45. package/framework/src/platform/linux/linux_helpers.cpp +26 -0
  46. package/framework/src/platform/linux/linux_helpers.h +19 -0
  47. package/framework/src/platform/linux/tray_linux.cpp +21 -0
  48. package/framework/src/platform/linux/tray_linux.h +26 -0
  49. package/framework/src/platform/linux/window_linux.cpp +256 -0
  50. package/framework/src/platform/linux/window_linux.h +70 -0
  51. package/framework/src/platform/macos/app_macos.h +59 -0
  52. package/framework/src/platform/macos/app_macos.mm +223 -0
  53. package/framework/src/platform/macos/macos_helpers.h +21 -0
  54. package/framework/src/platform/macos/tray_macos.h +22 -0
  55. package/framework/src/platform/macos/tray_macos.mm +53 -0
  56. package/framework/src/platform/macos/window_macos.h +74 -0
  57. package/framework/src/platform/macos/window_macos.mm +318 -0
  58. package/framework/src/platform/platform_bridge.h +514 -0
  59. package/framework/src/platform/platform_factory.cpp +33 -0
  60. package/framework/src/platform/platform_factory.h +19 -0
  61. package/framework/src/platform/win32/app_win32.cpp +572 -0
  62. package/framework/src/platform/win32/app_win32.h +83 -0
  63. package/framework/src/platform/win32/tray_win32.cpp +57 -0
  64. package/framework/src/platform/win32/tray_win32.h +30 -0
  65. package/framework/src/platform/win32/win32_helpers.h +61 -0
  66. package/framework/src/platform/win32/window_win32.cpp +267 -0
  67. package/framework/src/platform/win32/window_win32.h +79 -0
  68. package/framework/src/renderer/webgpu.h +128 -0
  69. package/framework/src/renderer/webview/include/WebView2.h +48014 -0
  70. package/framework/src/renderer/webview/include/WebView2EnvironmentOptions.h +342 -0
  71. package/framework/src/renderer/webview/webview.h +13 -0
  72. package/framework/src/renderer/webview/webview_linux.cpp +392 -0
  73. package/framework/src/renderer/webview/webview_macos.mm +388 -0
  74. package/framework/src/renderer/webview/webview_win32.cpp +688 -0
  75. package/framework/src/renderer/webview/x64/WebView2Loader.dll +0 -0
  76. package/framework/src/renderer/webview/x64/WebView2Loader.lib +0 -0
  77. package/framework/src/renderer/webview/x64/WebView2LoaderStatic.lib +0 -0
  78. package/lib/build.js +112 -0
  79. package/lib/create.js +283 -0
  80. package/lib/dev.js +155 -0
  81. package/package.json +24 -0
  82. package/scripts/publish.js +67 -0
  83. package/scripts/sync-framework.js +73 -0
  84. package/templates/solid/CMakeLists.txt +56 -0
  85. package/templates/solid/frontend/package.json +22 -0
  86. package/templates/solid/frontend/vite.config.ts +25 -0
  87. package/templates/solid/main.cpp +72 -0
  88. package/templates/solid/package.json +12 -0
@@ -0,0 +1,688 @@
1
+ // ============================================================================
2
+ // webview_win32.cpp — Win32 WebView2 implementation
3
+ // ============================================================================
4
+
5
+ #if defined(WIN32)
6
+
7
+ #define WPP_HAS_WEBVIEW2 1
8
+ #include "webview.h"
9
+ #include "../../Input/input_service.h"
10
+ #include "../../Input/Windows/win32_key_utils.h"
11
+ #include "../../platform/win32/window_win32.h"
12
+ #include <windows.h>
13
+ #include <wrl.h>
14
+ #include <wrl/client.h>
15
+ #include <WebView2.h>
16
+ #include <cctype>
17
+ #include <string>
18
+ #include <memory>
19
+ #include <codecvt>
20
+ #include <nlohmann/json.hpp>
21
+ #include <shlwapi.h>
22
+ #pragma comment(lib, "shlwapi.lib")
23
+
24
+ namespace wpp {
25
+
26
+ using namespace Microsoft::WRL;
27
+
28
+ namespace {
29
+
30
+ using json = nlohmann::json;
31
+
32
+ } // namespace
33
+
34
+ class Win32WebView : public WebView {
35
+ public:
36
+ Win32WebView(const WebViewConfig& config, HWND parent_hwnd)
37
+ : config_(config), parent_hwnd_(parent_hwnd) {
38
+
39
+ RECT rc;
40
+ GetClientRect(parent_hwnd_, &rc);
41
+ int inset = config_.frameless ? 1 : 0;
42
+ int host_width = rc.right - rc.left - (inset * 2);
43
+ int host_height = rc.bottom - rc.top - (inset * 2);
44
+ if (host_width < 1) host_width = 1;
45
+ if (host_height < 1) host_height = 1;
46
+
47
+ webview_host_hwnd_ = CreateWindowExW(
48
+ 0,
49
+ L"STATIC",
50
+ L"",
51
+ WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
52
+ inset,
53
+ inset,
54
+ host_width,
55
+ host_height,
56
+ parent_hwnd_,
57
+ nullptr,
58
+ GetModuleHandleW(nullptr),
59
+ nullptr);
60
+
61
+ // Convert user_data_dir to wide string (empty = let WebView2 pick default)
62
+ std::wstring wUserDataDir;
63
+ if (!config_.user_data_dir.empty()) {
64
+ int len = MultiByteToWideChar(CP_UTF8, 0,
65
+ config_.user_data_dir.c_str(), -1, nullptr, 0);
66
+ if (len > 0) {
67
+ wUserDataDir.resize(len - 1);
68
+ MultiByteToWideChar(CP_UTF8, 0,
69
+ config_.user_data_dir.c_str(), -1, &wUserDataDir[0], len);
70
+ }
71
+ }
72
+
73
+ HRESULT hr = CreateCoreWebView2EnvironmentWithOptions(
74
+ nullptr,
75
+ wUserDataDir.empty() ? nullptr : wUserDataDir.c_str(),
76
+ nullptr,
77
+ Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
78
+ [this, rc](HRESULT result, ICoreWebView2Environment* env) -> HRESULT {
79
+ if (SUCCEEDED(result) && env) {
80
+ env_ = env;
81
+ HRESULT hr = env_->CreateCoreWebView2Controller(
82
+ webview_host_hwnd_ ? webview_host_hwnd_ : parent_hwnd_,
83
+ Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
84
+ [this, rc](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT {
85
+ if (SUCCEEDED(result) && controller) {
86
+ controller_ = controller;
87
+ controller_->get_CoreWebView2(&webview_);
88
+
89
+ controller_->put_IsVisible(TRUE);
90
+
91
+ int inset = config_.frameless ? 1 : 0;
92
+ RECT bounds = {0, 0, rc.right - rc.left - (inset * 2), rc.bottom - rc.top - (inset * 2)};
93
+ if (bounds.right <= 0) bounds.right = 1;
94
+ if (bounds.bottom <= 0) bounds.bottom = 1;
95
+ controller_->put_Bounds(bounds);
96
+
97
+ if (config_.frameless) {
98
+ ComPtr<ICoreWebView2Controller2> controller2;
99
+ controller->QueryInterface(IID_PPV_ARGS(&controller2));
100
+ if (controller2) {
101
+ COREWEBVIEW2_COLOR transparent = {0, 0, 0, 0};
102
+ controller2->put_DefaultBackgroundColor(transparent);
103
+ }
104
+ }
105
+
106
+ EventRegistrationToken token;
107
+ webview_->add_NavigationCompleted(
108
+ Callback<ICoreWebView2NavigationCompletedEventHandler>(
109
+ [this](ICoreWebView2*, ICoreWebView2NavigationCompletedEventArgs*) -> HRESULT {
110
+ if (config_.on_load_finished) {
111
+ config_.on_load_finished("load_finished");
112
+ }
113
+ return S_OK;
114
+ }).Get(), &token);
115
+
116
+ webview_->add_WebMessageReceived(
117
+ Callback<ICoreWebView2WebMessageReceivedEventHandler>(
118
+ [this](ICoreWebView2*, ICoreWebView2WebMessageReceivedEventArgs* args) -> HRESULT {
119
+ LPWSTR message = nullptr;
120
+ if (SUCCEEDED(args->TryGetWebMessageAsString(&message)) && message) {
121
+ std::wstring wmsg(message);
122
+ CoTaskMemFree(message);
123
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
124
+ std::string msg = converter.to_bytes(wmsg);
125
+ if (handle_file_drop_message(msg)) {
126
+ return S_OK;
127
+ }
128
+ if (config_.on_message) {
129
+ config_.on_message(msg);
130
+ }
131
+ }
132
+ return S_OK;
133
+ }).Get(), &token);
134
+
135
+ controller_->add_AcceleratorKeyPressed(
136
+ Callback<ICoreWebView2AcceleratorKeyPressedEventHandler>(
137
+ [this](ICoreWebView2Controller*, ICoreWebView2AcceleratorKeyPressedEventArgs* args) -> HRESULT {
138
+ if (!parent_hwnd_) {
139
+ return S_OK;
140
+ }
141
+
142
+ auto* win = wpp::Win32Window::from_hwnd(parent_hwnd_);
143
+ if (!win) {
144
+ return S_OK;
145
+ }
146
+
147
+ COREWEBVIEW2_KEY_EVENT_KIND kind = COREWEBVIEW2_KEY_EVENT_KIND_KEY_DOWN;
148
+ UINT virtual_key = 0;
149
+ INT lparam = 0;
150
+ args->get_KeyEventKind(&kind);
151
+ args->get_VirtualKey(&virtual_key);
152
+ args->get_KeyEventLParam(&lparam);
153
+
154
+ const UINT message =
155
+ kind == COREWEBVIEW2_KEY_EVENT_KIND_KEY_UP
156
+ ? WM_KEYUP
157
+ : kind == COREWEBVIEW2_KEY_EVENT_KIND_SYSTEM_KEY_UP
158
+ ? WM_SYSKEYUP
159
+ : kind == COREWEBVIEW2_KEY_EVENT_KIND_SYSTEM_KEY_DOWN
160
+ ? WM_SYSKEYDOWN
161
+ : WM_KEYDOWN;
162
+
163
+ auto event = input::win32::make_key_event(
164
+ message,
165
+ static_cast<WPARAM>(virtual_key),
166
+ static_cast<LPARAM>(lparam),
167
+ win->id());
168
+
169
+ input::detail::emit_key_event(event);
170
+
171
+ if (event.type == input::KeyEventType::KeyDown) {
172
+ if (win->config().on_key_down) {
173
+ win->config().on_key_down(event);
174
+ }
175
+ } else if (win->config().on_key_up) {
176
+ win->config().on_key_up(event);
177
+ }
178
+ return S_OK;
179
+ }).Get(),
180
+ &token);
181
+
182
+ if (!config_.scrollbars) {
183
+ std::string hideScrollbarScript = R"(
184
+ (function() {
185
+ var css = "html, body { overflow: auto !important; height: 100% !important; margin: 0 !important; padding: 0 !important; } ::-webkit-scrollbar{display:none!important;width:0!important;height:0!important;}::-webkit-scrollbar-track{background:transparent!important;}::-webkit-scrollbar-thumb{background:transparent!important;}*{-ms-overflow-style:none!important;scrollbar-width:none!important;scrollbar-color:transparent transparent!important;}";
186
+ var styleId = "__wpp_hide_scrollbars";
187
+ var ensureStyle = function() {
188
+ if (document.getElementById(styleId)) return;
189
+ var style = document.createElement("style");
190
+ style.id = styleId;
191
+ style.type = "text/css";
192
+ style.appendChild(document.createTextNode(css));
193
+ var container = document.head || document.documentElement || document.body;
194
+ if (container) {
195
+ container.appendChild(style);
196
+ }
197
+ };
198
+ ensureStyle();
199
+ document.addEventListener("DOMContentLoaded", ensureStyle);
200
+ window.addEventListener("load", ensureStyle);
201
+ })();
202
+ )";
203
+ webview_->AddScriptToExecuteOnDocumentCreated(
204
+ std::wstring(hideScrollbarScript.begin(), hideScrollbarScript.end()).c_str(),
205
+ nullptr);
206
+ }
207
+
208
+ std::string dropzoneScript = R"(
209
+ (function() {
210
+ if (window.__wpp_dropzone_init) return;
211
+ window.__wpp_dropzone_init = true;
212
+ window.__wpp_dropzone_enabled = true;
213
+
214
+ var activeZone = null;
215
+ var pendingZone = null;
216
+ var cachedZones = [];
217
+ var outerBounds = null;
218
+ var refreshPending = false;
219
+ var framePending = false;
220
+ var _lastHit = null;
221
+ var observedZones = [];
222
+ var resizeObserver = typeof ResizeObserver === 'function'
223
+ ? new ResizeObserver(function() { scheduleRefreshZones(); })
224
+ : null;
225
+
226
+ var disconnectObservedZones = function() {
227
+ if (!resizeObserver) return;
228
+ for (var i = 0; i < observedZones.length; i++) {
229
+ resizeObserver.unobserve(observedZones[i]);
230
+ }
231
+ observedZones = [];
232
+ };
233
+
234
+ var refreshZones = function() {
235
+ refreshPending = false;
236
+ _lastHit = null;
237
+ disconnectObservedZones();
238
+ cachedZones = [];
239
+ outerBounds = null;
240
+ var zones = document.querySelectorAll('[data-dropzone]');
241
+ for (var i = 0; i < zones.length; i++) {
242
+ var zone = zones[i];
243
+ var rect = zone.getBoundingClientRect();
244
+ var zoneData = {
245
+ element: zone,
246
+ left: rect.left,
247
+ top: rect.top,
248
+ right: rect.right,
249
+ bottom: rect.bottom
250
+ };
251
+ cachedZones.push(zoneData);
252
+ if (resizeObserver) {
253
+ resizeObserver.observe(zone);
254
+ observedZones.push(zone);
255
+ }
256
+ if (!outerBounds) {
257
+ outerBounds = {left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom};
258
+ } else {
259
+ outerBounds.left = Math.min(outerBounds.left, rect.left);
260
+ outerBounds.top = Math.min(outerBounds.top, rect.top);
261
+ outerBounds.right = Math.max(outerBounds.right, rect.right);
262
+ outerBounds.bottom = Math.max(outerBounds.bottom, rect.bottom);
263
+ }
264
+ }
265
+ };
266
+
267
+ var scheduleRefreshZones = function() {
268
+ if (refreshPending) return;
269
+ refreshPending = true;
270
+ window.requestAnimationFrame(refreshZones);
271
+ };
272
+
273
+ var findDropZoneAt = function(x, y) {
274
+ if (!outerBounds || x < outerBounds.left || x > outerBounds.right || y < outerBounds.top || y > outerBounds.bottom) {
275
+ return null;
276
+ }
277
+ if (_lastHit && x >= _lastHit.left && x <= _lastHit.right && y >= _lastHit.top && y <= _lastHit.bottom) {
278
+ return _lastHit.element;
279
+ }
280
+ for (var i = cachedZones.length - 1; i >= 0; i--) {
281
+ var zone = cachedZones[i];
282
+ if (x >= zone.left && x <= zone.right && y >= zone.top && y <= zone.bottom) {
283
+ _lastHit = zone;
284
+ return zone.element;
285
+ }
286
+ }
287
+ _lastHit = null;
288
+ return null;
289
+ };
290
+
291
+ var updateActiveZone = function(zone) {
292
+ if (activeZone === zone) return;
293
+ if (activeZone) {
294
+ activeZone.classList.remove('wpp-dropzone-active');
295
+ }
296
+ activeZone = zone;
297
+ if (activeZone) {
298
+ activeZone.classList.add('wpp-dropzone-active');
299
+ }
300
+ };
301
+
302
+ var flushActiveZone = function() {
303
+ framePending = false;
304
+ updateActiveZone(window.__wpp_dropzone_enabled ? pendingZone : null);
305
+ };
306
+
307
+ var scheduleZoneUpdate = function(zone) {
308
+ pendingZone = zone;
309
+ if (framePending) return;
310
+ framePending = true;
311
+ window.requestAnimationFrame(flushActiveZone);
312
+ };
313
+
314
+ var setDropEffect = function(e, zone) {
315
+ if (!e.dataTransfer) return;
316
+ try {
317
+ e.dataTransfer.dropEffect = zone ? 'copy' : 'none';
318
+ } catch (_) {}
319
+ };
320
+
321
+ document.addEventListener('dragenter', function(e) {
322
+ if (!window.__wpp_dropzone_enabled) return;
323
+ e.preventDefault();
324
+ if (!cachedZones.length) refreshZones();
325
+ var zone = findDropZoneAt(e.clientX, e.clientY);
326
+ setDropEffect(e, zone);
327
+ scheduleZoneUpdate(zone);
328
+ }, true);
329
+
330
+ document.addEventListener('dragover', function(e) {
331
+ if (!window.__wpp_dropzone_enabled) return;
332
+ e.preventDefault();
333
+ var zone = findDropZoneAt(e.clientX, e.clientY);
334
+ setDropEffect(e, zone);
335
+ scheduleZoneUpdate(zone);
336
+ }, true);
337
+
338
+ document.addEventListener('dragleave', function(e) {
339
+ if (!window.__wpp_dropzone_enabled) return;
340
+ e.preventDefault();
341
+ var related = e.relatedTarget;
342
+ if (!related || related === document.documentElement || related === document.body) {
343
+ pendingZone = null;
344
+ framePending = false;
345
+ _lastHit = null;
346
+ updateActiveZone(null);
347
+ }
348
+ }, true);
349
+
350
+ document.addEventListener('drop', function(e) {
351
+ if (!window.__wpp_dropzone_enabled) return;
352
+ e.preventDefault();
353
+ pendingZone = null;
354
+ framePending = false;
355
+ var zone = findDropZoneAt(e.clientX, e.clientY);
356
+ var zoneName = zone ? zone.getAttribute('data-dropzone') : null;
357
+ _lastHit = null;
358
+ updateActiveZone(null);
359
+
360
+ if (zoneName && window.__wpp_fileDrop__) {
361
+ var files = [];
362
+ if (e.dataTransfer && e.dataTransfer.files) {
363
+ for (var i = 0; i < e.dataTransfer.files.length; i++) {
364
+ var file = e.dataTransfer.files[i];
365
+ files.push({
366
+ name: file.name,
367
+ size: file.size,
368
+ type: file.type || 'application/octet-stream'
369
+ });
370
+ }
371
+ }
372
+ window.__wpp_fileDrop__(zoneName, files);
373
+ }
374
+ }, true);
375
+ window.addEventListener('dragover', function(e) {
376
+ if (!window.__wpp_dropzone_enabled) return;
377
+ e.preventDefault();
378
+ setDropEffect(e, findDropZoneAt(e.clientX, e.clientY));
379
+ }, true);
380
+ window.addEventListener('drop', function(e) {
381
+ if (!window.__wpp_dropzone_enabled) return;
382
+ e.preventDefault();
383
+ }, true);
384
+ window.addEventListener('resize', scheduleRefreshZones, true);
385
+ window.addEventListener('scroll', scheduleRefreshZones, true);
386
+ document.addEventListener('DOMContentLoaded', scheduleRefreshZones, true);
387
+ window.addEventListener('load', scheduleRefreshZones, true);
388
+ var attachMutationObserver = function() {
389
+ if (typeof MutationObserver !== 'function') return;
390
+ var target = document.documentElement || document.body;
391
+ if (!target || typeof target.nodeType !== 'number') return;
392
+ new MutationObserver(scheduleRefreshZones).observe(
393
+ target,
394
+ { childList: true, subtree: true, attributes: true, attributeFilter: ['data-dropzone'] }
395
+ );
396
+ };
397
+ if (typeof MutationObserver === 'function') {
398
+ attachMutationObserver();
399
+ document.addEventListener('DOMContentLoaded', attachMutationObserver, { once: true });
400
+ window.addEventListener('load', attachMutationObserver, { once: true });
401
+ }
402
+ refreshZones();
403
+ })();
404
+ )";
405
+
406
+ webview_->AddScriptToExecuteOnDocumentCreated(
407
+ std::wstring(dropzoneScript.begin(), dropzoneScript.end()).c_str(),
408
+ nullptr);
409
+
410
+ if (config_.file_drop) {
411
+ ComPtr<ICoreWebView2Controller4> controller4;
412
+ if (SUCCEEDED(controller_->QueryInterface(IID_PPV_ARGS(&controller4))) && controller4) {
413
+ controller4->put_AllowExternalDrop(TRUE);
414
+ }
415
+ }
416
+
417
+ // Custom scheme interception
418
+ // Win32/WebView2 doesn't support navigating to arbitrary custom
419
+ // schemes without environment-level registration. Instead we map
420
+ // <scheme>://<host>/<path> -> https://<scheme>.localhost/<path>
421
+ // (*.localhost is always treated as a secure origin).
422
+ if (!config_.custom_scheme.empty() && config_.scheme_handler) {
423
+ std::wstring filter =
424
+ L"https://" +
425
+ std::wstring(config_.custom_scheme.begin(), config_.custom_scheme.end()) +
426
+ L".localhost/*";
427
+ webview_->AddWebResourceRequestedFilter(
428
+ filter.c_str(),
429
+ COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL);
430
+
431
+ EventRegistrationToken scheme_token;
432
+ webview_->add_WebResourceRequested(
433
+ Callback<ICoreWebView2WebResourceRequestedEventHandler>(
434
+ [this](ICoreWebView2*, ICoreWebView2WebResourceRequestedEventArgs* args) -> HRESULT {
435
+ ComPtr<ICoreWebView2WebResourceRequest> request;
436
+ args->get_Request(&request);
437
+ LPWSTR uri_raw = nullptr;
438
+ request->get_Uri(&uri_raw);
439
+ if (!uri_raw) return S_OK;
440
+ std::wstring wuri(uri_raw);
441
+ CoTaskMemFree(uri_raw);
442
+
443
+ // Extract path: strip "https://<scheme>.localhost"
444
+ // Use WideCharToMultiByte for correct wide→UTF-8 conversion.
445
+ int len = WideCharToMultiByte(
446
+ CP_UTF8, 0, wuri.c_str(), -1,
447
+ nullptr, 0, nullptr, nullptr);
448
+ std::string full(len > 0 ? len - 1 : 0, '\0');
449
+ if (len > 0) WideCharToMultiByte(
450
+ CP_UTF8, 0, wuri.c_str(), -1,
451
+ &full[0], len, nullptr, nullptr);
452
+ std::string host_prefix =
453
+ "https://" + config_.custom_scheme + ".localhost";
454
+ std::string path = "/";
455
+ if (full.size() > host_prefix.size() &&
456
+ full.compare(0, host_prefix.size(), host_prefix) == 0) {
457
+ path = full.substr(host_prefix.size());
458
+ if (path.empty()) path = "/";
459
+ }
460
+ // Strip query and fragment
461
+ auto q = path.find('?');
462
+ if (q != std::string::npos) path.resize(q);
463
+ auto f = path.find('#');
464
+ if (f != std::string::npos) path.resize(f);
465
+ if (path.empty()) path = "/";
466
+
467
+ wpp::SchemeResponse resp = config_.scheme_handler(path);
468
+
469
+ IStream* stream = SHCreateMemStream(
470
+ reinterpret_cast<const BYTE*>(resp.body.data()),
471
+ static_cast<UINT>(resp.body.size()));
472
+ if (!stream) return S_OK;
473
+
474
+ std::wstring wctype(resp.content_type.begin(), resp.content_type.end());
475
+ std::wstring headers = L"Content-Type: " + wctype;
476
+ ComPtr<ICoreWebView2WebResourceResponse> response;
477
+ env_->CreateWebResourceResponse(
478
+ stream, resp.status, L"OK",
479
+ headers.c_str(), &response);
480
+ stream->Release();
481
+ args->put_Response(response.Get());
482
+ return S_OK;
483
+ }).Get(), &scheme_token);
484
+ }
485
+
486
+ if (!config_.initial_url.empty()) {
487
+ load_url(config_.initial_url);
488
+ } else if (!config_.initial_html.empty()) {
489
+ load_html(config_.initial_html);
490
+ }
491
+ }
492
+ return S_OK;
493
+ }).Get());
494
+ }
495
+ return S_OK;
496
+ }).Get());
497
+ }
498
+
499
+ ~Win32WebView() override {
500
+ close();
501
+ }
502
+
503
+ void load_url(const std::string& url) override {
504
+ if (!webview_) return;
505
+ std::string final_url = url;
506
+ // Remap <custom_scheme>://host/path -> https://<custom_scheme>.localhost/path
507
+ // so that Win32 WebView2 can navigate without custom scheme registration.
508
+ if (!config_.custom_scheme.empty()) {
509
+ const std::string prefix = config_.custom_scheme + "://";
510
+ if (url.size() > prefix.size() &&
511
+ url.compare(0, prefix.size(), prefix) == 0) {
512
+ std::string rest = url.substr(prefix.size());
513
+ auto sl = rest.find('/');
514
+ std::string path = (sl != std::string::npos) ? rest.substr(sl) : "/";
515
+ final_url = "https://" + config_.custom_scheme + ".localhost" + path;
516
+ }
517
+ }
518
+ std::wstring wurl(final_url.begin(), final_url.end());
519
+ webview_->Navigate(wurl.c_str());
520
+ }
521
+
522
+ void load_html(const std::string& html) override {
523
+ if (webview_) {
524
+ std::wstring whtml(html.begin(), html.end());
525
+ webview_->NavigateToString(whtml.c_str());
526
+ }
527
+ }
528
+
529
+ void execute_script(const std::string& script) override {
530
+ if (webview_) {
531
+ std::wstring wscript(script.begin(), script.end());
532
+ webview_->ExecuteScript(wscript.c_str(), nullptr);
533
+ }
534
+ }
535
+
536
+ void send_message(const std::string& message) override {
537
+ if (webview_) {
538
+ std::wstring wmsg(message.begin(), message.end());
539
+ webview_->PostWebMessageAsString(wmsg.c_str());
540
+ }
541
+ }
542
+
543
+ void go_back() override {
544
+ if (webview_) webview_->GoBack();
545
+ }
546
+
547
+ void go_forward() override {
548
+ if (webview_) webview_->GoForward();
549
+ }
550
+
551
+ void reload() override {
552
+ if (webview_) webview_->Reload();
553
+ }
554
+
555
+ void close() override {
556
+ if (controller_) {
557
+ controller_->put_IsVisible(FALSE);
558
+ controller_.Reset();
559
+ }
560
+ if (webview_) {
561
+ webview_.Reset();
562
+ }
563
+ if (env_) {
564
+ env_.Reset();
565
+ }
566
+ if (webview_host_hwnd_ && IsWindow(webview_host_hwnd_)) {
567
+ DestroyWindow(webview_host_hwnd_);
568
+ webview_host_hwnd_ = nullptr;
569
+ }
570
+ }
571
+
572
+ void resize(int width, int height) override {
573
+ int inset = config_.frameless ? 8 : 0;
574
+ int host_width = width - (inset * 2);
575
+ int host_height = height - (inset * 2);
576
+ if (host_width < 1) host_width = 1;
577
+ if (host_height < 1) host_height = 1;
578
+ if (webview_host_hwnd_ && IsWindow(webview_host_hwnd_)) {
579
+ SetWindowPos(
580
+ webview_host_hwnd_,
581
+ nullptr,
582
+ inset,
583
+ inset,
584
+ host_width,
585
+ host_height,
586
+ SWPP_NOZORDER | SWPP_NOACTIVATE | SWPP_SHOWWINDOW);
587
+ }
588
+ if (controller_) {
589
+ RECT bounds = {0, 0, host_width, host_height};
590
+ controller_->put_Bounds(bounds);
591
+ controller_->NotifyParentWindowPositionChanged();
592
+ }
593
+ }
594
+
595
+ void set_drag_drop_enabled(bool enabled) override {
596
+ drag_drop_enabled_ = enabled;
597
+ if (controller_) {
598
+ ComPtr<ICoreWebView2Controller4> controller4;
599
+ if (SUCCEEDED(controller_->QueryInterface(IID_PPV_ARGS(&controller4))) && controller4) {
600
+ controller4->put_AllowExternalDrop(enabled ? TRUE : FALSE);
601
+ }
602
+ }
603
+ if (!webview_) return;
604
+ std::wstring script = enabled
605
+ ? L"window.__wpp_dropzone_enabled = true;"
606
+ : L"window.__wpp_dropzone_enabled = false; document.querySelectorAll('.wpp-dropzone-active').forEach(function(el){ el.classList.remove('wpp-dropzone-active'); });";
607
+ webview_->ExecuteScript(script.c_str(), nullptr);
608
+ }
609
+
610
+ private:
611
+ void respond_to_bridge(const std::string& id, const json& result, const json* error = nullptr) {
612
+ if (!webview_) {
613
+ return;
614
+ }
615
+
616
+ std::string script = "window.__response__(" + json(id).dump() + "," + result.dump();
617
+ if (error) {
618
+ script += "," + error->dump();
619
+ }
620
+ script += ");";
621
+ execute_script(script);
622
+ }
623
+
624
+ bool handle_file_drop_message(const std::string& msg) {
625
+ json payload;
626
+ try {
627
+ payload = json::parse(msg);
628
+ } catch (...) {
629
+ return false;
630
+ }
631
+
632
+ if (!payload.is_object()) {
633
+ return false;
634
+ }
635
+
636
+ const auto method_it = payload.find("method");
637
+ const auto id_it = payload.find("id");
638
+ if (method_it == payload.end() || id_it == payload.end() || !method_it->is_string() || !id_it->is_string()) {
639
+ return false;
640
+ }
641
+
642
+ const std::string method = method_it->get<std::string>();
643
+ const std::string id = id_it->get<std::string>();
644
+ if (method == "wpp.fileDrop.setEnabled") {
645
+ bool enabled = true;
646
+ const auto params_it = payload.find("params");
647
+ if (params_it != payload.end() && params_it->is_array() && !params_it->empty() && (*params_it)[0].is_boolean()) {
648
+ enabled = (*params_it)[0].get<bool>();
649
+ }
650
+ set_drag_drop_enabled(enabled);
651
+ respond_to_bridge(id, nullptr);
652
+ return true;
653
+ }
654
+
655
+ if (method == "wpp.fileDrop.isEnabled") {
656
+ respond_to_bridge(id, drag_drop_enabled_);
657
+ return true;
658
+ }
659
+
660
+ if (method == "wpp.fileDrop.startDrag") {
661
+ const json error = "wpp.fileDrop.startDrag is not implemented on Win32 yet";
662
+ respond_to_bridge(id, nullptr, &error);
663
+ return true;
664
+ }
665
+
666
+ return false;
667
+ }
668
+
669
+ WebViewConfig config_;
670
+ HWND parent_hwnd_;
671
+ ComPtr<ICoreWebView2Environment> env_;
672
+ ComPtr<ICoreWebView2Controller> controller_;
673
+ ComPtr<ICoreWebView2> webview_;
674
+ HWND webview_host_hwnd_ = nullptr;
675
+ bool drag_drop_enabled_ = true;
676
+ };
677
+
678
+ std::unique_ptr<WebView> create_webview(const WebViewConfig& config, void* native_handle) {
679
+ HWND hwnd = static_cast<HWND>(native_handle);
680
+ if (!hwnd || !IsWindow(hwnd)) {
681
+ return nullptr;
682
+ }
683
+ return std::make_unique<Win32WebView>(config, hwnd);
684
+ }
685
+
686
+ } // namespace wpp
687
+
688
+ #endif // WIN32