plusui-native-core 0.1.105 → 0.1.107

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 (77) hide show
  1. package/Core/.cache/clangd/index/api.hpp.016B34C8046EF490.idx +0 -0
  2. package/Core/.cache/clangd/index/app.cpp.1E6FAF043D496421.idx +0 -0
  3. package/Core/.cache/clangd/index/app.hpp.FA0E5D412C4E6148.idx +0 -0
  4. package/Core/.cache/clangd/index/browser.cpp.9461A2CAF129F1D9.idx +0 -0
  5. package/Core/.cache/clangd/index/browser.hpp.BE40AE80881B3107.idx +0 -0
  6. package/Core/.cache/clangd/index/clipboard.cpp.2399913537B2A7AD.idx +0 -0
  7. package/Core/.cache/clangd/index/clipboard.hpp.C1095DDACD7149E9.idx +0 -0
  8. package/Core/.cache/clangd/index/connect.cpp.518C66C7C28B30A9.idx +0 -0
  9. package/Core/.cache/clangd/index/connect.hpp.08E2F7CD13B78601.idx +0 -0
  10. package/Core/.cache/clangd/index/connection.hpp.849FAEF1523BF2C3.idx +0 -0
  11. package/Core/.cache/clangd/index/display.cpp.F6F6D932BF9F8D8E.idx +0 -0
  12. package/Core/.cache/clangd/index/display.hpp.0C1A9CAD11EE4404.idx +0 -0
  13. package/Core/.cache/clangd/index/filedrop.cpp.669B524B3C501C52.idx +0 -0
  14. package/Core/.cache/clangd/index/filedrop.hpp.48460099C3F35F2D.idx +0 -0
  15. package/Core/.cache/clangd/index/keyboard.cpp.DC6D34E4A4F798DD.idx +0 -0
  16. package/Core/.cache/clangd/index/keyboard.hpp.F016CB68D7DE5A46.idx +0 -0
  17. package/Core/.cache/clangd/index/keyboard_linux.cpp.B403FDCEA7A6CA53.idx +0 -0
  18. package/Core/.cache/clangd/index/menu.cpp.3059F08D8D2DF265.idx +0 -0
  19. package/Core/.cache/clangd/index/menu.hpp.8716DCCC573910D4.idx +0 -0
  20. package/Core/.cache/clangd/index/plusui.hpp.8CFCDFDC2E3F41DD.idx +0 -0
  21. package/Core/.cache/clangd/index/router.cpp.EAC9EAD34C59E573.idx +0 -0
  22. package/Core/.cache/clangd/index/router.hpp.2B06E2EE9998468D.idx +0 -0
  23. package/Core/.cache/clangd/index/stb_image.h.E26CF48FE089A0D3.idx +0 -0
  24. package/Core/.cache/clangd/index/tray.cpp.92F244E7E1D7F0EC.idx +0 -0
  25. package/Core/.cache/clangd/index/tray.hpp.0D881B0601BBBD25.idx +0 -0
  26. package/Core/.cache/clangd/index/webgpu.cpp.FC656FA8BE10FE15.idx +0 -0
  27. package/Core/.cache/clangd/index/webgpu.hpp.5AF1A5E9DF9E5AE0.idx +0 -0
  28. package/Core/.cache/clangd/index/window.cpp.191D8C9ADF874B22.idx +0 -0
  29. package/Core/.cache/clangd/index/window.hpp.B9811B43AA295697.idx +0 -0
  30. package/Core/.claude/settings.local.json +7 -0
  31. package/Core/CMakeLists.txt +1 -1
  32. package/Core/Features/API/app-api.ts +28 -28
  33. package/Core/Features/API/browser-api.ts +38 -38
  34. package/Core/Features/API/clipboard-api.ts +21 -21
  35. package/Core/Features/API/display-api.ts +33 -33
  36. package/Core/Features/API/keyboard-api.ts +33 -33
  37. package/Core/Features/API/menu-api.ts +39 -39
  38. package/Core/Features/API/router-api.ts +23 -23
  39. package/Core/Features/API/tray-api.ts +22 -22
  40. package/Core/Features/API/webgpu-api.ts +55 -55
  41. package/Core/Features/App/app.cpp +135 -102
  42. package/Core/Features/Browser/browser.cpp +227 -227
  43. package/Core/Features/Browser/browser.ts +161 -161
  44. package/Core/Features/Clipboard/clipboard.cpp +235 -235
  45. package/Core/Features/Display/display.cpp +212 -212
  46. package/Core/Features/FileDrop/filedrop.cpp +448 -324
  47. package/Core/Features/FileDrop/filedrop.css +421 -421
  48. package/Core/Features/FileDrop/filedrop.ts +0 -7
  49. package/Core/Features/Keyboard/keyboard_linux.cpp +4 -0
  50. package/Core/Features/Router/router.cpp +62 -62
  51. package/Core/Features/Router/router.ts +113 -113
  52. package/Core/Features/Tray/tray.cpp +437 -324
  53. package/Core/Features/WebGPU/webgpu.cpp +948 -948
  54. package/Core/Features/Window/webview.cpp +1009 -1009
  55. package/Core/Features/Window/webview.ts +516 -516
  56. package/Core/Features/Window/window.cpp +2240 -1986
  57. package/Core/include/plusui/api.hpp +237 -237
  58. package/Core/include/plusui/app.hpp +36 -34
  59. package/Core/include/plusui/browser.hpp +67 -67
  60. package/Core/include/plusui/clipboard.hpp +41 -41
  61. package/Core/include/plusui/connect.hpp +340 -340
  62. package/Core/include/plusui/connection.hpp +3 -3
  63. package/Core/include/plusui/display.hpp +90 -90
  64. package/Core/include/plusui/filedrop.hpp +92 -77
  65. package/Core/include/plusui/keyboard.hpp +112 -112
  66. package/Core/include/plusui/menu.hpp +153 -153
  67. package/Core/include/plusui/plusui +18 -18
  68. package/Core/include/plusui/router.hpp +42 -42
  69. package/Core/include/plusui/tray.hpp +94 -94
  70. package/Core/include/plusui/webgpu.hpp +434 -434
  71. package/Core/include/plusui/window.hpp +180 -177
  72. package/Core/scripts/generate-umbrella-header.mjs +77 -77
  73. package/Core/vendor/WebView2EnvironmentOptions.h +406 -406
  74. package/Core/vendor/webview.h +487 -487
  75. package/Core/vendor/webview2.h +52079 -52079
  76. package/README.md +19 -19
  77. package/package.json +1 -1
@@ -1,13 +1,13 @@
1
- #include <fstream>
2
- #include <functional>
3
- #include <iostream>
4
- #include <map>
5
- #include <plusui/tray.hpp>
6
- #include <plusui/webgpu.hpp>
7
- #include <plusui/window.hpp>
8
- #include <regex>
9
- #include <sstream>
10
-
1
+ #include <fstream>
2
+ #include <functional>
3
+ #include <iostream>
4
+ #include <map>
5
+ #include <plusui/tray.hpp>
6
+ #include <plusui/webgpu.hpp>
7
+ #include <plusui/window.hpp>
8
+ #include <regex>
9
+ #include <sstream>
10
+
11
11
  #ifdef _WIN32
12
12
  #pragma warning(push)
13
13
  #pragma warning(disable: 4996) // Disable sscanf deprecation warning
@@ -26,30 +26,30 @@ using namespace Microsoft::WRL;
26
26
  #include <gtk/gtk.h>
27
27
  #include <webkit2/webkit2.h>
28
28
  #endif
29
-
30
- namespace plusui {
31
-
32
- namespace {
33
- constexpr char kHideScrollbarsScript[] = R"(
34
- (function() {
35
- var css = "::-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;}";
36
- var styleId = "__plusui_hide_scrollbars";
37
- var ensureStyle = function() {
38
- if (document.getElementById(styleId)) return;
39
- var style = document.createElement("style");
40
- style.id = styleId;
41
- style.type = "text/css";
42
- style.appendChild(document.createTextNode(css));
43
- var container = document.head || document.documentElement || document.body;
44
- if (container) {
45
- container.appendChild(style);
46
- }
47
- };
48
- ensureStyle();
49
- document.addEventListener("DOMContentLoaded", ensureStyle);
50
- window.addEventListener("load", ensureStyle);
51
- })();
52
- )";
29
+
30
+ namespace plusui {
31
+
32
+ namespace {
33
+ constexpr char kHideScrollbarsScript[] = R"(
34
+ (function() {
35
+ var css = "::-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;}";
36
+ var styleId = "__plusui_hide_scrollbars";
37
+ var ensureStyle = function() {
38
+ if (document.getElementById(styleId)) return;
39
+ var style = document.createElement("style");
40
+ style.id = styleId;
41
+ style.type = "text/css";
42
+ style.appendChild(document.createTextNode(css));
43
+ var container = document.head || document.documentElement || document.body;
44
+ if (container) {
45
+ container.appendChild(style);
46
+ }
47
+ };
48
+ ensureStyle();
49
+ document.addEventListener("DOMContentLoaded", ensureStyle);
50
+ window.addEventListener("load", ensureStyle);
51
+ })();
52
+ )";
53
53
  // Build the JS snippet that dispatches a plusui:keyboard:keydown/keyup CustomEvent.
54
54
  // keyCode — PlusUI KeyCode value
55
55
  // scancode — raw scan/keycode from the platform
@@ -89,79 +89,79 @@ static std::string buildShortcutScript(const std::string& id) {
89
89
 
90
90
  // Note: Window::Impl is defined in window.cpp
91
91
  // This file only provides implementations for webview-specific methods
92
-
93
- Window Window::create(void *windowHandle, const WindowConfig &config) {
94
- Window win;
95
- win.pImpl->config = config;
96
-
97
- #ifdef _WIN32
98
- HWND hwnd = static_cast<HWND>(windowHandle);
99
-
100
- // Create WebView2 environment and controller
101
- auto pImpl = win.pImpl;
102
- CreateCoreWebView2EnvironmentWithOptions(
103
- nullptr, nullptr, nullptr,
104
- Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
105
- [hwnd, pImpl](HRESULT result,
106
- ICoreWebView2Environment *env) -> HRESULT {
107
- if (FAILED(result) || !env)
108
- return result;
109
- env->CreateCoreWebView2Controller(
110
- hwnd,
111
- Callback<
112
- ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
113
- [pImpl](HRESULT result,
114
- ICoreWebView2Controller *controller) -> HRESULT {
115
- (void)result; // Suppress unused warning
116
- if (controller != nullptr) {
117
- pImpl->controller = controller;
118
- controller->get_CoreWebView2(&pImpl->webview);
119
-
120
- RECT bounds;
121
- HWND parentHwnd;
122
- controller->get_ParentWindow(&parentHwnd);
123
- GetClientRect(parentHwnd, &bounds);
124
- controller->put_Bounds(bounds);
125
-
126
- pImpl->nativeWebView = pImpl->webview.Get();
127
- pImpl->ready = true;
128
-
129
- // Disable scrollbars at the control level if configured
130
- if (!pImpl->config.scrollbars) {
131
- ComPtr<ICoreWebView2Settings> settings;
132
- pImpl->webview->get_Settings(&settings);
133
- if (settings) {
134
- // Attempt to disable scrollbars if this API is available
135
- // Note: This may not be available in all WebView2 versions
136
- // The CSS injection below will serve as fallback
137
- }
138
- }
139
-
140
- // Inject bridge script that runs on EVERY document
141
- // (survives navigation)
142
- std::string bridgeScript = R"(
143
- window.__native_invoke__ = function(request) {
144
- if (window.chrome && window.chrome.webview) {
145
- window.chrome.webview.postMessage(request);
146
- }
147
- };
148
- )";
149
- pImpl->webview->AddScriptToExecuteOnDocumentCreated(
150
- std::wstring(bridgeScript.begin(),
151
- bridgeScript.end())
152
- .c_str(),
153
- nullptr);
154
-
155
- // Disable scrollbars if configured
156
- if (!pImpl->config.scrollbars) {
157
- std::string scrollbarScript = kHideScrollbarsScript;
158
- pImpl->webview->AddScriptToExecuteOnDocumentCreated(
159
- std::wstring(scrollbarScript.begin(),
160
- scrollbarScript.end())
161
- .c_str(),
162
- nullptr);
163
- }
164
-
92
+
93
+ Window Window::create(void *windowHandle, const WindowConfig &config) {
94
+ Window win;
95
+ win.pImpl->config = config;
96
+
97
+ #ifdef _WIN32
98
+ HWND hwnd = static_cast<HWND>(windowHandle);
99
+
100
+ // Create WebView2 environment and controller
101
+ auto pImpl = win.pImpl;
102
+ CreateCoreWebView2EnvironmentWithOptions(
103
+ nullptr, nullptr, nullptr,
104
+ Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
105
+ [hwnd, pImpl](HRESULT result,
106
+ ICoreWebView2Environment *env) -> HRESULT {
107
+ if (FAILED(result) || !env)
108
+ return result;
109
+ env->CreateCoreWebView2Controller(
110
+ hwnd,
111
+ Callback<
112
+ ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
113
+ [pImpl](HRESULT result,
114
+ ICoreWebView2Controller *controller) -> HRESULT {
115
+ (void)result; // Suppress unused warning
116
+ if (controller != nullptr) {
117
+ pImpl->controller = controller;
118
+ controller->get_CoreWebView2(&pImpl->webview);
119
+
120
+ RECT bounds;
121
+ HWND parentHwnd;
122
+ controller->get_ParentWindow(&parentHwnd);
123
+ GetClientRect(parentHwnd, &bounds);
124
+ controller->put_Bounds(bounds);
125
+
126
+ pImpl->nativeWebView = pImpl->webview.Get();
127
+ pImpl->ready = true;
128
+
129
+ // Disable scrollbars at the control level if configured
130
+ if (!pImpl->config.scrollbars) {
131
+ ComPtr<ICoreWebView2Settings> settings;
132
+ pImpl->webview->get_Settings(&settings);
133
+ if (settings) {
134
+ // Attempt to disable scrollbars if this API is available
135
+ // Note: This may not be available in all WebView2 versions
136
+ // The CSS injection below will serve as fallback
137
+ }
138
+ }
139
+
140
+ // Inject bridge script that runs on EVERY document
141
+ // (survives navigation)
142
+ std::string bridgeScript = R"(
143
+ window.__native_invoke__ = function(request) {
144
+ if (window.chrome && window.chrome.webview) {
145
+ window.chrome.webview.postMessage(request);
146
+ }
147
+ };
148
+ )";
149
+ pImpl->webview->AddScriptToExecuteOnDocumentCreated(
150
+ std::wstring(bridgeScript.begin(),
151
+ bridgeScript.end())
152
+ .c_str(),
153
+ nullptr);
154
+
155
+ // Disable scrollbars if configured
156
+ if (!pImpl->config.scrollbars) {
157
+ std::string scrollbarScript = kHideScrollbarsScript;
158
+ pImpl->webview->AddScriptToExecuteOnDocumentCreated(
159
+ std::wstring(scrollbarScript.begin(),
160
+ scrollbarScript.end())
161
+ .c_str(),
162
+ nullptr);
163
+ }
164
+
165
165
  // Block browser default drag-drop behavior (prevents
166
166
  // the browser from navigating to the dropped file)
167
167
  // while still showing visual feedback on drop zones.
@@ -246,314 +246,314 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
246
246
  .c_str(),
247
247
  nullptr);
248
248
  }
249
-
250
- // Set up WebMessageReceived handler for JS->C++ bridge
251
- pImpl->webview->add_WebMessageReceived(
252
- Callback<
253
- ICoreWebView2WebMessageReceivedEventHandler>(
254
- [pImpl](ICoreWebView2 *sender,
255
- ICoreWebView2WebMessageReceivedEventArgs
256
- *args) -> HRESULT {
257
- (void)sender; // Suppress unused warning
258
- LPWSTR message = nullptr;
259
- args->TryGetWebMessageAsString(&message);
260
- if (!message)
261
- return S_OK;
262
- std::wstring wmsg(message);
263
- #pragma warning(push)
264
- #pragma warning(disable: 4244) // Suppress wchar_t to char conversion warning
265
- std::string msg(wmsg.begin(), wmsg.end());
266
- #pragma warning(pop)
267
- CoTaskMemFree(message);
268
-
269
- // Debug: log received message
270
- std::cout << "[PlusUI] Received: " << msg
271
- << std::endl;
272
-
273
- // Parse JSON-RPC: {"id":"...",
274
- // "method":"window.minimize", "params":[...]}
275
- std::string id, method;
276
- std::string result = "null";
277
- bool success = false;
278
-
279
- // Simple JSON parsing
280
- auto getId = [&msg]() {
281
- size_t pos = msg.find("\"id\"");
282
- if (pos == std::string::npos)
283
- return std::string();
284
- pos = msg.find(":", pos);
285
- if (pos == std::string::npos)
286
- return std::string();
287
- size_t start = msg.find("\"", pos);
288
- if (start == std::string::npos)
289
- return std::string();
290
- size_t end = msg.find("\"", start + 1);
291
- if (end == std::string::npos)
292
- return std::string();
293
- return msg.substr(start + 1,
294
- end - start - 1);
295
- };
296
- auto getMethod = [&msg]() {
297
- size_t pos = msg.find("\"method\"");
298
- if (pos == std::string::npos)
299
- return std::string();
300
- pos = msg.find(":", pos);
301
- if (pos == std::string::npos)
302
- return std::string();
303
- size_t start = msg.find("\"", pos);
304
- if (start == std::string::npos)
305
- return std::string();
306
- size_t end = msg.find("\"", start + 1);
307
- if (end == std::string::npos)
308
- return std::string();
309
- return msg.substr(start + 1,
310
- end - start - 1);
311
- };
312
-
313
- id = getId();
314
- method = getMethod();
315
-
316
- // Route to handlers
317
- if (method.find("window.") == 0) {
318
- std::string winMethod = method.substr(7);
319
- if (winMethod == "minimize") {
320
- if (pImpl->window) pImpl->window->minimize();
321
- success = true;
322
- } else if (winMethod == "maximize") {
323
- if (pImpl->window) pImpl->window->maximize();
324
- success = true;
325
- } else if (winMethod == "restore") {
326
- if (pImpl->window) pImpl->window->restore();
327
- success = true;
328
- } else if (winMethod == "close") {
329
- if (pImpl->window) pImpl->window->close();
330
- success = true;
331
- } else if (winMethod == "show") {
332
- if (pImpl->window) pImpl->window->show();
333
- success = true;
334
- } else if (winMethod == "hide") {
335
- if (pImpl->window) pImpl->window->hide();
336
- success = true;
337
- } else if (winMethod == "getSize") {
338
- if (pImpl->window) {
339
- int w, h;
340
- pImpl->window->getSize(w, h);
341
- result = "{\"width\":" +
342
- std::to_string(w) +
343
- ",\"height\":" +
344
- std::to_string(h) +
345
- "}";
346
- }
347
- success = true;
348
- } else if (winMethod == "getPosition") {
349
- if (pImpl->window) {
350
- int x, y;
351
- pImpl->window->getPosition(x, y);
352
- result =
353
- "{\"x\":" + std::to_string(x) +
354
- ",\"y\":" + std::to_string(y) +
355
- "}";
356
- }
357
- success = true;
358
- } else if (winMethod == "setSize") {
359
- // Parse params [width, height]
360
- size_t p1 = msg.find("[");
361
- size_t p2 = msg.find("]");
362
- if (p1 != std::string::npos &&
363
- p2 != std::string::npos) {
364
- std::string params =
365
- msg.substr(p1 + 1, p2 - p1 - 1);
366
- int w = 0, h = 0;
367
- sscanf(params.c_str(), "%d, %d", &w,
368
- &h);
369
- if (pImpl->window) pImpl->window->setSize(w, h);
370
- }
371
- success = true;
372
- } else if (winMethod == "setPosition") {
373
- size_t p1 = msg.find("[");
374
- size_t p2 = msg.find("]");
375
- if (p1 != std::string::npos &&
376
- p2 != std::string::npos) {
377
- std::string params =
378
- msg.substr(p1 + 1, p2 - p1 - 1);
379
- int x = 0, y = 0;
380
- sscanf(params.c_str(), "%d, %d", &x,
381
- &y);
382
- if (pImpl->window) pImpl->window->setPosition(x, y);
383
- }
384
- success = true;
385
- } else if (winMethod == "setTitle") {
386
- size_t p1 = msg.find("[\"");
387
- size_t p2 = msg.find("\"]");
388
- if (p1 != std::string::npos &&
389
- p2 != std::string::npos) {
390
- std::string title =
391
- msg.substr(p1 + 2, p2 - p1 - 2);
392
- if (pImpl->window) pImpl->window->setTitle(title);
393
- }
394
- success = true;
395
- } else if (winMethod == "setFullscreen") {
396
- size_t p1 = msg.find("[");
397
- size_t p2 = msg.find("]");
398
- if (p1 != std::string::npos &&
399
- p2 != std::string::npos) {
400
- std::string params =
401
- msg.substr(p1 + 1, p2 - p1 - 1);
402
- if (pImpl->window) pImpl->window->setFullscreen(
403
- params.find("true") !=
404
- std::string::npos);
405
- }
406
- success = true;
407
- } else if (winMethod == "setAlwaysOnTop") {
408
- size_t p1 = msg.find("[");
409
- size_t p2 = msg.find("]");
410
- if (p1 != std::string::npos &&
411
- p2 != std::string::npos) {
412
- std::string params =
413
- msg.substr(p1 + 1, p2 - p1 - 1);
414
- if (pImpl->window) pImpl->window->setAlwaysOnTop(
415
- params.find("true") !=
416
- std::string::npos);
417
- }
418
- success = true;
419
- } else if (winMethod == "setResizable") {
420
- size_t p1 = msg.find("[");
421
- size_t p2 = msg.find("]");
422
- if (p1 != std::string::npos &&
423
- p2 != std::string::npos) {
424
- std::string params =
425
- msg.substr(p1 + 1, p2 - p1 - 1);
426
- if (pImpl->window) pImpl->window->setResizable(
427
- params.find("true") !=
428
- std::string::npos);
429
- }
430
- success = true;
431
- } else if (winMethod == "isMaximized") {
432
- result =
433
- (pImpl->window && pImpl->window->isMaximized())
434
- ? "true"
435
- : "false";
436
- success = true;
437
- } else if (winMethod == "isMinimized") {
438
- result =
439
- (pImpl->window && pImpl->window->isMinimized())
440
- ? "true"
441
- : "false";
442
- success = true;
443
- } else if (winMethod == "isVisible") {
444
- result = (pImpl->window && pImpl->window->isVisible())
445
- ? "true"
446
- : "false";
447
- success = true;
448
- } else if (winMethod == "center") {
449
- if (pImpl->window) pImpl->window->center();
450
- success = true;
451
- }
452
- } else if (method.find("tray.") == 0) {
453
- std::string trayMethod = method.substr(5);
454
- if (trayMethod == "setIcon") {
455
- // TODO: implement
456
- success = true;
457
- } else if (trayMethod == "setTooltip") {
458
- // TODO: implement
459
- success = true;
460
- } else if (trayMethod == "setVisible") {
461
- // TODO: implement
462
- success = true;
463
- }
464
- } else if (method.find("browser.") == 0) {
465
- std::string browserMethod =
466
- method.substr(8);
467
- if (browserMethod == "navigate") {
468
- size_t p1 = msg.find("[\"");
469
- size_t p2 = msg.find("\"]");
470
- if (p1 != std::string::npos &&
471
- p2 != std::string::npos) {
472
- std::string url =
473
- msg.substr(p1 + 2, p2 - p1 - 2);
474
- pImpl->webview->Navigate(
475
- std::wstring(url.begin(), url.end())
476
- .c_str());
477
- }
478
- success = true;
479
- } else if (browserMethod == "goBack") {
480
- pImpl->webview->GoBack();
481
- success = true;
482
- } else if (browserMethod == "goForward") {
483
- pImpl->webview->GoForward();
484
- success = true;
485
- } else if (browserMethod == "reload") {
486
- pImpl->webview->Reload();
487
- success = true;
488
- } else if (browserMethod == "stop") {
489
- pImpl->webview->Stop();
490
- success = true;
491
- } else if (browserMethod == "getUrl") {
492
- result = "\"" + pImpl->currentURL + "\"";
493
- success = true;
494
- } else if (browserMethod == "getTitle") {
495
- result =
496
- "\"" + pImpl->currentTitle + "\"";
497
- success = true;
498
- } else if (browserMethod == "canGoBack") {
499
- BOOL canBack;
500
- pImpl->webview->get_CanGoBack(&canBack);
501
- result = canBack ? "true" : "false";
502
- success = true;
503
- } else if (browserMethod ==
504
- "canGoForward") {
505
- BOOL canForward;
506
- pImpl->webview->get_CanGoForward(
507
- &canForward);
508
- result = canForward ? "true" : "false";
509
- success = true;
510
- }
511
- } else if (method.find("app.") == 0) {
512
- std::string appMethod = method.substr(4);
513
- if (appMethod == "quit") {
514
- if (pImpl->window) pImpl->window->close();
515
- PostQuitMessage(0);
516
- success = true;
517
- }
518
- } else if (method.find("display.") == 0) {
519
- std::string displayMethod =
520
- method.substr(8);
521
- if (displayMethod == "getAll") {
522
- result =
523
- "[{\"id\":1,\"name\":\"Primary\","
524
- "\"x\":0,\"y\":0,\"width\":1920,"
525
- "\"height\":1080,\"scale\":1,"
526
- "\"isPrimary\":true}]";
527
- success = true;
528
- } else if (displayMethod == "getPrimary") {
529
- result = "{\"id\":1,\"name\":\"Primary\","
530
- "\"x\":0,\"y\":0,\"width\":1920,"
531
- "\"height\":1080,\"scale\":1,"
532
- "\"isPrimary\":true}";
533
- success = true;
534
- } else if (displayMethod == "getCurrent") {
535
- result = "{\"id\":1,\"name\":\"Primary\","
536
- "\"x\":0,\"y\":0,\"width\":1920,"
537
- "\"height\":1080,\"scale\":1,"
538
- "\"isPrimary\":true}";
539
- success = true;
540
- }
541
- } else if (method.find("clipboard.") == 0) {
542
- std::string clipMethod = method.substr(10);
543
- if (clipMethod == "writeText") {
544
- size_t p1 = msg.find("[\"");
545
- size_t p2 = msg.find("\"]");
546
- if (p1 != std::string::npos &&
547
- p2 != std::string::npos) {
548
- // TODO: implement clipboard write
549
- }
550
- success = true;
551
- } else if (clipMethod == "readText") {
552
- result = "\"\"";
553
- success = true;
554
- } else if (clipMethod == "clear") {
555
- success = true;
556
- }
249
+
250
+ // Set up WebMessageReceived handler for JS->C++ bridge
251
+ pImpl->webview->add_WebMessageReceived(
252
+ Callback<
253
+ ICoreWebView2WebMessageReceivedEventHandler>(
254
+ [pImpl](ICoreWebView2 *sender,
255
+ ICoreWebView2WebMessageReceivedEventArgs
256
+ *args) -> HRESULT {
257
+ (void)sender; // Suppress unused warning
258
+ LPWSTR message = nullptr;
259
+ args->TryGetWebMessageAsString(&message);
260
+ if (!message)
261
+ return S_OK;
262
+ std::wstring wmsg(message);
263
+ #pragma warning(push)
264
+ #pragma warning(disable: 4244) // Suppress wchar_t to char conversion warning
265
+ std::string msg(wmsg.begin(), wmsg.end());
266
+ #pragma warning(pop)
267
+ CoTaskMemFree(message);
268
+
269
+ // Debug: log received message
270
+ std::cout << "[PlusUI] Received: " << msg
271
+ << std::endl;
272
+
273
+ // Parse JSON-RPC: {"id":"...",
274
+ // "method":"window.minimize", "params":[...]}
275
+ std::string id, method;
276
+ std::string result = "null";
277
+ bool success = false;
278
+
279
+ // Simple JSON parsing
280
+ auto getId = [&msg]() {
281
+ size_t pos = msg.find("\"id\"");
282
+ if (pos == std::string::npos)
283
+ return std::string();
284
+ pos = msg.find(":", pos);
285
+ if (pos == std::string::npos)
286
+ return std::string();
287
+ size_t start = msg.find("\"", pos);
288
+ if (start == std::string::npos)
289
+ return std::string();
290
+ size_t end = msg.find("\"", start + 1);
291
+ if (end == std::string::npos)
292
+ return std::string();
293
+ return msg.substr(start + 1,
294
+ end - start - 1);
295
+ };
296
+ auto getMethod = [&msg]() {
297
+ size_t pos = msg.find("\"method\"");
298
+ if (pos == std::string::npos)
299
+ return std::string();
300
+ pos = msg.find(":", pos);
301
+ if (pos == std::string::npos)
302
+ return std::string();
303
+ size_t start = msg.find("\"", pos);
304
+ if (start == std::string::npos)
305
+ return std::string();
306
+ size_t end = msg.find("\"", start + 1);
307
+ if (end == std::string::npos)
308
+ return std::string();
309
+ return msg.substr(start + 1,
310
+ end - start - 1);
311
+ };
312
+
313
+ id = getId();
314
+ method = getMethod();
315
+
316
+ // Route to handlers
317
+ if (method.find("window.") == 0) {
318
+ std::string winMethod = method.substr(7);
319
+ if (winMethod == "minimize") {
320
+ if (pImpl->window) pImpl->window->minimize();
321
+ success = true;
322
+ } else if (winMethod == "maximize") {
323
+ if (pImpl->window) pImpl->window->maximize();
324
+ success = true;
325
+ } else if (winMethod == "restore") {
326
+ if (pImpl->window) pImpl->window->restore();
327
+ success = true;
328
+ } else if (winMethod == "close") {
329
+ if (pImpl->window) pImpl->window->close();
330
+ success = true;
331
+ } else if (winMethod == "show") {
332
+ if (pImpl->window) pImpl->window->show();
333
+ success = true;
334
+ } else if (winMethod == "hide") {
335
+ if (pImpl->window) pImpl->window->hide();
336
+ success = true;
337
+ } else if (winMethod == "getSize") {
338
+ if (pImpl->window) {
339
+ int w, h;
340
+ pImpl->window->getSize(w, h);
341
+ result = "{\"width\":" +
342
+ std::to_string(w) +
343
+ ",\"height\":" +
344
+ std::to_string(h) +
345
+ "}";
346
+ }
347
+ success = true;
348
+ } else if (winMethod == "getPosition") {
349
+ if (pImpl->window) {
350
+ int x, y;
351
+ pImpl->window->getPosition(x, y);
352
+ result =
353
+ "{\"x\":" + std::to_string(x) +
354
+ ",\"y\":" + std::to_string(y) +
355
+ "}";
356
+ }
357
+ success = true;
358
+ } else if (winMethod == "setSize") {
359
+ // Parse params [width, height]
360
+ size_t p1 = msg.find("[");
361
+ size_t p2 = msg.find("]");
362
+ if (p1 != std::string::npos &&
363
+ p2 != std::string::npos) {
364
+ std::string params =
365
+ msg.substr(p1 + 1, p2 - p1 - 1);
366
+ int w = 0, h = 0;
367
+ sscanf(params.c_str(), "%d, %d", &w,
368
+ &h);
369
+ if (pImpl->window) pImpl->window->setSize(w, h);
370
+ }
371
+ success = true;
372
+ } else if (winMethod == "setPosition") {
373
+ size_t p1 = msg.find("[");
374
+ size_t p2 = msg.find("]");
375
+ if (p1 != std::string::npos &&
376
+ p2 != std::string::npos) {
377
+ std::string params =
378
+ msg.substr(p1 + 1, p2 - p1 - 1);
379
+ int x = 0, y = 0;
380
+ sscanf(params.c_str(), "%d, %d", &x,
381
+ &y);
382
+ if (pImpl->window) pImpl->window->setPosition(x, y);
383
+ }
384
+ success = true;
385
+ } else if (winMethod == "setTitle") {
386
+ size_t p1 = msg.find("[\"");
387
+ size_t p2 = msg.find("\"]");
388
+ if (p1 != std::string::npos &&
389
+ p2 != std::string::npos) {
390
+ std::string title =
391
+ msg.substr(p1 + 2, p2 - p1 - 2);
392
+ if (pImpl->window) pImpl->window->setTitle(title);
393
+ }
394
+ success = true;
395
+ } else if (winMethod == "setFullscreen") {
396
+ size_t p1 = msg.find("[");
397
+ size_t p2 = msg.find("]");
398
+ if (p1 != std::string::npos &&
399
+ p2 != std::string::npos) {
400
+ std::string params =
401
+ msg.substr(p1 + 1, p2 - p1 - 1);
402
+ if (pImpl->window) pImpl->window->setFullscreen(
403
+ params.find("true") !=
404
+ std::string::npos);
405
+ }
406
+ success = true;
407
+ } else if (winMethod == "setAlwaysOnTop") {
408
+ size_t p1 = msg.find("[");
409
+ size_t p2 = msg.find("]");
410
+ if (p1 != std::string::npos &&
411
+ p2 != std::string::npos) {
412
+ std::string params =
413
+ msg.substr(p1 + 1, p2 - p1 - 1);
414
+ if (pImpl->window) pImpl->window->setAlwaysOnTop(
415
+ params.find("true") !=
416
+ std::string::npos);
417
+ }
418
+ success = true;
419
+ } else if (winMethod == "setResizable") {
420
+ size_t p1 = msg.find("[");
421
+ size_t p2 = msg.find("]");
422
+ if (p1 != std::string::npos &&
423
+ p2 != std::string::npos) {
424
+ std::string params =
425
+ msg.substr(p1 + 1, p2 - p1 - 1);
426
+ if (pImpl->window) pImpl->window->setResizable(
427
+ params.find("true") !=
428
+ std::string::npos);
429
+ }
430
+ success = true;
431
+ } else if (winMethod == "isMaximized") {
432
+ result =
433
+ (pImpl->window && pImpl->window->isMaximized())
434
+ ? "true"
435
+ : "false";
436
+ success = true;
437
+ } else if (winMethod == "isMinimized") {
438
+ result =
439
+ (pImpl->window && pImpl->window->isMinimized())
440
+ ? "true"
441
+ : "false";
442
+ success = true;
443
+ } else if (winMethod == "isVisible") {
444
+ result = (pImpl->window && pImpl->window->isVisible())
445
+ ? "true"
446
+ : "false";
447
+ success = true;
448
+ } else if (winMethod == "center") {
449
+ if (pImpl->window) pImpl->window->center();
450
+ success = true;
451
+ }
452
+ } else if (method.find("tray.") == 0) {
453
+ std::string trayMethod = method.substr(5);
454
+ if (trayMethod == "setIcon") {
455
+ // TODO: implement
456
+ success = true;
457
+ } else if (trayMethod == "setTooltip") {
458
+ // TODO: implement
459
+ success = true;
460
+ } else if (trayMethod == "setVisible") {
461
+ // TODO: implement
462
+ success = true;
463
+ }
464
+ } else if (method.find("browser.") == 0) {
465
+ std::string browserMethod =
466
+ method.substr(8);
467
+ if (browserMethod == "navigate") {
468
+ size_t p1 = msg.find("[\"");
469
+ size_t p2 = msg.find("\"]");
470
+ if (p1 != std::string::npos &&
471
+ p2 != std::string::npos) {
472
+ std::string url =
473
+ msg.substr(p1 + 2, p2 - p1 - 2);
474
+ pImpl->webview->Navigate(
475
+ std::wstring(url.begin(), url.end())
476
+ .c_str());
477
+ }
478
+ success = true;
479
+ } else if (browserMethod == "goBack") {
480
+ pImpl->webview->GoBack();
481
+ success = true;
482
+ } else if (browserMethod == "goForward") {
483
+ pImpl->webview->GoForward();
484
+ success = true;
485
+ } else if (browserMethod == "reload") {
486
+ pImpl->webview->Reload();
487
+ success = true;
488
+ } else if (browserMethod == "stop") {
489
+ pImpl->webview->Stop();
490
+ success = true;
491
+ } else if (browserMethod == "getUrl") {
492
+ result = "\"" + pImpl->currentURL + "\"";
493
+ success = true;
494
+ } else if (browserMethod == "getTitle") {
495
+ result =
496
+ "\"" + pImpl->currentTitle + "\"";
497
+ success = true;
498
+ } else if (browserMethod == "canGoBack") {
499
+ BOOL canBack;
500
+ pImpl->webview->get_CanGoBack(&canBack);
501
+ result = canBack ? "true" : "false";
502
+ success = true;
503
+ } else if (browserMethod ==
504
+ "canGoForward") {
505
+ BOOL canForward;
506
+ pImpl->webview->get_CanGoForward(
507
+ &canForward);
508
+ result = canForward ? "true" : "false";
509
+ success = true;
510
+ }
511
+ } else if (method.find("app.") == 0) {
512
+ std::string appMethod = method.substr(4);
513
+ if (appMethod == "quit") {
514
+ if (pImpl->window) pImpl->window->close();
515
+ PostQuitMessage(0);
516
+ success = true;
517
+ }
518
+ } else if (method.find("display.") == 0) {
519
+ std::string displayMethod =
520
+ method.substr(8);
521
+ if (displayMethod == "getAll") {
522
+ result =
523
+ "[{\"id\":1,\"name\":\"Primary\","
524
+ "\"x\":0,\"y\":0,\"width\":1920,"
525
+ "\"height\":1080,\"scale\":1,"
526
+ "\"isPrimary\":true}]";
527
+ success = true;
528
+ } else if (displayMethod == "getPrimary") {
529
+ result = "{\"id\":1,\"name\":\"Primary\","
530
+ "\"x\":0,\"y\":0,\"width\":1920,"
531
+ "\"height\":1080,\"scale\":1,"
532
+ "\"isPrimary\":true}";
533
+ success = true;
534
+ } else if (displayMethod == "getCurrent") {
535
+ result = "{\"id\":1,\"name\":\"Primary\","
536
+ "\"x\":0,\"y\":0,\"width\":1920,"
537
+ "\"height\":1080,\"scale\":1,"
538
+ "\"isPrimary\":true}";
539
+ success = true;
540
+ }
541
+ } else if (method.find("clipboard.") == 0) {
542
+ std::string clipMethod = method.substr(10);
543
+ if (clipMethod == "writeText") {
544
+ size_t p1 = msg.find("[\"");
545
+ size_t p2 = msg.find("\"]");
546
+ if (p1 != std::string::npos &&
547
+ p2 != std::string::npos) {
548
+ // TODO: implement clipboard write
549
+ }
550
+ success = true;
551
+ } else if (clipMethod == "readText") {
552
+ result = "\"\"";
553
+ success = true;
554
+ } else if (clipMethod == "clear") {
555
+ success = true;
556
+ }
557
557
  } else if (method.find("keyboard.") == 0) {
558
558
  // Keyboard API routing
559
559
  std::string kbMethod = method.substr(9);
@@ -646,38 +646,38 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
646
646
  "method: "
647
647
  << kbMethod << std::endl;
648
648
  }
649
- } else if (method.find("menu.") == 0) {
650
- // Menu API routing
651
- std::string menuMethod = method.substr(5);
652
- if (menuMethod == "create") {
653
- // TODO: integrate with
654
- // MenuBindings/ContextMenuManager
655
- result = "\"menu_" + id + "\"";
656
- success = true;
657
- } else if (menuMethod == "popup") {
658
- success = true;
659
- } else if (menuMethod == "popupAtCursor") {
660
- success = true;
661
- } else if (menuMethod == "close") {
662
- success = true;
663
- } else if (menuMethod == "destroy") {
664
- success = true;
665
- } else if (menuMethod ==
666
- "setApplicationMenu") {
667
- // TODO: integrate with MenuBar
668
- success = true;
669
- } else if (menuMethod ==
670
- "getApplicationMenu") {
671
- result = "[]";
672
- success = true;
673
- } else if (menuMethod ==
674
- "appendToMenuBar") {
675
- success = true;
676
- } else {
677
- std::cout
678
- << "[PlusUI] Unknown menu method: "
679
- << menuMethod << std::endl;
680
- }
649
+ } else if (method.find("menu.") == 0) {
650
+ // Menu API routing
651
+ std::string menuMethod = method.substr(5);
652
+ if (menuMethod == "create") {
653
+ // TODO: integrate with
654
+ // MenuBindings/ContextMenuManager
655
+ result = "\"menu_" + id + "\"";
656
+ success = true;
657
+ } else if (menuMethod == "popup") {
658
+ success = true;
659
+ } else if (menuMethod == "popupAtCursor") {
660
+ success = true;
661
+ } else if (menuMethod == "close") {
662
+ success = true;
663
+ } else if (menuMethod == "destroy") {
664
+ success = true;
665
+ } else if (menuMethod ==
666
+ "setApplicationMenu") {
667
+ // TODO: integrate with MenuBar
668
+ success = true;
669
+ } else if (menuMethod ==
670
+ "getApplicationMenu") {
671
+ result = "[]";
672
+ success = true;
673
+ } else if (menuMethod ==
674
+ "appendToMenuBar") {
675
+ success = true;
676
+ } else {
677
+ std::cout
678
+ << "[PlusUI] Unknown menu method: "
679
+ << menuMethod << std::endl;
680
+ }
681
681
  } else if (method.find("webview.") == 0) {
682
682
  // WebView API routing
683
683
  std::string webviewMethod = method.substr(8);
@@ -702,122 +702,122 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
702
702
  }
703
703
  success = true;
704
704
  }
705
- } else if (method.find("webgpu.") == 0) {
706
- // WebGPU API routing
707
- std::string webgpuMethod = method.substr(
708
- 7); // Remove "webgpu." prefix
709
-
710
- if (webgpuMethod == "requestAdapter") {
711
- result = pImpl->webgpu.requestAdapter(
712
- msg.substr(msg.find("["),
713
- msg.rfind("]") -
714
- msg.find("[") + 1));
715
- success = true;
716
- } else if (webgpuMethod ==
717
- "requestDevice") {
718
- // Extract adapterId and descriptor from
719
- // params
720
- size_t p1 = msg.find("[");
721
- size_t p2 = msg.find("]");
722
- if (p1 != std::string::npos &&
723
- p2 != std::string::npos) {
724
- std::string params =
725
- msg.substr(p1 + 1, p2 - p1 - 1);
726
- // TODO: Parse adapterId and descriptor
727
- // properly
728
- }
729
- success = true;
730
- } else if (webgpuMethod == "createBuffer") {
731
- success = true;
732
- } else if (webgpuMethod ==
733
- "createTexture") {
734
- success = true;
735
- } else if (webgpuMethod ==
736
- "createShaderModule") {
737
- success = true;
738
- } else if (webgpuMethod ==
739
- "createRenderPipeline") {
740
- success = true;
741
- } else if (webgpuMethod ==
742
- "createCommandEncoder") {
743
- success = true;
744
- } else if (webgpuMethod ==
745
- "submitCommands") {
746
- success = true;
747
- } else {
748
- // Unknown WebGPU method
749
- std::cout
750
- << "[PlusUI] Unknown WebGPU method: "
751
- << webgpuMethod << std::endl;
752
- }
753
- }
754
-
755
- // Send response back to JS (matches
756
- // plusui-native-core SDK bridge)
757
- std::string response =
758
- "window.__response__(\"" + id + "\", " +
759
- result + ");";
760
- pImpl->webview->ExecuteScript(
761
- std::wstring(response.begin(),
762
- response.end())
763
- .c_str(),
764
- nullptr);
765
-
766
- return S_OK;
767
- })
768
- .Get(),
769
- nullptr);
770
-
771
- // Process pending scripts
772
- for (const auto &script : pImpl->pendingScripts) {
773
- pImpl->webview->ExecuteScript(
774
- std::wstring(script.begin(), script.end())
775
- .c_str(),
776
- nullptr);
777
- }
778
- pImpl->pendingScripts.clear();
779
-
780
- // Process pending navigation
781
- if (!pImpl->pendingNavigation.empty()) {
782
- pImpl->webview->Navigate(
783
- std::wstring(pImpl->pendingNavigation.begin(),
784
- pImpl->pendingNavigation.end())
785
- .c_str());
786
- pImpl->pendingNavigation.clear();
787
- }
788
-
789
- // Process pending HTML
790
- if (!pImpl->pendingHTML.empty()) {
791
- pImpl->webview->NavigateToString(
792
- std::wstring(pImpl->pendingHTML.begin(),
793
- pImpl->pendingHTML.end())
794
- .c_str());
795
- pImpl->pendingHTML.clear();
796
- }
797
-
798
- // Process pending File
799
- if (!pImpl->pendingFile.empty()) {
800
- // For now, loadFile is implemented via navigate in
801
- // some versions or direct file reading. We'll handle
802
- // it via navigate for simplicity if it's already
803
- // implemented that way.
804
- }
805
- }
806
- return S_OK;
807
- })
808
- .Get());
809
- return S_OK;
810
- })
811
- .Get());
812
-
813
- #elif defined(__APPLE__)
814
- WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
815
- config.preferences.javaScriptEnabled = YES;
816
-
817
- if (win.pImpl->config.devtools) {
818
- [config.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
819
- }
820
-
705
+ } else if (method.find("webgpu.") == 0) {
706
+ // WebGPU API routing
707
+ std::string webgpuMethod = method.substr(
708
+ 7); // Remove "webgpu." prefix
709
+
710
+ if (webgpuMethod == "requestAdapter") {
711
+ result = pImpl->webgpu.requestAdapter(
712
+ msg.substr(msg.find("["),
713
+ msg.rfind("]") -
714
+ msg.find("[") + 1));
715
+ success = true;
716
+ } else if (webgpuMethod ==
717
+ "requestDevice") {
718
+ // Extract adapterId and descriptor from
719
+ // params
720
+ size_t p1 = msg.find("[");
721
+ size_t p2 = msg.find("]");
722
+ if (p1 != std::string::npos &&
723
+ p2 != std::string::npos) {
724
+ std::string params =
725
+ msg.substr(p1 + 1, p2 - p1 - 1);
726
+ // TODO: Parse adapterId and descriptor
727
+ // properly
728
+ }
729
+ success = true;
730
+ } else if (webgpuMethod == "createBuffer") {
731
+ success = true;
732
+ } else if (webgpuMethod ==
733
+ "createTexture") {
734
+ success = true;
735
+ } else if (webgpuMethod ==
736
+ "createShaderModule") {
737
+ success = true;
738
+ } else if (webgpuMethod ==
739
+ "createRenderPipeline") {
740
+ success = true;
741
+ } else if (webgpuMethod ==
742
+ "createCommandEncoder") {
743
+ success = true;
744
+ } else if (webgpuMethod ==
745
+ "submitCommands") {
746
+ success = true;
747
+ } else {
748
+ // Unknown WebGPU method
749
+ std::cout
750
+ << "[PlusUI] Unknown WebGPU method: "
751
+ << webgpuMethod << std::endl;
752
+ }
753
+ }
754
+
755
+ // Send response back to JS (matches
756
+ // plusui-native-core SDK bridge)
757
+ std::string response =
758
+ "window.__response__(\"" + id + "\", " +
759
+ result + ");";
760
+ pImpl->webview->ExecuteScript(
761
+ std::wstring(response.begin(),
762
+ response.end())
763
+ .c_str(),
764
+ nullptr);
765
+
766
+ return S_OK;
767
+ })
768
+ .Get(),
769
+ nullptr);
770
+
771
+ // Process pending scripts
772
+ for (const auto &script : pImpl->pendingScripts) {
773
+ pImpl->webview->ExecuteScript(
774
+ std::wstring(script.begin(), script.end())
775
+ .c_str(),
776
+ nullptr);
777
+ }
778
+ pImpl->pendingScripts.clear();
779
+
780
+ // Process pending navigation
781
+ if (!pImpl->pendingNavigation.empty()) {
782
+ pImpl->webview->Navigate(
783
+ std::wstring(pImpl->pendingNavigation.begin(),
784
+ pImpl->pendingNavigation.end())
785
+ .c_str());
786
+ pImpl->pendingNavigation.clear();
787
+ }
788
+
789
+ // Process pending HTML
790
+ if (!pImpl->pendingHTML.empty()) {
791
+ pImpl->webview->NavigateToString(
792
+ std::wstring(pImpl->pendingHTML.begin(),
793
+ pImpl->pendingHTML.end())
794
+ .c_str());
795
+ pImpl->pendingHTML.clear();
796
+ }
797
+
798
+ // Process pending File
799
+ if (!pImpl->pendingFile.empty()) {
800
+ // For now, loadFile is implemented via navigate in
801
+ // some versions or direct file reading. We'll handle
802
+ // it via navigate for simplicity if it's already
803
+ // implemented that way.
804
+ }
805
+ }
806
+ return S_OK;
807
+ })
808
+ .Get());
809
+ return S_OK;
810
+ })
811
+ .Get());
812
+
813
+ #elif defined(__APPLE__)
814
+ WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
815
+ config.preferences.javaScriptEnabled = YES;
816
+
817
+ if (win.pImpl->config.devtools) {
818
+ [config.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
819
+ }
820
+
821
821
  // Block browser default drag-drop while allowing drop zone visual feedback.
822
822
  // File delivery is handled natively by macOS drag APIs, not browser events.
823
823
  if (win.pImpl->config.disableWebviewDragDrop ||
@@ -874,18 +874,18 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
874
874
  forMainFrameOnly:NO];
875
875
  [config.userContentController addUserScript:userScript];
876
876
  }
877
-
878
- // Hide scrollbars if disabled
879
- if (!win.pImpl->config.scrollbars) {
880
- NSString *scrollbarScript = [NSString stringWithUTF8String:kHideScrollbarsScript];
881
-
882
- WKUserScript *scrollScript = [[WKUserScript alloc]
883
- initWithSource:scrollbarScript
884
- injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
885
- forMainFrameOnly:NO];
886
- [config.userContentController addUserScript:scrollScript];
887
- }
888
-
877
+
878
+ // Hide scrollbars if disabled
879
+ if (!win.pImpl->config.scrollbars) {
880
+ NSString *scrollbarScript = [NSString stringWithUTF8String:kHideScrollbarsScript];
881
+
882
+ WKUserScript *scrollScript = [[WKUserScript alloc]
883
+ initWithSource:scrollbarScript
884
+ injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
885
+ forMainFrameOnly:NO];
886
+ [config.userContentController addUserScript:scrollScript];
887
+ }
888
+
889
889
  NSView *parentView = (__bridge NSView *)windowHandle;
890
890
  win.pImpl->wkWebView =
891
891
  [[WKWebView alloc] initWithFrame:parentView.bounds configuration:config];
@@ -975,406 +975,406 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
975
975
  }
976
976
 
977
977
  #endif
978
-
979
- win.pImpl->trayManager = std::make_unique<TrayManager>();
980
- win.pImpl->trayManager->setWindowHandle(windowHandle);
981
-
982
- return win;
983
- }
984
-
985
- TrayManager &Window::tray() { return *pImpl->trayManager; }
986
-
987
- void Window::setWindow(std::shared_ptr<Window> win) {
988
- pImpl->window = win;
989
- if (!win)
990
- return;
991
-
992
- std::weak_ptr<Impl> weak_pImpl = pImpl;
993
- win->onResize([weak_pImpl](int w, int h) {
994
- if (auto pImpl = weak_pImpl.lock()) {
995
- #ifdef _WIN32
996
- if (pImpl->controller) {
997
- RECT bounds = {0, 0, w, h};
998
- pImpl->controller->put_Bounds(bounds);
999
- }
1000
- #elif defined(__APPLE__)
1001
- if (pImpl->wkWebView) {
1002
- NSView *view = (__bridge NSView *)pImpl->wkWebView;
1003
- NSView *parent = [view superview];
1004
- if (parent) {
1005
- [view setFrame:[parent bounds]];
1006
- }
1007
- }
1008
- #else
1009
- if (pImpl->gtkWebView) {
1010
- // GTK usually handles this if added to a container with expand=TRUE
1011
- // but we can ensure it here if it's a fixed layout parent
1012
- gtk_widget_set_size_request(GTK_WIDGET(pImpl->gtkWebView), w, h);
1013
- }
1014
- #endif
1015
- }
1016
- });
1017
-
1018
- // Trigger initial resize
1019
- int w, h;
1020
- win->getSize(w, h);
1021
- #ifdef _WIN32
1022
- if (pImpl->controller) {
1023
- RECT bounds = {0, 0, w, h};
1024
- pImpl->controller->put_Bounds(bounds);
1025
- }
1026
- #endif
1027
- }
1028
-
1029
- void Window::on(const std::string &event,
1030
- std::function<void(const std::string &)> callback) {
1031
- pImpl->events[event] = callback;
1032
- }
1033
-
1034
- void Window::emit(const std::string &event, const std::string &data) {
1035
- std::string js = "window.dispatchEvent(new CustomEvent('" + event +
1036
- "', {detail: " + data + "}))";
1037
- executeScript(js);
1038
- }
1039
-
1040
- void Window::navigate(const std::string &url) {
1041
- pImpl->currentURL = url;
1042
- pImpl->loading = true;
1043
-
1044
- #ifdef _WIN32
1045
- if (pImpl->ready && pImpl->webview) {
1046
- pImpl->webview->Navigate(std::wstring(url.begin(), url.end()).c_str());
1047
- } else {
1048
- pImpl->pendingNavigation = url;
1049
- }
1050
- #elif defined(__APPLE__)
1051
- if (pImpl->wkWebView) {
1052
- NSURL *nsurl =
1053
- [NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]];
1054
- NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
1055
- [pImpl->wkWebView loadRequest:request];
1056
- }
1057
- #else
1058
- if (pImpl->gtkWebView) {
1059
- webkit_web_view_load_uri(pImpl->gtkWebView, url.c_str());
1060
- }
1061
- #endif
1062
- }
1063
-
1064
- void Window::loadHTML(const std::string &html) {
1065
- loadHTML(html, "about:blank");
1066
- }
1067
-
1068
- void Window::loadHTML(const std::string &html, const std::string &baseURL) {
1069
- pImpl->loading = true;
1070
-
1071
- #ifdef _WIN32
1072
- (void)baseURL; // Not used on Windows
1073
- if (pImpl->ready && pImpl->webview) {
1074
- pImpl->webview->NavigateToString(
1075
- std::wstring(html.begin(), html.end()).c_str());
1076
- } else {
1077
- pImpl->pendingHTML = html;
1078
- }
1079
- #elif defined(__APPLE__)
1080
- if (pImpl->wkWebView) {
1081
- NSString *htmlString = [NSString stringWithUTF8String:html.c_str()];
1082
- NSURL *base =
1083
- [NSURL URLWithString:[NSString stringWithUTF8String:baseURL.c_str()]];
1084
- [pImpl->wkWebView loadHTMLString:htmlString baseURL:base];
1085
- }
1086
- #else
1087
- if (pImpl->gtkWebView) {
1088
- webkit_web_view_load_html(pImpl->gtkWebView, html.c_str(), baseURL.c_str());
1089
- }
1090
- #endif
1091
- }
1092
-
1093
- void Window::loadFile(const std::string &filePath) {
1094
- std::ifstream file(filePath);
1095
- if (!file) {
1096
- std::cerr << "PlusUI: Failed to open file: " << filePath << std::endl;
1097
- return;
1098
- }
1099
-
1100
- std::stringstream buffer;
1101
- buffer << file.rdbuf();
1102
-
1103
- // Use file:// URL as base for relative paths
1104
- std::string baseURL =
1105
- "file://" + filePath.substr(0, filePath.find_last_of("/\\") + 1);
1106
- loadHTML(buffer.str(), baseURL);
1107
- }
1108
-
1109
- void Window::reload() {
1110
- #ifdef _WIN32
1111
- if (pImpl->webview) {
1112
- pImpl->webview->Reload();
1113
- }
1114
- #elif defined(__APPLE__)
1115
- if (pImpl->wkWebView) {
1116
- [pImpl->wkWebView reload];
1117
- }
1118
- #else
1119
- if (pImpl->gtkWebView) {
1120
- webkit_web_view_reload(pImpl->gtkWebView);
1121
- }
1122
- #endif
1123
- }
1124
-
1125
- void Window::stop() {
1126
- #ifdef _WIN32
1127
- if (pImpl->webview) {
1128
- pImpl->webview->Stop();
1129
- }
1130
- #elif defined(__APPLE__)
1131
- if (pImpl->wkWebView) {
1132
- [pImpl->wkWebView stopLoading];
1133
- }
1134
- #else
1135
- if (pImpl->gtkWebView) {
1136
- webkit_web_view_stop_loading(pImpl->gtkWebView);
1137
- }
1138
- #endif
1139
- }
1140
-
1141
- void Window::goBack() {
1142
- #ifdef _WIN32
1143
- if (pImpl->webview) {
1144
- pImpl->webview->GoBack();
1145
- }
1146
- #elif defined(__APPLE__)
1147
- if (pImpl->wkWebView) {
1148
- [pImpl->wkWebView goBack];
1149
- }
1150
- #else
1151
- if (pImpl->gtkWebView) {
1152
- webkit_web_view_go_back(pImpl->gtkWebView);
1153
- }
1154
- #endif
1155
- }
1156
-
1157
- void Window::goForward() {
1158
- #ifdef _WIN32
1159
- if (pImpl->webview) {
1160
- pImpl->webview->GoForward();
1161
- }
1162
- #elif defined(__APPLE__)
1163
- if (pImpl->wkWebView) {
1164
- [pImpl->wkWebView goForward];
1165
- }
1166
- #else
1167
- if (pImpl->gtkWebView) {
1168
- webkit_web_view_go_forward(pImpl->gtkWebView);
1169
- }
1170
- #endif
1171
- }
1172
-
1173
- bool Window::canGoBack() const {
1174
- #ifdef _WIN32
1175
- if (pImpl->webview) {
1176
- BOOL canGoBack;
1177
- pImpl->webview->get_CanGoBack(&canGoBack);
1178
- return canGoBack;
1179
- }
1180
- #elif defined(__APPLE__)
1181
- if (pImpl->wkWebView) {
1182
- return [pImpl->wkWebView canGoBack];
1183
- }
1184
- #else
1185
- if (pImpl->gtkWebView) {
1186
- return webkit_web_view_can_go_back(pImpl->gtkWebView);
1187
- }
1188
- #endif
1189
- return false;
1190
- }
1191
-
1192
- bool Window::canGoForward() const {
1193
- #ifdef _WIN32
1194
- if (pImpl->webview) {
1195
- BOOL canGoForward;
1196
- pImpl->webview->get_CanGoForward(&canGoForward);
1197
- return canGoForward;
1198
- }
1199
- #elif defined(__APPLE__)
1200
- if (pImpl->wkWebView) {
1201
- return [pImpl->wkWebView canGoForward];
1202
- }
1203
- #else
1204
- if (pImpl->gtkWebView) {
1205
- return webkit_web_view_can_go_forward(pImpl->gtkWebView);
1206
- }
1207
- #endif
1208
- return false;
1209
- }
1210
-
1211
- void Window::executeScript(const std::string &script) {
1212
- #ifdef _WIN32
1213
- if (pImpl->ready && pImpl->webview) {
1214
- pImpl->webview->ExecuteScript(
1215
- std::wstring(script.begin(), script.end()).c_str(), nullptr);
1216
- } else {
1217
- pImpl->pendingScripts.push_back(script);
1218
- }
1219
- #elif defined(__APPLE__)
1220
- if (pImpl->wkWebView) {
1221
- NSString *js = [NSString stringWithUTF8String:script.c_str()];
1222
- [pImpl->wkWebView evaluateJavaScript:js completionHandler:nil];
1223
- }
1224
- #else
1225
- if (pImpl->gtkWebView) {
1226
- webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
1227
- nullptr, nullptr);
1228
- }
1229
- #endif
1230
- }
1231
-
1232
- void Window::executeScript(const std::string &script,
1233
- std::function<void(const std::string &)> callback) {
1234
- #ifdef _WIN32
1235
- if (pImpl->webview) {
1236
- pImpl->webview->ExecuteScript(
1237
- std::wstring(script.begin(), script.end()).c_str(),
1238
- Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
1239
- [callback](HRESULT error, LPCWSTR result) -> HRESULT {
1240
- (void)error; // Suppress unused warning
1241
- if (result && callback) {
1242
- std::wstring wstr(result);
1243
- callback(std::string(wstr.begin(), wstr.end()));
1244
- }
1245
- return S_OK;
1246
- })
1247
- .Get());
1248
- }
1249
- #elif defined(__APPLE__)
1250
- if (pImpl->wkWebView) {
1251
- NSString *js = [NSString stringWithUTF8String:script.c_str()];
1252
- [pImpl->wkWebView evaluateJavaScript:js
1253
- completionHandler:^(id result, NSError *error) {
1254
- if (result && callback) {
1255
- NSString *resultStr =
1256
- [NSString stringWithFormat:@"%@", result];
1257
- callback([resultStr UTF8String]);
1258
- }
1259
- }];
1260
- }
1261
- #else
1262
- if (pImpl->gtkWebView) {
1263
- // GTK WebKit callback handling would go here
1264
- webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
1265
- nullptr, nullptr);
1266
- }
1267
- #endif
1268
- }
1269
-
1270
- void Window::bind(const std::string &name, JSCallback callback) {
1271
- pImpl->bindings[name] = callback;
1272
-
1273
- // Inject bridge code to expose function to JavaScript
1274
- std::string bridgeScript = R"(
1275
- window.)" + name + R"( = function(...args) {
1276
- return window.plusui.invoke('webview.)" +
1277
- name + R"(', args);
1278
- };
1279
- )";
1280
-
1281
- executeScript(bridgeScript);
1282
- }
1283
-
1284
- void Window::unbind(const std::string &name) {
1285
- pImpl->bindings.erase(name);
1286
-
1287
- std::string script = "delete window." + name + ";";
1288
- executeScript(script);
1289
- }
1290
-
1291
- void Window::openDevTools() {
1292
- #ifdef _WIN32
1293
- if (pImpl->webview) {
1294
- pImpl->webview->OpenDevToolsWindow();
1295
- }
1296
- #elif defined(__APPLE__)
1297
- // macOS: Dev tools open in Safari's Web Inspector
1298
- #else
1299
- if (pImpl->gtkWebView) {
1300
- WebKitWebInspector *inspector =
1301
- webkit_web_view_get_inspector(pImpl->gtkWebView);
1302
- webkit_web_inspector_show(inspector);
1303
- }
1304
- #endif
1305
- }
1306
-
1307
- void Window::closeDevTools() {
1308
- #ifdef _WIN32
1309
- // WebView2 doesn't have explicit close
1310
- #elif defined(__APPLE__)
1311
- // macOS: Handled by Web Inspector
1312
- #else
1313
- if (pImpl->gtkWebView) {
1314
- WebKitWebInspector *inspector =
1315
- webkit_web_view_get_inspector(pImpl->gtkWebView);
1316
- webkit_web_inspector_close(inspector);
1317
- }
1318
- #endif
1319
- }
1320
-
1321
- void Window::setUserAgent(const std::string &userAgent) {
1322
- pImpl->userAgent = userAgent;
1323
-
1324
- #ifdef _WIN32
1325
- if (pImpl->webview) {
1326
- ComPtr<ICoreWebView2Settings> settings;
1327
- pImpl->webview->get_Settings(&settings);
1328
- // WebView2 user agent requires settings2 interface
1329
- }
1330
- #elif defined(__APPLE__)
1331
- if (pImpl->wkWebView) {
1332
- [pImpl->wkWebView
1333
- setCustomUserAgent:[NSString stringWithUTF8String:userAgent.c_str()]];
1334
- }
1335
- #else
1336
- if (pImpl->gtkWebView) {
1337
- WebKitSettings *settings = webkit_web_view_get_settings(pImpl->gtkWebView);
1338
- webkit_settings_set_user_agent(settings, userAgent.c_str());
1339
- }
1340
- #endif
1341
- }
1342
-
1343
- std::string Window::getUserAgent() const { return pImpl->userAgent; }
1344
-
1345
- void Window::setZoom(double factor) {
1346
- pImpl->zoom = factor;
1347
-
1348
- #ifdef _WIN32
1349
- if (pImpl->controller) {
1350
- pImpl->controller->put_ZoomFactor(factor);
1351
- }
1352
- #elif defined(__APPLE__)
1353
- if (pImpl->wkWebView) {
1354
- [pImpl->wkWebView setPageZoom:factor];
1355
- }
1356
- #else
1357
- if (pImpl->gtkWebView) {
1358
- webkit_web_view_set_zoom_level(pImpl->gtkWebView, factor);
1359
- }
1360
- #endif
1361
- }
1362
-
1363
- double Window::getZoom() const { return pImpl->zoom; }
1364
-
1365
- void Window::injectCSS(const std::string &css) {
1366
- std::string script = R"(
1367
- (function() {
1368
- const style = document.createElement('style');
1369
- style.textContent = `)" +
1370
- css + R"(`;
1371
- document.head.appendChild(style);
1372
- })();
1373
- )";
1374
-
1375
- executeScript(script);
1376
- }
1377
-
978
+
979
+ win.pImpl->trayManager = std::make_unique<TrayManager>();
980
+ win.pImpl->trayManager->setWindowHandle(windowHandle);
981
+
982
+ return win;
983
+ }
984
+
985
+ TrayManager &Window::tray() { return *pImpl->trayManager; }
986
+
987
+ void Window::setWindow(std::shared_ptr<Window> win) {
988
+ pImpl->window = win;
989
+ if (!win)
990
+ return;
991
+
992
+ std::weak_ptr<Impl> weak_pImpl = pImpl;
993
+ win->onResize([weak_pImpl](int w, int h) {
994
+ if (auto pImpl = weak_pImpl.lock()) {
995
+ #ifdef _WIN32
996
+ if (pImpl->controller) {
997
+ RECT bounds = {0, 0, w, h};
998
+ pImpl->controller->put_Bounds(bounds);
999
+ }
1000
+ #elif defined(__APPLE__)
1001
+ if (pImpl->wkWebView) {
1002
+ NSView *view = (__bridge NSView *)pImpl->wkWebView;
1003
+ NSView *parent = [view superview];
1004
+ if (parent) {
1005
+ [view setFrame:[parent bounds]];
1006
+ }
1007
+ }
1008
+ #else
1009
+ if (pImpl->gtkWebView) {
1010
+ // GTK usually handles this if added to a container with expand=TRUE
1011
+ // but we can ensure it here if it's a fixed layout parent
1012
+ gtk_widget_set_size_request(GTK_WIDGET(pImpl->gtkWebView), w, h);
1013
+ }
1014
+ #endif
1015
+ }
1016
+ });
1017
+
1018
+ // Trigger initial resize
1019
+ int w, h;
1020
+ win->getSize(w, h);
1021
+ #ifdef _WIN32
1022
+ if (pImpl->controller) {
1023
+ RECT bounds = {0, 0, w, h};
1024
+ pImpl->controller->put_Bounds(bounds);
1025
+ }
1026
+ #endif
1027
+ }
1028
+
1029
+ void Window::on(const std::string &event,
1030
+ std::function<void(const std::string &)> callback) {
1031
+ pImpl->events[event] = callback;
1032
+ }
1033
+
1034
+ void Window::emit(const std::string &event, const std::string &data) {
1035
+ std::string js = "window.dispatchEvent(new CustomEvent('" + event +
1036
+ "', {detail: " + data + "}))";
1037
+ executeScript(js);
1038
+ }
1039
+
1040
+ void Window::navigate(const std::string &url) {
1041
+ pImpl->currentURL = url;
1042
+ pImpl->loading = true;
1043
+
1044
+ #ifdef _WIN32
1045
+ if (pImpl->ready && pImpl->webview) {
1046
+ pImpl->webview->Navigate(std::wstring(url.begin(), url.end()).c_str());
1047
+ } else {
1048
+ pImpl->pendingNavigation = url;
1049
+ }
1050
+ #elif defined(__APPLE__)
1051
+ if (pImpl->wkWebView) {
1052
+ NSURL *nsurl =
1053
+ [NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]];
1054
+ NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
1055
+ [pImpl->wkWebView loadRequest:request];
1056
+ }
1057
+ #else
1058
+ if (pImpl->gtkWebView) {
1059
+ webkit_web_view_load_uri(pImpl->gtkWebView, url.c_str());
1060
+ }
1061
+ #endif
1062
+ }
1063
+
1064
+ void Window::loadHTML(const std::string &html) {
1065
+ loadHTML(html, "about:blank");
1066
+ }
1067
+
1068
+ void Window::loadHTML(const std::string &html, const std::string &baseURL) {
1069
+ pImpl->loading = true;
1070
+
1071
+ #ifdef _WIN32
1072
+ (void)baseURL; // Not used on Windows
1073
+ if (pImpl->ready && pImpl->webview) {
1074
+ pImpl->webview->NavigateToString(
1075
+ std::wstring(html.begin(), html.end()).c_str());
1076
+ } else {
1077
+ pImpl->pendingHTML = html;
1078
+ }
1079
+ #elif defined(__APPLE__)
1080
+ if (pImpl->wkWebView) {
1081
+ NSString *htmlString = [NSString stringWithUTF8String:html.c_str()];
1082
+ NSURL *base =
1083
+ [NSURL URLWithString:[NSString stringWithUTF8String:baseURL.c_str()]];
1084
+ [pImpl->wkWebView loadHTMLString:htmlString baseURL:base];
1085
+ }
1086
+ #else
1087
+ if (pImpl->gtkWebView) {
1088
+ webkit_web_view_load_html(pImpl->gtkWebView, html.c_str(), baseURL.c_str());
1089
+ }
1090
+ #endif
1091
+ }
1092
+
1093
+ void Window::loadFile(const std::string &filePath) {
1094
+ std::ifstream file(filePath);
1095
+ if (!file) {
1096
+ std::cerr << "PlusUI: Failed to open file: " << filePath << std::endl;
1097
+ return;
1098
+ }
1099
+
1100
+ std::stringstream buffer;
1101
+ buffer << file.rdbuf();
1102
+
1103
+ // Use file:// URL as base for relative paths
1104
+ std::string baseURL =
1105
+ "file://" + filePath.substr(0, filePath.find_last_of("/\\") + 1);
1106
+ loadHTML(buffer.str(), baseURL);
1107
+ }
1108
+
1109
+ void Window::reload() {
1110
+ #ifdef _WIN32
1111
+ if (pImpl->webview) {
1112
+ pImpl->webview->Reload();
1113
+ }
1114
+ #elif defined(__APPLE__)
1115
+ if (pImpl->wkWebView) {
1116
+ [pImpl->wkWebView reload];
1117
+ }
1118
+ #else
1119
+ if (pImpl->gtkWebView) {
1120
+ webkit_web_view_reload(pImpl->gtkWebView);
1121
+ }
1122
+ #endif
1123
+ }
1124
+
1125
+ void Window::stop() {
1126
+ #ifdef _WIN32
1127
+ if (pImpl->webview) {
1128
+ pImpl->webview->Stop();
1129
+ }
1130
+ #elif defined(__APPLE__)
1131
+ if (pImpl->wkWebView) {
1132
+ [pImpl->wkWebView stopLoading];
1133
+ }
1134
+ #else
1135
+ if (pImpl->gtkWebView) {
1136
+ webkit_web_view_stop_loading(pImpl->gtkWebView);
1137
+ }
1138
+ #endif
1139
+ }
1140
+
1141
+ void Window::goBack() {
1142
+ #ifdef _WIN32
1143
+ if (pImpl->webview) {
1144
+ pImpl->webview->GoBack();
1145
+ }
1146
+ #elif defined(__APPLE__)
1147
+ if (pImpl->wkWebView) {
1148
+ [pImpl->wkWebView goBack];
1149
+ }
1150
+ #else
1151
+ if (pImpl->gtkWebView) {
1152
+ webkit_web_view_go_back(pImpl->gtkWebView);
1153
+ }
1154
+ #endif
1155
+ }
1156
+
1157
+ void Window::goForward() {
1158
+ #ifdef _WIN32
1159
+ if (pImpl->webview) {
1160
+ pImpl->webview->GoForward();
1161
+ }
1162
+ #elif defined(__APPLE__)
1163
+ if (pImpl->wkWebView) {
1164
+ [pImpl->wkWebView goForward];
1165
+ }
1166
+ #else
1167
+ if (pImpl->gtkWebView) {
1168
+ webkit_web_view_go_forward(pImpl->gtkWebView);
1169
+ }
1170
+ #endif
1171
+ }
1172
+
1173
+ bool Window::canGoBack() const {
1174
+ #ifdef _WIN32
1175
+ if (pImpl->webview) {
1176
+ BOOL canGoBack;
1177
+ pImpl->webview->get_CanGoBack(&canGoBack);
1178
+ return canGoBack;
1179
+ }
1180
+ #elif defined(__APPLE__)
1181
+ if (pImpl->wkWebView) {
1182
+ return [pImpl->wkWebView canGoBack];
1183
+ }
1184
+ #else
1185
+ if (pImpl->gtkWebView) {
1186
+ return webkit_web_view_can_go_back(pImpl->gtkWebView);
1187
+ }
1188
+ #endif
1189
+ return false;
1190
+ }
1191
+
1192
+ bool Window::canGoForward() const {
1193
+ #ifdef _WIN32
1194
+ if (pImpl->webview) {
1195
+ BOOL canGoForward;
1196
+ pImpl->webview->get_CanGoForward(&canGoForward);
1197
+ return canGoForward;
1198
+ }
1199
+ #elif defined(__APPLE__)
1200
+ if (pImpl->wkWebView) {
1201
+ return [pImpl->wkWebView canGoForward];
1202
+ }
1203
+ #else
1204
+ if (pImpl->gtkWebView) {
1205
+ return webkit_web_view_can_go_forward(pImpl->gtkWebView);
1206
+ }
1207
+ #endif
1208
+ return false;
1209
+ }
1210
+
1211
+ void Window::executeScript(const std::string &script) {
1212
+ #ifdef _WIN32
1213
+ if (pImpl->ready && pImpl->webview) {
1214
+ pImpl->webview->ExecuteScript(
1215
+ std::wstring(script.begin(), script.end()).c_str(), nullptr);
1216
+ } else {
1217
+ pImpl->pendingScripts.push_back(script);
1218
+ }
1219
+ #elif defined(__APPLE__)
1220
+ if (pImpl->wkWebView) {
1221
+ NSString *js = [NSString stringWithUTF8String:script.c_str()];
1222
+ [pImpl->wkWebView evaluateJavaScript:js completionHandler:nil];
1223
+ }
1224
+ #else
1225
+ if (pImpl->gtkWebView) {
1226
+ webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
1227
+ nullptr, nullptr);
1228
+ }
1229
+ #endif
1230
+ }
1231
+
1232
+ void Window::executeScript(const std::string &script,
1233
+ std::function<void(const std::string &)> callback) {
1234
+ #ifdef _WIN32
1235
+ if (pImpl->webview) {
1236
+ pImpl->webview->ExecuteScript(
1237
+ std::wstring(script.begin(), script.end()).c_str(),
1238
+ Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
1239
+ [callback](HRESULT error, LPCWSTR result) -> HRESULT {
1240
+ (void)error; // Suppress unused warning
1241
+ if (result && callback) {
1242
+ std::wstring wstr(result);
1243
+ callback(std::string(wstr.begin(), wstr.end()));
1244
+ }
1245
+ return S_OK;
1246
+ })
1247
+ .Get());
1248
+ }
1249
+ #elif defined(__APPLE__)
1250
+ if (pImpl->wkWebView) {
1251
+ NSString *js = [NSString stringWithUTF8String:script.c_str()];
1252
+ [pImpl->wkWebView evaluateJavaScript:js
1253
+ completionHandler:^(id result, NSError *error) {
1254
+ if (result && callback) {
1255
+ NSString *resultStr =
1256
+ [NSString stringWithFormat:@"%@", result];
1257
+ callback([resultStr UTF8String]);
1258
+ }
1259
+ }];
1260
+ }
1261
+ #else
1262
+ if (pImpl->gtkWebView) {
1263
+ // GTK WebKit callback handling would go here
1264
+ webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
1265
+ nullptr, nullptr);
1266
+ }
1267
+ #endif
1268
+ }
1269
+
1270
+ void Window::bind(const std::string &name, JSCallback callback) {
1271
+ pImpl->bindings[name] = callback;
1272
+
1273
+ // Inject bridge code to expose function to JavaScript
1274
+ std::string bridgeScript = R"(
1275
+ window.)" + name + R"( = function(...args) {
1276
+ return window.plusui.invoke('webview.)" +
1277
+ name + R"(', args);
1278
+ };
1279
+ )";
1280
+
1281
+ executeScript(bridgeScript);
1282
+ }
1283
+
1284
+ void Window::unbind(const std::string &name) {
1285
+ pImpl->bindings.erase(name);
1286
+
1287
+ std::string script = "delete window." + name + ";";
1288
+ executeScript(script);
1289
+ }
1290
+
1291
+ void Window::openDevTools() {
1292
+ #ifdef _WIN32
1293
+ if (pImpl->webview) {
1294
+ pImpl->webview->OpenDevToolsWindow();
1295
+ }
1296
+ #elif defined(__APPLE__)
1297
+ // macOS: Dev tools open in Safari's Web Inspector
1298
+ #else
1299
+ if (pImpl->gtkWebView) {
1300
+ WebKitWebInspector *inspector =
1301
+ webkit_web_view_get_inspector(pImpl->gtkWebView);
1302
+ webkit_web_inspector_show(inspector);
1303
+ }
1304
+ #endif
1305
+ }
1306
+
1307
+ void Window::closeDevTools() {
1308
+ #ifdef _WIN32
1309
+ // WebView2 doesn't have explicit close
1310
+ #elif defined(__APPLE__)
1311
+ // macOS: Handled by Web Inspector
1312
+ #else
1313
+ if (pImpl->gtkWebView) {
1314
+ WebKitWebInspector *inspector =
1315
+ webkit_web_view_get_inspector(pImpl->gtkWebView);
1316
+ webkit_web_inspector_close(inspector);
1317
+ }
1318
+ #endif
1319
+ }
1320
+
1321
+ void Window::setUserAgent(const std::string &userAgent) {
1322
+ pImpl->userAgent = userAgent;
1323
+
1324
+ #ifdef _WIN32
1325
+ if (pImpl->webview) {
1326
+ ComPtr<ICoreWebView2Settings> settings;
1327
+ pImpl->webview->get_Settings(&settings);
1328
+ // WebView2 user agent requires settings2 interface
1329
+ }
1330
+ #elif defined(__APPLE__)
1331
+ if (pImpl->wkWebView) {
1332
+ [pImpl->wkWebView
1333
+ setCustomUserAgent:[NSString stringWithUTF8String:userAgent.c_str()]];
1334
+ }
1335
+ #else
1336
+ if (pImpl->gtkWebView) {
1337
+ WebKitSettings *settings = webkit_web_view_get_settings(pImpl->gtkWebView);
1338
+ webkit_settings_set_user_agent(settings, userAgent.c_str());
1339
+ }
1340
+ #endif
1341
+ }
1342
+
1343
+ std::string Window::getUserAgent() const { return pImpl->userAgent; }
1344
+
1345
+ void Window::setZoom(double factor) {
1346
+ pImpl->zoom = factor;
1347
+
1348
+ #ifdef _WIN32
1349
+ if (pImpl->controller) {
1350
+ pImpl->controller->put_ZoomFactor(factor);
1351
+ }
1352
+ #elif defined(__APPLE__)
1353
+ if (pImpl->wkWebView) {
1354
+ [pImpl->wkWebView setPageZoom:factor];
1355
+ }
1356
+ #else
1357
+ if (pImpl->gtkWebView) {
1358
+ webkit_web_view_set_zoom_level(pImpl->gtkWebView, factor);
1359
+ }
1360
+ #endif
1361
+ }
1362
+
1363
+ double Window::getZoom() const { return pImpl->zoom; }
1364
+
1365
+ void Window::injectCSS(const std::string &css) {
1366
+ std::string script = R"(
1367
+ (function() {
1368
+ const style = document.createElement('style');
1369
+ style.textContent = `)" +
1370
+ css + R"(`;
1371
+ document.head.appendChild(style);
1372
+ })();
1373
+ )";
1374
+
1375
+ executeScript(script);
1376
+ }
1377
+
1378
1378
  void Window::setWebviewDragDropEnabled(bool enabled) {
1379
1379
  if (enabled) {
1380
1380
  executeScript(R"(
@@ -1442,37 +1442,37 @@ void Window::setWebviewDragDropEnabled(bool enabled) {
1442
1442
  )");
1443
1443
  }
1444
1444
  }
1445
-
1446
- void Window::onNavigationStart(NavigationCallback callback) {
1447
- pImpl->navigationCallback = callback;
1448
- }
1449
-
1450
- void Window::onNavigationComplete(LoadCallback callback) {
1451
- pImpl->navigationCompleteCallback = callback;
1452
- }
1453
-
1454
- void Window::onLoadStart(LoadCallback callback) {
1455
- pImpl->loadStartCallback = callback;
1456
- }
1457
-
1458
- void Window::onLoadEnd(LoadCallback callback) {
1459
- pImpl->loadEndCallback = callback;
1460
- }
1461
-
1462
- void Window::onLoadError(ErrorCallback callback) {
1463
- pImpl->errorCallback = callback;
1464
- }
1465
-
1466
- void Window::onConsoleMessage(ConsoleCallback callback) {
1467
- pImpl->consoleCallback = callback;
1468
- }
1469
-
1470
- bool Window::isLoading() const { return pImpl->loading; }
1471
-
1472
- std::string Window::getURL() const { return pImpl->currentURL; }
1473
-
1474
- std::string Window::getTitle() const { return pImpl->currentTitle; }
1475
-
1476
- void *Window::nativeHandle() const { return pImpl->nativeWebView; }
1477
-
1478
- } // namespace plusui
1445
+
1446
+ void Window::onNavigationStart(NavigationCallback callback) {
1447
+ pImpl->navigationCallback = callback;
1448
+ }
1449
+
1450
+ void Window::onNavigationComplete(LoadCallback callback) {
1451
+ pImpl->navigationCompleteCallback = callback;
1452
+ }
1453
+
1454
+ void Window::onLoadStart(LoadCallback callback) {
1455
+ pImpl->loadStartCallback = callback;
1456
+ }
1457
+
1458
+ void Window::onLoadEnd(LoadCallback callback) {
1459
+ pImpl->loadEndCallback = callback;
1460
+ }
1461
+
1462
+ void Window::onLoadError(ErrorCallback callback) {
1463
+ pImpl->errorCallback = callback;
1464
+ }
1465
+
1466
+ void Window::onConsoleMessage(ConsoleCallback callback) {
1467
+ pImpl->consoleCallback = callback;
1468
+ }
1469
+
1470
+ bool Window::isLoading() const { return pImpl->loading; }
1471
+
1472
+ std::string Window::getURL() const { return pImpl->currentURL; }
1473
+
1474
+ std::string Window::getTitle() const { return pImpl->currentTitle; }
1475
+
1476
+ void *Window::nativeHandle() const { return pImpl->nativeWebView; }
1477
+
1478
+ } // namespace plusui