plusui-native-core 0.1.104 → 0.1.106

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 (49) hide show
  1. package/Core/.claude/settings.local.json +7 -0
  2. package/Core/CMakeLists.txt +1 -1
  3. package/Core/Features/API/Connect_API.ts +20 -52
  4. package/Core/Features/API/app-api.ts +28 -28
  5. package/Core/Features/API/browser-api.ts +38 -38
  6. package/Core/Features/API/clipboard-api.ts +21 -21
  7. package/Core/Features/API/display-api.ts +33 -33
  8. package/Core/Features/API/keyboard-api.ts +33 -33
  9. package/Core/Features/API/menu-api.ts +39 -39
  10. package/Core/Features/API/router-api.ts +23 -23
  11. package/Core/Features/API/tray-api.ts +22 -22
  12. package/Core/Features/API/webgpu-api.ts +55 -55
  13. package/Core/Features/App/app.cpp +128 -102
  14. package/Core/Features/Browser/browser.cpp +227 -227
  15. package/Core/Features/Browser/browser.ts +161 -161
  16. package/Core/Features/Clipboard/clipboard.cpp +235 -235
  17. package/Core/Features/Display/display.cpp +212 -212
  18. package/Core/Features/FileDrop/filedrop.cpp +448 -324
  19. package/Core/Features/FileDrop/filedrop.css +421 -421
  20. package/Core/Features/FileDrop/filedrop.ts +5 -9
  21. package/Core/Features/Keyboard/keyboard_linux.cpp +4 -0
  22. package/Core/Features/Router/router.cpp +62 -62
  23. package/Core/Features/Router/router.ts +113 -113
  24. package/Core/Features/Tray/tray.cpp +328 -324
  25. package/Core/Features/WebGPU/webgpu.cpp +948 -948
  26. package/Core/Features/Window/webview.cpp +1026 -1014
  27. package/Core/Features/Window/webview.ts +516 -516
  28. package/Core/Features/Window/window.cpp +2265 -1988
  29. package/Core/include/plusui/api.hpp +237 -237
  30. package/Core/include/plusui/app.hpp +33 -33
  31. package/Core/include/plusui/browser.hpp +67 -67
  32. package/Core/include/plusui/clipboard.hpp +41 -41
  33. package/Core/include/plusui/connect.hpp +340 -340
  34. package/Core/include/plusui/connection.hpp +3 -3
  35. package/Core/include/plusui/display.hpp +90 -90
  36. package/Core/include/plusui/filedrop.hpp +92 -77
  37. package/Core/include/plusui/keyboard.hpp +112 -112
  38. package/Core/include/plusui/menu.hpp +153 -153
  39. package/Core/include/plusui/plusui +18 -18
  40. package/Core/include/plusui/router.hpp +42 -42
  41. package/Core/include/plusui/tray.hpp +94 -94
  42. package/Core/include/plusui/webgpu.hpp +434 -434
  43. package/Core/include/plusui/window.hpp +180 -177
  44. package/Core/scripts/generate-umbrella-header.mjs +77 -77
  45. package/Core/vendor/WebView2EnvironmentOptions.h +406 -406
  46. package/Core/vendor/webview.h +487 -487
  47. package/Core/vendor/webview2.h +52079 -52079
  48. package/README.md +19 -19
  49. 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.
@@ -174,6 +174,7 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
174
174
  window.__plusui_dropzone_init = true;
175
175
 
176
176
  var activeZone = null;
177
+ var dragDepth = 0;
177
178
 
178
179
  var findDropZone = function(e) {
179
180
  var target = null;
@@ -187,13 +188,20 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
187
188
 
188
189
  var updateActiveZone = function(zone) {
189
190
  if (activeZone === zone) return;
190
- if (activeZone) activeZone.classList.remove('dropzone-active');
191
+ if (activeZone) {
192
+ activeZone.classList.remove('dropzone-active');
193
+ activeZone.classList.remove('filedrop-active');
194
+ }
191
195
  activeZone = zone;
192
- if (activeZone) activeZone.classList.add('dropzone-active');
196
+ if (activeZone) {
197
+ activeZone.classList.add('dropzone-active');
198
+ activeZone.classList.add('filedrop-active');
199
+ }
193
200
  };
194
201
 
195
202
  document.addEventListener('dragenter', function(e) {
196
203
  e.preventDefault();
204
+ dragDepth++;
197
205
  var zone = findDropZone(e);
198
206
  updateActiveZone(zone);
199
207
  if (e.dataTransfer) {
@@ -212,16 +220,20 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
212
220
 
213
221
  document.addEventListener('dragleave', function(e) {
214
222
  e.preventDefault();
215
- var zone = findDropZone(e);
216
- // Only remove if actually leaving the zone (relatedTarget is outside)
217
- if (e.relatedTarget && zone && !zone.contains(e.relatedTarget)) {
223
+ dragDepth--;
224
+ if (dragDepth <= 0) {
225
+ dragDepth = 0;
218
226
  updateActiveZone(null);
227
+ } else {
228
+ var zone = findDropZone(e);
229
+ updateActiveZone(zone);
219
230
  }
220
231
  }, true);
221
232
 
222
233
  document.addEventListener('drop', function(e) {
223
234
  e.preventDefault();
224
235
  updateActiveZone(null);
236
+ dragDepth = 0;
225
237
  }, true);
226
238
 
227
239
  window.addEventListener('dragover', function(e) { e.preventDefault(); }, true);
@@ -234,314 +246,314 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
234
246
  .c_str(),
235
247
  nullptr);
236
248
  }
237
-
238
- // Set up WebMessageReceived handler for JS->C++ bridge
239
- pImpl->webview->add_WebMessageReceived(
240
- Callback<
241
- ICoreWebView2WebMessageReceivedEventHandler>(
242
- [pImpl](ICoreWebView2 *sender,
243
- ICoreWebView2WebMessageReceivedEventArgs
244
- *args) -> HRESULT {
245
- (void)sender; // Suppress unused warning
246
- LPWSTR message = nullptr;
247
- args->TryGetWebMessageAsString(&message);
248
- if (!message)
249
- return S_OK;
250
- std::wstring wmsg(message);
251
- #pragma warning(push)
252
- #pragma warning(disable: 4244) // Suppress wchar_t to char conversion warning
253
- std::string msg(wmsg.begin(), wmsg.end());
254
- #pragma warning(pop)
255
- CoTaskMemFree(message);
256
-
257
- // Debug: log received message
258
- std::cout << "[PlusUI] Received: " << msg
259
- << std::endl;
260
-
261
- // Parse JSON-RPC: {"id":"...",
262
- // "method":"window.minimize", "params":[...]}
263
- std::string id, method;
264
- std::string result = "null";
265
- bool success = false;
266
-
267
- // Simple JSON parsing
268
- auto getId = [&msg]() {
269
- size_t pos = msg.find("\"id\"");
270
- if (pos == std::string::npos)
271
- return std::string();
272
- pos = msg.find(":", pos);
273
- if (pos == std::string::npos)
274
- return std::string();
275
- size_t start = msg.find("\"", pos);
276
- if (start == std::string::npos)
277
- return std::string();
278
- size_t end = msg.find("\"", start + 1);
279
- if (end == std::string::npos)
280
- return std::string();
281
- return msg.substr(start + 1,
282
- end - start - 1);
283
- };
284
- auto getMethod = [&msg]() {
285
- size_t pos = msg.find("\"method\"");
286
- if (pos == std::string::npos)
287
- return std::string();
288
- pos = msg.find(":", pos);
289
- if (pos == std::string::npos)
290
- return std::string();
291
- size_t start = msg.find("\"", pos);
292
- if (start == std::string::npos)
293
- return std::string();
294
- size_t end = msg.find("\"", start + 1);
295
- if (end == std::string::npos)
296
- return std::string();
297
- return msg.substr(start + 1,
298
- end - start - 1);
299
- };
300
-
301
- id = getId();
302
- method = getMethod();
303
-
304
- // Route to handlers
305
- if (method.find("window.") == 0) {
306
- std::string winMethod = method.substr(7);
307
- if (winMethod == "minimize") {
308
- if (pImpl->window) pImpl->window->minimize();
309
- success = true;
310
- } else if (winMethod == "maximize") {
311
- if (pImpl->window) pImpl->window->maximize();
312
- success = true;
313
- } else if (winMethod == "restore") {
314
- if (pImpl->window) pImpl->window->restore();
315
- success = true;
316
- } else if (winMethod == "close") {
317
- if (pImpl->window) pImpl->window->close();
318
- success = true;
319
- } else if (winMethod == "show") {
320
- if (pImpl->window) pImpl->window->show();
321
- success = true;
322
- } else if (winMethod == "hide") {
323
- if (pImpl->window) pImpl->window->hide();
324
- success = true;
325
- } else if (winMethod == "getSize") {
326
- if (pImpl->window) {
327
- int w, h;
328
- pImpl->window->getSize(w, h);
329
- result = "{\"width\":" +
330
- std::to_string(w) +
331
- ",\"height\":" +
332
- std::to_string(h) +
333
- "}";
334
- }
335
- success = true;
336
- } else if (winMethod == "getPosition") {
337
- if (pImpl->window) {
338
- int x, y;
339
- pImpl->window->getPosition(x, y);
340
- result =
341
- "{\"x\":" + std::to_string(x) +
342
- ",\"y\":" + std::to_string(y) +
343
- "}";
344
- }
345
- success = true;
346
- } else if (winMethod == "setSize") {
347
- // Parse params [width, height]
348
- size_t p1 = msg.find("[");
349
- size_t p2 = msg.find("]");
350
- if (p1 != std::string::npos &&
351
- p2 != std::string::npos) {
352
- std::string params =
353
- msg.substr(p1 + 1, p2 - p1 - 1);
354
- int w = 0, h = 0;
355
- sscanf(params.c_str(), "%d, %d", &w,
356
- &h);
357
- if (pImpl->window) pImpl->window->setSize(w, h);
358
- }
359
- success = true;
360
- } else if (winMethod == "setPosition") {
361
- size_t p1 = msg.find("[");
362
- size_t p2 = msg.find("]");
363
- if (p1 != std::string::npos &&
364
- p2 != std::string::npos) {
365
- std::string params =
366
- msg.substr(p1 + 1, p2 - p1 - 1);
367
- int x = 0, y = 0;
368
- sscanf(params.c_str(), "%d, %d", &x,
369
- &y);
370
- if (pImpl->window) pImpl->window->setPosition(x, y);
371
- }
372
- success = true;
373
- } else if (winMethod == "setTitle") {
374
- size_t p1 = msg.find("[\"");
375
- size_t p2 = msg.find("\"]");
376
- if (p1 != std::string::npos &&
377
- p2 != std::string::npos) {
378
- std::string title =
379
- msg.substr(p1 + 2, p2 - p1 - 2);
380
- if (pImpl->window) pImpl->window->setTitle(title);
381
- }
382
- success = true;
383
- } else if (winMethod == "setFullscreen") {
384
- size_t p1 = msg.find("[");
385
- size_t p2 = msg.find("]");
386
- if (p1 != std::string::npos &&
387
- p2 != std::string::npos) {
388
- std::string params =
389
- msg.substr(p1 + 1, p2 - p1 - 1);
390
- if (pImpl->window) pImpl->window->setFullscreen(
391
- params.find("true") !=
392
- std::string::npos);
393
- }
394
- success = true;
395
- } else if (winMethod == "setAlwaysOnTop") {
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->setAlwaysOnTop(
403
- params.find("true") !=
404
- std::string::npos);
405
- }
406
- success = true;
407
- } else if (winMethod == "setResizable") {
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->setResizable(
415
- params.find("true") !=
416
- std::string::npos);
417
- }
418
- success = true;
419
- } else if (winMethod == "isMaximized") {
420
- result =
421
- (pImpl->window && pImpl->window->isMaximized())
422
- ? "true"
423
- : "false";
424
- success = true;
425
- } else if (winMethod == "isMinimized") {
426
- result =
427
- (pImpl->window && pImpl->window->isMinimized())
428
- ? "true"
429
- : "false";
430
- success = true;
431
- } else if (winMethod == "isVisible") {
432
- result = (pImpl->window && pImpl->window->isVisible())
433
- ? "true"
434
- : "false";
435
- success = true;
436
- } else if (winMethod == "center") {
437
- if (pImpl->window) pImpl->window->center();
438
- success = true;
439
- }
440
- } else if (method.find("tray.") == 0) {
441
- std::string trayMethod = method.substr(5);
442
- if (trayMethod == "setIcon") {
443
- // TODO: implement
444
- success = true;
445
- } else if (trayMethod == "setTooltip") {
446
- // TODO: implement
447
- success = true;
448
- } else if (trayMethod == "setVisible") {
449
- // TODO: implement
450
- success = true;
451
- }
452
- } else if (method.find("browser.") == 0) {
453
- std::string browserMethod =
454
- method.substr(8);
455
- if (browserMethod == "navigate") {
456
- size_t p1 = msg.find("[\"");
457
- size_t p2 = msg.find("\"]");
458
- if (p1 != std::string::npos &&
459
- p2 != std::string::npos) {
460
- std::string url =
461
- msg.substr(p1 + 2, p2 - p1 - 2);
462
- pImpl->webview->Navigate(
463
- std::wstring(url.begin(), url.end())
464
- .c_str());
465
- }
466
- success = true;
467
- } else if (browserMethod == "goBack") {
468
- pImpl->webview->GoBack();
469
- success = true;
470
- } else if (browserMethod == "goForward") {
471
- pImpl->webview->GoForward();
472
- success = true;
473
- } else if (browserMethod == "reload") {
474
- pImpl->webview->Reload();
475
- success = true;
476
- } else if (browserMethod == "stop") {
477
- pImpl->webview->Stop();
478
- success = true;
479
- } else if (browserMethod == "getUrl") {
480
- result = "\"" + pImpl->currentURL + "\"";
481
- success = true;
482
- } else if (browserMethod == "getTitle") {
483
- result =
484
- "\"" + pImpl->currentTitle + "\"";
485
- success = true;
486
- } else if (browserMethod == "canGoBack") {
487
- BOOL canBack;
488
- pImpl->webview->get_CanGoBack(&canBack);
489
- result = canBack ? "true" : "false";
490
- success = true;
491
- } else if (browserMethod ==
492
- "canGoForward") {
493
- BOOL canForward;
494
- pImpl->webview->get_CanGoForward(
495
- &canForward);
496
- result = canForward ? "true" : "false";
497
- success = true;
498
- }
499
- } else if (method.find("app.") == 0) {
500
- std::string appMethod = method.substr(4);
501
- if (appMethod == "quit") {
502
- if (pImpl->window) pImpl->window->close();
503
- PostQuitMessage(0);
504
- success = true;
505
- }
506
- } else if (method.find("display.") == 0) {
507
- std::string displayMethod =
508
- method.substr(8);
509
- if (displayMethod == "getAll") {
510
- result =
511
- "[{\"id\":1,\"name\":\"Primary\","
512
- "\"x\":0,\"y\":0,\"width\":1920,"
513
- "\"height\":1080,\"scale\":1,"
514
- "\"isPrimary\":true}]";
515
- success = true;
516
- } else if (displayMethod == "getPrimary") {
517
- result = "{\"id\":1,\"name\":\"Primary\","
518
- "\"x\":0,\"y\":0,\"width\":1920,"
519
- "\"height\":1080,\"scale\":1,"
520
- "\"isPrimary\":true}";
521
- success = true;
522
- } else if (displayMethod == "getCurrent") {
523
- result = "{\"id\":1,\"name\":\"Primary\","
524
- "\"x\":0,\"y\":0,\"width\":1920,"
525
- "\"height\":1080,\"scale\":1,"
526
- "\"isPrimary\":true}";
527
- success = true;
528
- }
529
- } else if (method.find("clipboard.") == 0) {
530
- std::string clipMethod = method.substr(10);
531
- if (clipMethod == "writeText") {
532
- size_t p1 = msg.find("[\"");
533
- size_t p2 = msg.find("\"]");
534
- if (p1 != std::string::npos &&
535
- p2 != std::string::npos) {
536
- // TODO: implement clipboard write
537
- }
538
- success = true;
539
- } else if (clipMethod == "readText") {
540
- result = "\"\"";
541
- success = true;
542
- } else if (clipMethod == "clear") {
543
- success = true;
544
- }
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
+ }
545
557
  } else if (method.find("keyboard.") == 0) {
546
558
  // Keyboard API routing
547
559
  std::string kbMethod = method.substr(9);
@@ -634,38 +646,38 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
634
646
  "method: "
635
647
  << kbMethod << std::endl;
636
648
  }
637
- } else if (method.find("menu.") == 0) {
638
- // Menu API routing
639
- std::string menuMethod = method.substr(5);
640
- if (menuMethod == "create") {
641
- // TODO: integrate with
642
- // MenuBindings/ContextMenuManager
643
- result = "\"menu_" + id + "\"";
644
- success = true;
645
- } else if (menuMethod == "popup") {
646
- success = true;
647
- } else if (menuMethod == "popupAtCursor") {
648
- success = true;
649
- } else if (menuMethod == "close") {
650
- success = true;
651
- } else if (menuMethod == "destroy") {
652
- success = true;
653
- } else if (menuMethod ==
654
- "setApplicationMenu") {
655
- // TODO: integrate with MenuBar
656
- success = true;
657
- } else if (menuMethod ==
658
- "getApplicationMenu") {
659
- result = "[]";
660
- success = true;
661
- } else if (menuMethod ==
662
- "appendToMenuBar") {
663
- success = true;
664
- } else {
665
- std::cout
666
- << "[PlusUI] Unknown menu method: "
667
- << menuMethod << std::endl;
668
- }
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
+ }
669
681
  } else if (method.find("webview.") == 0) {
670
682
  // WebView API routing
671
683
  std::string webviewMethod = method.substr(8);
@@ -690,122 +702,122 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
690
702
  }
691
703
  success = true;
692
704
  }
693
- } else if (method.find("webgpu.") == 0) {
694
- // WebGPU API routing
695
- std::string webgpuMethod = method.substr(
696
- 7); // Remove "webgpu." prefix
697
-
698
- if (webgpuMethod == "requestAdapter") {
699
- result = pImpl->webgpu.requestAdapter(
700
- msg.substr(msg.find("["),
701
- msg.rfind("]") -
702
- msg.find("[") + 1));
703
- success = true;
704
- } else if (webgpuMethod ==
705
- "requestDevice") {
706
- // Extract adapterId and descriptor from
707
- // params
708
- size_t p1 = msg.find("[");
709
- size_t p2 = msg.find("]");
710
- if (p1 != std::string::npos &&
711
- p2 != std::string::npos) {
712
- std::string params =
713
- msg.substr(p1 + 1, p2 - p1 - 1);
714
- // TODO: Parse adapterId and descriptor
715
- // properly
716
- }
717
- success = true;
718
- } else if (webgpuMethod == "createBuffer") {
719
- success = true;
720
- } else if (webgpuMethod ==
721
- "createTexture") {
722
- success = true;
723
- } else if (webgpuMethod ==
724
- "createShaderModule") {
725
- success = true;
726
- } else if (webgpuMethod ==
727
- "createRenderPipeline") {
728
- success = true;
729
- } else if (webgpuMethod ==
730
- "createCommandEncoder") {
731
- success = true;
732
- } else if (webgpuMethod ==
733
- "submitCommands") {
734
- success = true;
735
- } else {
736
- // Unknown WebGPU method
737
- std::cout
738
- << "[PlusUI] Unknown WebGPU method: "
739
- << webgpuMethod << std::endl;
740
- }
741
- }
742
-
743
- // Send response back to JS (matches
744
- // plusui-native-core SDK bridge)
745
- std::string response =
746
- "window.__response__(\"" + id + "\", " +
747
- result + ");";
748
- pImpl->webview->ExecuteScript(
749
- std::wstring(response.begin(),
750
- response.end())
751
- .c_str(),
752
- nullptr);
753
-
754
- return S_OK;
755
- })
756
- .Get(),
757
- nullptr);
758
-
759
- // Process pending scripts
760
- for (const auto &script : pImpl->pendingScripts) {
761
- pImpl->webview->ExecuteScript(
762
- std::wstring(script.begin(), script.end())
763
- .c_str(),
764
- nullptr);
765
- }
766
- pImpl->pendingScripts.clear();
767
-
768
- // Process pending navigation
769
- if (!pImpl->pendingNavigation.empty()) {
770
- pImpl->webview->Navigate(
771
- std::wstring(pImpl->pendingNavigation.begin(),
772
- pImpl->pendingNavigation.end())
773
- .c_str());
774
- pImpl->pendingNavigation.clear();
775
- }
776
-
777
- // Process pending HTML
778
- if (!pImpl->pendingHTML.empty()) {
779
- pImpl->webview->NavigateToString(
780
- std::wstring(pImpl->pendingHTML.begin(),
781
- pImpl->pendingHTML.end())
782
- .c_str());
783
- pImpl->pendingHTML.clear();
784
- }
785
-
786
- // Process pending File
787
- if (!pImpl->pendingFile.empty()) {
788
- // For now, loadFile is implemented via navigate in
789
- // some versions or direct file reading. We'll handle
790
- // it via navigate for simplicity if it's already
791
- // implemented that way.
792
- }
793
- }
794
- return S_OK;
795
- })
796
- .Get());
797
- return S_OK;
798
- })
799
- .Get());
800
-
801
- #elif defined(__APPLE__)
802
- WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
803
- config.preferences.javaScriptEnabled = YES;
804
-
805
- if (win.pImpl->config.devtools) {
806
- [config.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
807
- }
808
-
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
+
809
821
  // Block browser default drag-drop while allowing drop zone visual feedback.
810
822
  // File delivery is handled natively by macOS drag APIs, not browser events.
811
823
  if (win.pImpl->config.disableWebviewDragDrop ||
@@ -862,18 +874,18 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
862
874
  forMainFrameOnly:NO];
863
875
  [config.userContentController addUserScript:userScript];
864
876
  }
865
-
866
- // Hide scrollbars if disabled
867
- if (!win.pImpl->config.scrollbars) {
868
- NSString *scrollbarScript = [NSString stringWithUTF8String:kHideScrollbarsScript];
869
-
870
- WKUserScript *scrollScript = [[WKUserScript alloc]
871
- initWithSource:scrollbarScript
872
- injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
873
- forMainFrameOnly:NO];
874
- [config.userContentController addUserScript:scrollScript];
875
- }
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
889
  NSView *parentView = (__bridge NSView *)windowHandle;
878
890
  win.pImpl->wkWebView =
879
891
  [[WKWebView alloc] initWithFrame:parentView.bounds configuration:config];
@@ -963,406 +975,406 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
963
975
  }
964
976
 
965
977
  #endif
966
-
967
- win.pImpl->trayManager = std::make_unique<TrayManager>();
968
- win.pImpl->trayManager->setWindowHandle(windowHandle);
969
-
970
- return win;
971
- }
972
-
973
- TrayManager &Window::tray() { return *pImpl->trayManager; }
974
-
975
- void Window::setWindow(std::shared_ptr<Window> win) {
976
- pImpl->window = win;
977
- if (!win)
978
- return;
979
-
980
- std::weak_ptr<Impl> weak_pImpl = pImpl;
981
- win->onResize([weak_pImpl](int w, int h) {
982
- if (auto pImpl = weak_pImpl.lock()) {
983
- #ifdef _WIN32
984
- if (pImpl->controller) {
985
- RECT bounds = {0, 0, w, h};
986
- pImpl->controller->put_Bounds(bounds);
987
- }
988
- #elif defined(__APPLE__)
989
- if (pImpl->wkWebView) {
990
- NSView *view = (__bridge NSView *)pImpl->wkWebView;
991
- NSView *parent = [view superview];
992
- if (parent) {
993
- [view setFrame:[parent bounds]];
994
- }
995
- }
996
- #else
997
- if (pImpl->gtkWebView) {
998
- // GTK usually handles this if added to a container with expand=TRUE
999
- // but we can ensure it here if it's a fixed layout parent
1000
- gtk_widget_set_size_request(GTK_WIDGET(pImpl->gtkWebView), w, h);
1001
- }
1002
- #endif
1003
- }
1004
- });
1005
-
1006
- // Trigger initial resize
1007
- int w, h;
1008
- win->getSize(w, h);
1009
- #ifdef _WIN32
1010
- if (pImpl->controller) {
1011
- RECT bounds = {0, 0, w, h};
1012
- pImpl->controller->put_Bounds(bounds);
1013
- }
1014
- #endif
1015
- }
1016
-
1017
- void Window::on(const std::string &event,
1018
- std::function<void(const std::string &)> callback) {
1019
- pImpl->events[event] = callback;
1020
- }
1021
-
1022
- void Window::emit(const std::string &event, const std::string &data) {
1023
- std::string js = "window.dispatchEvent(new CustomEvent('" + event +
1024
- "', {detail: " + data + "}))";
1025
- executeScript(js);
1026
- }
1027
-
1028
- void Window::navigate(const std::string &url) {
1029
- pImpl->currentURL = url;
1030
- pImpl->loading = true;
1031
-
1032
- #ifdef _WIN32
1033
- if (pImpl->ready && pImpl->webview) {
1034
- pImpl->webview->Navigate(std::wstring(url.begin(), url.end()).c_str());
1035
- } else {
1036
- pImpl->pendingNavigation = url;
1037
- }
1038
- #elif defined(__APPLE__)
1039
- if (pImpl->wkWebView) {
1040
- NSURL *nsurl =
1041
- [NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]];
1042
- NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
1043
- [pImpl->wkWebView loadRequest:request];
1044
- }
1045
- #else
1046
- if (pImpl->gtkWebView) {
1047
- webkit_web_view_load_uri(pImpl->gtkWebView, url.c_str());
1048
- }
1049
- #endif
1050
- }
1051
-
1052
- void Window::loadHTML(const std::string &html) {
1053
- loadHTML(html, "about:blank");
1054
- }
1055
-
1056
- void Window::loadHTML(const std::string &html, const std::string &baseURL) {
1057
- pImpl->loading = true;
1058
-
1059
- #ifdef _WIN32
1060
- (void)baseURL; // Not used on Windows
1061
- if (pImpl->ready && pImpl->webview) {
1062
- pImpl->webview->NavigateToString(
1063
- std::wstring(html.begin(), html.end()).c_str());
1064
- } else {
1065
- pImpl->pendingHTML = html;
1066
- }
1067
- #elif defined(__APPLE__)
1068
- if (pImpl->wkWebView) {
1069
- NSString *htmlString = [NSString stringWithUTF8String:html.c_str()];
1070
- NSURL *base =
1071
- [NSURL URLWithString:[NSString stringWithUTF8String:baseURL.c_str()]];
1072
- [pImpl->wkWebView loadHTMLString:htmlString baseURL:base];
1073
- }
1074
- #else
1075
- if (pImpl->gtkWebView) {
1076
- webkit_web_view_load_html(pImpl->gtkWebView, html.c_str(), baseURL.c_str());
1077
- }
1078
- #endif
1079
- }
1080
-
1081
- void Window::loadFile(const std::string &filePath) {
1082
- std::ifstream file(filePath);
1083
- if (!file) {
1084
- std::cerr << "PlusUI: Failed to open file: " << filePath << std::endl;
1085
- return;
1086
- }
1087
-
1088
- std::stringstream buffer;
1089
- buffer << file.rdbuf();
1090
-
1091
- // Use file:// URL as base for relative paths
1092
- std::string baseURL =
1093
- "file://" + filePath.substr(0, filePath.find_last_of("/\\") + 1);
1094
- loadHTML(buffer.str(), baseURL);
1095
- }
1096
-
1097
- void Window::reload() {
1098
- #ifdef _WIN32
1099
- if (pImpl->webview) {
1100
- pImpl->webview->Reload();
1101
- }
1102
- #elif defined(__APPLE__)
1103
- if (pImpl->wkWebView) {
1104
- [pImpl->wkWebView reload];
1105
- }
1106
- #else
1107
- if (pImpl->gtkWebView) {
1108
- webkit_web_view_reload(pImpl->gtkWebView);
1109
- }
1110
- #endif
1111
- }
1112
-
1113
- void Window::stop() {
1114
- #ifdef _WIN32
1115
- if (pImpl->webview) {
1116
- pImpl->webview->Stop();
1117
- }
1118
- #elif defined(__APPLE__)
1119
- if (pImpl->wkWebView) {
1120
- [pImpl->wkWebView stopLoading];
1121
- }
1122
- #else
1123
- if (pImpl->gtkWebView) {
1124
- webkit_web_view_stop_loading(pImpl->gtkWebView);
1125
- }
1126
- #endif
1127
- }
1128
-
1129
- void Window::goBack() {
1130
- #ifdef _WIN32
1131
- if (pImpl->webview) {
1132
- pImpl->webview->GoBack();
1133
- }
1134
- #elif defined(__APPLE__)
1135
- if (pImpl->wkWebView) {
1136
- [pImpl->wkWebView goBack];
1137
- }
1138
- #else
1139
- if (pImpl->gtkWebView) {
1140
- webkit_web_view_go_back(pImpl->gtkWebView);
1141
- }
1142
- #endif
1143
- }
1144
-
1145
- void Window::goForward() {
1146
- #ifdef _WIN32
1147
- if (pImpl->webview) {
1148
- pImpl->webview->GoForward();
1149
- }
1150
- #elif defined(__APPLE__)
1151
- if (pImpl->wkWebView) {
1152
- [pImpl->wkWebView goForward];
1153
- }
1154
- #else
1155
- if (pImpl->gtkWebView) {
1156
- webkit_web_view_go_forward(pImpl->gtkWebView);
1157
- }
1158
- #endif
1159
- }
1160
-
1161
- bool Window::canGoBack() const {
1162
- #ifdef _WIN32
1163
- if (pImpl->webview) {
1164
- BOOL canGoBack;
1165
- pImpl->webview->get_CanGoBack(&canGoBack);
1166
- return canGoBack;
1167
- }
1168
- #elif defined(__APPLE__)
1169
- if (pImpl->wkWebView) {
1170
- return [pImpl->wkWebView canGoBack];
1171
- }
1172
- #else
1173
- if (pImpl->gtkWebView) {
1174
- return webkit_web_view_can_go_back(pImpl->gtkWebView);
1175
- }
1176
- #endif
1177
- return false;
1178
- }
1179
-
1180
- bool Window::canGoForward() const {
1181
- #ifdef _WIN32
1182
- if (pImpl->webview) {
1183
- BOOL canGoForward;
1184
- pImpl->webview->get_CanGoForward(&canGoForward);
1185
- return canGoForward;
1186
- }
1187
- #elif defined(__APPLE__)
1188
- if (pImpl->wkWebView) {
1189
- return [pImpl->wkWebView canGoForward];
1190
- }
1191
- #else
1192
- if (pImpl->gtkWebView) {
1193
- return webkit_web_view_can_go_forward(pImpl->gtkWebView);
1194
- }
1195
- #endif
1196
- return false;
1197
- }
1198
-
1199
- void Window::executeScript(const std::string &script) {
1200
- #ifdef _WIN32
1201
- if (pImpl->ready && pImpl->webview) {
1202
- pImpl->webview->ExecuteScript(
1203
- std::wstring(script.begin(), script.end()).c_str(), nullptr);
1204
- } else {
1205
- pImpl->pendingScripts.push_back(script);
1206
- }
1207
- #elif defined(__APPLE__)
1208
- if (pImpl->wkWebView) {
1209
- NSString *js = [NSString stringWithUTF8String:script.c_str()];
1210
- [pImpl->wkWebView evaluateJavaScript:js completionHandler:nil];
1211
- }
1212
- #else
1213
- if (pImpl->gtkWebView) {
1214
- webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
1215
- nullptr, nullptr);
1216
- }
1217
- #endif
1218
- }
1219
-
1220
- void Window::executeScript(const std::string &script,
1221
- std::function<void(const std::string &)> callback) {
1222
- #ifdef _WIN32
1223
- if (pImpl->webview) {
1224
- pImpl->webview->ExecuteScript(
1225
- std::wstring(script.begin(), script.end()).c_str(),
1226
- Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
1227
- [callback](HRESULT error, LPCWSTR result) -> HRESULT {
1228
- (void)error; // Suppress unused warning
1229
- if (result && callback) {
1230
- std::wstring wstr(result);
1231
- callback(std::string(wstr.begin(), wstr.end()));
1232
- }
1233
- return S_OK;
1234
- })
1235
- .Get());
1236
- }
1237
- #elif defined(__APPLE__)
1238
- if (pImpl->wkWebView) {
1239
- NSString *js = [NSString stringWithUTF8String:script.c_str()];
1240
- [pImpl->wkWebView evaluateJavaScript:js
1241
- completionHandler:^(id result, NSError *error) {
1242
- if (result && callback) {
1243
- NSString *resultStr =
1244
- [NSString stringWithFormat:@"%@", result];
1245
- callback([resultStr UTF8String]);
1246
- }
1247
- }];
1248
- }
1249
- #else
1250
- if (pImpl->gtkWebView) {
1251
- // GTK WebKit callback handling would go here
1252
- webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
1253
- nullptr, nullptr);
1254
- }
1255
- #endif
1256
- }
1257
-
1258
- void Window::bind(const std::string &name, JSCallback callback) {
1259
- pImpl->bindings[name] = callback;
1260
-
1261
- // Inject bridge code to expose function to JavaScript
1262
- std::string bridgeScript = R"(
1263
- window.)" + name + R"( = function(...args) {
1264
- return window.plusui.invoke('webview.)" +
1265
- name + R"(', args);
1266
- };
1267
- )";
1268
-
1269
- executeScript(bridgeScript);
1270
- }
1271
-
1272
- void Window::unbind(const std::string &name) {
1273
- pImpl->bindings.erase(name);
1274
-
1275
- std::string script = "delete window." + name + ";";
1276
- executeScript(script);
1277
- }
1278
-
1279
- void Window::openDevTools() {
1280
- #ifdef _WIN32
1281
- if (pImpl->webview) {
1282
- pImpl->webview->OpenDevToolsWindow();
1283
- }
1284
- #elif defined(__APPLE__)
1285
- // macOS: Dev tools open in Safari's Web Inspector
1286
- #else
1287
- if (pImpl->gtkWebView) {
1288
- WebKitWebInspector *inspector =
1289
- webkit_web_view_get_inspector(pImpl->gtkWebView);
1290
- webkit_web_inspector_show(inspector);
1291
- }
1292
- #endif
1293
- }
1294
-
1295
- void Window::closeDevTools() {
1296
- #ifdef _WIN32
1297
- // WebView2 doesn't have explicit close
1298
- #elif defined(__APPLE__)
1299
- // macOS: Handled by Web Inspector
1300
- #else
1301
- if (pImpl->gtkWebView) {
1302
- WebKitWebInspector *inspector =
1303
- webkit_web_view_get_inspector(pImpl->gtkWebView);
1304
- webkit_web_inspector_close(inspector);
1305
- }
1306
- #endif
1307
- }
1308
-
1309
- void Window::setUserAgent(const std::string &userAgent) {
1310
- pImpl->userAgent = userAgent;
1311
-
1312
- #ifdef _WIN32
1313
- if (pImpl->webview) {
1314
- ComPtr<ICoreWebView2Settings> settings;
1315
- pImpl->webview->get_Settings(&settings);
1316
- // WebView2 user agent requires settings2 interface
1317
- }
1318
- #elif defined(__APPLE__)
1319
- if (pImpl->wkWebView) {
1320
- [pImpl->wkWebView
1321
- setCustomUserAgent:[NSString stringWithUTF8String:userAgent.c_str()]];
1322
- }
1323
- #else
1324
- if (pImpl->gtkWebView) {
1325
- WebKitSettings *settings = webkit_web_view_get_settings(pImpl->gtkWebView);
1326
- webkit_settings_set_user_agent(settings, userAgent.c_str());
1327
- }
1328
- #endif
1329
- }
1330
-
1331
- std::string Window::getUserAgent() const { return pImpl->userAgent; }
1332
-
1333
- void Window::setZoom(double factor) {
1334
- pImpl->zoom = factor;
1335
-
1336
- #ifdef _WIN32
1337
- if (pImpl->controller) {
1338
- pImpl->controller->put_ZoomFactor(factor);
1339
- }
1340
- #elif defined(__APPLE__)
1341
- if (pImpl->wkWebView) {
1342
- [pImpl->wkWebView setPageZoom:factor];
1343
- }
1344
- #else
1345
- if (pImpl->gtkWebView) {
1346
- webkit_web_view_set_zoom_level(pImpl->gtkWebView, factor);
1347
- }
1348
- #endif
1349
- }
1350
-
1351
- double Window::getZoom() const { return pImpl->zoom; }
1352
-
1353
- void Window::injectCSS(const std::string &css) {
1354
- std::string script = R"(
1355
- (function() {
1356
- const style = document.createElement('style');
1357
- style.textContent = `)" +
1358
- css + R"(`;
1359
- document.head.appendChild(style);
1360
- })();
1361
- )";
1362
-
1363
- executeScript(script);
1364
- }
1365
-
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
+
1366
1378
  void Window::setWebviewDragDropEnabled(bool enabled) {
1367
1379
  if (enabled) {
1368
1380
  executeScript(R"(
@@ -1430,37 +1442,37 @@ void Window::setWebviewDragDropEnabled(bool enabled) {
1430
1442
  )");
1431
1443
  }
1432
1444
  }
1433
-
1434
- void Window::onNavigationStart(NavigationCallback callback) {
1435
- pImpl->navigationCallback = callback;
1436
- }
1437
-
1438
- void Window::onNavigationComplete(LoadCallback callback) {
1439
- pImpl->navigationCompleteCallback = callback;
1440
- }
1441
-
1442
- void Window::onLoadStart(LoadCallback callback) {
1443
- pImpl->loadStartCallback = callback;
1444
- }
1445
-
1446
- void Window::onLoadEnd(LoadCallback callback) {
1447
- pImpl->loadEndCallback = callback;
1448
- }
1449
-
1450
- void Window::onLoadError(ErrorCallback callback) {
1451
- pImpl->errorCallback = callback;
1452
- }
1453
-
1454
- void Window::onConsoleMessage(ConsoleCallback callback) {
1455
- pImpl->consoleCallback = callback;
1456
- }
1457
-
1458
- bool Window::isLoading() const { return pImpl->loading; }
1459
-
1460
- std::string Window::getURL() const { return pImpl->currentURL; }
1461
-
1462
- std::string Window::getTitle() const { return pImpl->currentTitle; }
1463
-
1464
- void *Window::nativeHandle() const { return pImpl->nativeWebView; }
1465
-
1466
- } // 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