plusui-native-core 0.1.4 → 0.1.5

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 (54) hide show
  1. package/Core/CMakeLists.txt +190 -7
  2. package/Core/Features/App/app.cpp +129 -0
  3. package/Core/Features/App/app.ts +126 -0
  4. package/Core/Features/Browser/browser.cpp +181 -0
  5. package/Core/Features/Browser/browser.ts +182 -0
  6. package/Core/Features/Clipboard/clipboard.cpp +234 -0
  7. package/Core/Features/Clipboard/clipboard.ts +113 -0
  8. package/Core/Features/Display/display.cpp +209 -0
  9. package/Core/Features/Display/display.ts +104 -0
  10. package/Core/Features/Event/Events.ts +166 -0
  11. package/Core/Features/Event/events.cpp +200 -0
  12. package/Core/Features/Keyboard/keyboard.cpp +186 -0
  13. package/Core/Features/Keyboard/keyboard.ts +175 -0
  14. package/Core/Features/Menu/context-menu.css +293 -0
  15. package/Core/Features/Menu/menu.cpp +481 -0
  16. package/Core/Features/Menu/menu.ts +439 -0
  17. package/Core/Features/Tray/tray.cpp +310 -0
  18. package/Core/Features/Tray/tray.ts +68 -0
  19. package/Core/Features/WebGPU/webgpu.cpp +937 -0
  20. package/Core/Features/WebGPU/webgpu.ts +1013 -0
  21. package/Core/Features/WebView/webview.cpp +1052 -0
  22. package/Core/Features/WebView/webview.ts +510 -0
  23. package/Core/Features/Window/window.cpp +664 -0
  24. package/Core/Features/Window/window.ts +142 -0
  25. package/Core/Features/WindowManager/window_manager.cpp +341 -0
  26. package/Core/include/plusui/app.hpp +73 -0
  27. package/Core/include/plusui/browser.hpp +66 -0
  28. package/Core/include/plusui/clipboard.hpp +41 -0
  29. package/Core/include/plusui/events.hpp +58 -0
  30. package/Core/include/{keyboard.hpp → plusui/keyboard.hpp} +21 -44
  31. package/Core/include/plusui/menu.hpp +153 -0
  32. package/Core/include/plusui/tray.hpp +93 -0
  33. package/Core/include/plusui/webgpu.hpp +434 -0
  34. package/Core/include/plusui/webview.hpp +142 -0
  35. package/Core/include/plusui/window.hpp +111 -0
  36. package/Core/include/plusui/window_manager.hpp +57 -0
  37. package/Core/vendor/WebView2EnvironmentOptions.h +406 -0
  38. package/Core/vendor/stb_image.h +7988 -0
  39. package/Core/vendor/webview.h +618 -510
  40. package/Core/vendor/webview2.h +52079 -0
  41. package/README.md +19 -0
  42. package/package.json +12 -15
  43. package/Core/include/app.hpp +0 -121
  44. package/Core/include/menu.hpp +0 -79
  45. package/Core/include/tray.hpp +0 -81
  46. package/Core/include/window.hpp +0 -106
  47. package/Core/src/app.cpp +0 -311
  48. package/Core/src/display.cpp +0 -424
  49. package/Core/src/tray.cpp +0 -275
  50. package/Core/src/window.cpp +0 -528
  51. package/dist/index.d.ts +0 -205
  52. package/dist/index.js +0 -198
  53. package/src/index.ts +0 -574
  54. /package/Core/include/{display.hpp → plusui/display.hpp} +0 -0
@@ -0,0 +1,1052 @@
1
+ #include <fstream>
2
+ #include <iostream>
3
+ #include <map>
4
+ #include <plusui/tray.hpp>
5
+ #include <plusui/webgpu.hpp>
6
+ #include <plusui/webview.hpp>
7
+ #include <plusui/window.hpp>
8
+ #include <plusui/window_manager.hpp>
9
+ #include <regex>
10
+ #include <sstream>
11
+
12
+ #ifdef _WIN32
13
+ #include <WebView2.h>
14
+ #include <windows.h>
15
+ #include <wrl.h>
16
+ using namespace Microsoft::WRL;
17
+ #elif defined(__APPLE__)
18
+ #import <WebKit/WebKit.h>
19
+ #import <objc/objc-runtime.h>
20
+ #else
21
+ #include <webkit2/webkit2.h>
22
+ #endif
23
+
24
+ namespace plusui {
25
+
26
+ struct WebView::Impl {
27
+ void *nativeWebView = nullptr;
28
+ WebViewConfig config;
29
+ std::string currentURL;
30
+ std::string currentTitle;
31
+ bool loading = false;
32
+ double zoom = 1.0;
33
+ bool ready = false;
34
+ std::vector<std::string> pendingScripts;
35
+ std::string pendingNavigation;
36
+ std::string pendingHTML;
37
+ std::string pendingFile;
38
+
39
+ std::map<std::string, JSCallback> bindings;
40
+
41
+ std::unique_ptr<TrayManager> trayManager;
42
+ std::unique_ptr<WindowManager> windowManager;
43
+ std::shared_ptr<Window> window; // Keep the window alive
44
+ std::map<std::string, std::function<void(const std::string &)>> events;
45
+ WebGPU webgpu; // WebGPU support
46
+
47
+ NavigationCallback navigationCallback;
48
+ LoadCallback loadStartCallback;
49
+ LoadCallback loadEndCallback;
50
+ LoadCallback navigationCompleteCallback;
51
+ ErrorCallback errorCallback;
52
+ ConsoleCallback consoleCallback;
53
+
54
+ #ifdef _WIN32
55
+ ComPtr<ICoreWebView2Controller> controller;
56
+ ComPtr<ICoreWebView2> webview;
57
+ #elif defined(__APPLE__)
58
+ WKWebView *wkWebView = nullptr;
59
+ #else
60
+ WebKitWebView *gtkWebView = nullptr;
61
+ #endif
62
+ };
63
+
64
+ WebView::WebView() : pImpl(std::shared_ptr<Impl>(new Impl())) {}
65
+
66
+ WebView::~WebView() = default;
67
+
68
+ WebView::WebView(WebView &&other) noexcept = default;
69
+ WebView &WebView::operator=(WebView &&other) noexcept = default;
70
+
71
+ WebView WebView::create(void *windowHandle, const WebViewConfig &config) {
72
+ WebView wv;
73
+ wv.pImpl->config = config;
74
+
75
+ #ifdef _WIN32
76
+ HWND hwnd = static_cast<HWND>(windowHandle);
77
+
78
+ // Create WebView2 environment and controller
79
+ auto pImpl = wv.pImpl;
80
+ CreateCoreWebView2EnvironmentWithOptions(
81
+ nullptr, nullptr, nullptr,
82
+ Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
83
+ [hwnd, pImpl](HRESULT result,
84
+ ICoreWebView2Environment *env) -> HRESULT {
85
+ if (FAILED(result) || !env)
86
+ return result;
87
+ env->CreateCoreWebView2Controller(
88
+ hwnd,
89
+ Callback<
90
+ ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
91
+ [pImpl](HRESULT result,
92
+ ICoreWebView2Controller *controller) -> HRESULT {
93
+ if (controller != nullptr) {
94
+ pImpl->controller = controller;
95
+ controller->get_CoreWebView2(&pImpl->webview);
96
+
97
+ RECT bounds;
98
+ HWND parentHwnd;
99
+ controller->get_ParentWindow(&parentHwnd);
100
+ GetClientRect(parentHwnd, &bounds);
101
+ controller->put_Bounds(bounds);
102
+
103
+ pImpl->nativeWebView = pImpl->webview.Get();
104
+ pImpl->ready = true;
105
+
106
+ // Inject bridge script that runs on EVERY document
107
+ // (survives navigation)
108
+ std::string bridgeScript = R"(
109
+ window.__native_invoke__ = function(request) {
110
+ if (window.chrome && window.chrome.webview) {
111
+ window.chrome.webview.postMessage(request);
112
+ }
113
+ };
114
+ )";
115
+ pImpl->webview->AddScriptToExecuteOnDocumentCreated(
116
+ std::wstring(bridgeScript.begin(),
117
+ bridgeScript.end())
118
+ .c_str(),
119
+ nullptr);
120
+
121
+ // Inject scrollbar hiding CSS if disabled
122
+ if (!pImpl->config.scrollbars) {
123
+ std::string scrollbarScript = R"(
124
+ (function() {
125
+ var css = 'html, body { overflow: hidden !important; margin: 0 !important; padding: 0 !important; } ::-webkit-scrollbar { display: none !important; width: 0 !important; height: 0 !important; }';
126
+ var style = document.createElement('style');
127
+ style.type = 'text/css';
128
+ style.appendChild(document.createTextNode(css));
129
+ (document.head || document.documentElement).appendChild(style);
130
+ })();
131
+ )";
132
+ pImpl->webview->AddScriptToExecuteOnDocumentCreated(
133
+ std::wstring(scrollbarScript.begin(),
134
+ scrollbarScript.end())
135
+ .c_str(),
136
+ nullptr);
137
+ }
138
+
139
+ // Set up WebMessageReceived handler for JS->C++ bridge
140
+ pImpl->webview->add_WebMessageReceived(
141
+ Callback<
142
+ ICoreWebView2WebMessageReceivedEventHandler>(
143
+ [pImpl](ICoreWebView2 *sender,
144
+ ICoreWebView2WebMessageReceivedEventArgs
145
+ *args) -> HRESULT {
146
+ LPWSTR message = nullptr;
147
+ args->TryGetWebMessageAsString(&message);
148
+ if (!message)
149
+ return S_OK;
150
+ std::wstring wmsg(message);
151
+ std::string msg(wmsg.begin(), wmsg.end());
152
+ CoTaskMemFree(message);
153
+
154
+ // Debug: log received message
155
+ std::cout << "[PlusUI] Received: " << msg
156
+ << std::endl;
157
+
158
+ // Parse JSON-RPC: {"id":"...",
159
+ // "method":"window.minimize", "params":[...]}
160
+ std::string id, method;
161
+ std::string result = "null";
162
+ bool success = false;
163
+
164
+ // Simple JSON parsing
165
+ auto getId = [&msg]() {
166
+ size_t pos = msg.find("\"id\"");
167
+ if (pos == std::string::npos)
168
+ return std::string();
169
+ pos = msg.find(":", pos);
170
+ if (pos == std::string::npos)
171
+ return std::string();
172
+ size_t start = msg.find("\"", pos);
173
+ if (start == std::string::npos)
174
+ return std::string();
175
+ size_t end = msg.find("\"", start + 1);
176
+ if (end == std::string::npos)
177
+ return std::string();
178
+ return msg.substr(start + 1,
179
+ end - start - 1);
180
+ };
181
+ auto getMethod = [&msg]() {
182
+ size_t pos = msg.find("\"method\"");
183
+ if (pos == std::string::npos)
184
+ return std::string();
185
+ pos = msg.find(":", pos);
186
+ if (pos == std::string::npos)
187
+ return std::string();
188
+ size_t start = msg.find("\"", pos);
189
+ if (start == std::string::npos)
190
+ return std::string();
191
+ size_t end = msg.find("\"", start + 1);
192
+ if (end == std::string::npos)
193
+ return std::string();
194
+ return msg.substr(start + 1,
195
+ end - start - 1);
196
+ };
197
+
198
+ id = getId();
199
+ method = getMethod();
200
+
201
+ // Route to handlers
202
+ if (method.find("window.") == 0) {
203
+ std::string winMethod = method.substr(7);
204
+ if (winMethod == "minimize") {
205
+ pImpl->windowManager->minimize();
206
+ success = true;
207
+ } else if (winMethod == "maximize") {
208
+ pImpl->windowManager->maximize();
209
+ success = true;
210
+ } else if (winMethod == "restore") {
211
+ pImpl->windowManager->restore();
212
+ success = true;
213
+ } else if (winMethod == "close") {
214
+ pImpl->windowManager->close();
215
+ success = true;
216
+ } else if (winMethod == "show") {
217
+ pImpl->windowManager->show();
218
+ success = true;
219
+ } else if (winMethod == "hide") {
220
+ pImpl->windowManager->hide();
221
+ success = true;
222
+ } else if (winMethod == "getSize") {
223
+ auto size =
224
+ pImpl->windowManager->getSize();
225
+ result = "{\"width\":" +
226
+ std::to_string(size.width) +
227
+ ",\"height\":" +
228
+ std::to_string(size.height) +
229
+ "}";
230
+ success = true;
231
+ } else if (winMethod == "getPosition") {
232
+ auto pos =
233
+ pImpl->windowManager->getPosition();
234
+ result =
235
+ "{\"x\":" + std::to_string(pos.x) +
236
+ ",\"y\":" + std::to_string(pos.y) +
237
+ "}";
238
+ success = true;
239
+ } else if (winMethod == "setSize") {
240
+ // Parse params [width, height]
241
+ size_t p1 = msg.find("[");
242
+ size_t p2 = msg.find("]");
243
+ if (p1 != std::string::npos &&
244
+ p2 != std::string::npos) {
245
+ std::string params =
246
+ msg.substr(p1 + 1, p2 - p1 - 1);
247
+ int w = 0, h = 0;
248
+ sscanf(params.c_str(), "%d, %d", &w,
249
+ &h);
250
+ pImpl->windowManager->setSize(w, h);
251
+ }
252
+ success = true;
253
+ } else if (winMethod == "setPosition") {
254
+ size_t p1 = msg.find("[");
255
+ size_t p2 = msg.find("]");
256
+ if (p1 != std::string::npos &&
257
+ p2 != std::string::npos) {
258
+ std::string params =
259
+ msg.substr(p1 + 1, p2 - p1 - 1);
260
+ int x = 0, y = 0;
261
+ sscanf(params.c_str(), "%d, %d", &x,
262
+ &y);
263
+ pImpl->windowManager->setPosition(x, y);
264
+ }
265
+ success = true;
266
+ } else if (winMethod == "setTitle") {
267
+ size_t p1 = msg.find("[\"");
268
+ size_t p2 = msg.find("\"]");
269
+ if (p1 != std::string::npos &&
270
+ p2 != std::string::npos) {
271
+ std::string title =
272
+ msg.substr(p1 + 2, p2 - p1 - 2);
273
+ pImpl->windowManager->setTitle(title);
274
+ }
275
+ success = true;
276
+ } else if (winMethod == "setFullscreen") {
277
+ size_t p1 = msg.find("[");
278
+ size_t p2 = msg.find("]");
279
+ if (p1 != std::string::npos &&
280
+ p2 != std::string::npos) {
281
+ std::string params =
282
+ msg.substr(p1 + 1, p2 - p1 - 1);
283
+ pImpl->windowManager->setFullscreen(
284
+ params.find("true") !=
285
+ std::string::npos);
286
+ }
287
+ success = true;
288
+ } else if (winMethod == "setAlwaysOnTop") {
289
+ size_t p1 = msg.find("[");
290
+ size_t p2 = msg.find("]");
291
+ if (p1 != std::string::npos &&
292
+ p2 != std::string::npos) {
293
+ std::string params =
294
+ msg.substr(p1 + 1, p2 - p1 - 1);
295
+ pImpl->windowManager->setAlwaysOnTop(
296
+ params.find("true") !=
297
+ std::string::npos);
298
+ }
299
+ success = true;
300
+ } else if (winMethod == "setResizable") {
301
+ size_t p1 = msg.find("[");
302
+ size_t p2 = msg.find("]");
303
+ if (p1 != std::string::npos &&
304
+ p2 != std::string::npos) {
305
+ std::string params =
306
+ msg.substr(p1 + 1, p2 - p1 - 1);
307
+ pImpl->windowManager->setResizable(
308
+ params.find("true") !=
309
+ std::string::npos);
310
+ }
311
+ success = true;
312
+ } else if (winMethod == "isMaximized") {
313
+ result =
314
+ pImpl->windowManager->isMaximized()
315
+ ? "true"
316
+ : "false";
317
+ success = true;
318
+ } else if (winMethod == "isMinimized") {
319
+ result =
320
+ pImpl->windowManager->isMinimized()
321
+ ? "true"
322
+ : "false";
323
+ success = true;
324
+ } else if (winMethod == "isVisible") {
325
+ result = pImpl->windowManager->isVisible()
326
+ ? "true"
327
+ : "false";
328
+ success = true;
329
+ } else if (winMethod == "center") {
330
+ pImpl->windowManager->setCenter();
331
+ success = true;
332
+ }
333
+ } else if (method.find("tray.") == 0) {
334
+ std::string trayMethod = method.substr(5);
335
+ if (trayMethod == "setIcon") {
336
+ // TODO: implement
337
+ success = true;
338
+ } else if (trayMethod == "setTooltip") {
339
+ // TODO: implement
340
+ success = true;
341
+ } else if (trayMethod == "setVisible") {
342
+ // TODO: implement
343
+ success = true;
344
+ }
345
+ } else if (method.find("browser.") == 0) {
346
+ std::string browserMethod =
347
+ method.substr(8);
348
+ if (browserMethod == "navigate") {
349
+ size_t p1 = msg.find("[\"");
350
+ size_t p2 = msg.find("\"]");
351
+ if (p1 != std::string::npos &&
352
+ p2 != std::string::npos) {
353
+ std::string url =
354
+ msg.substr(p1 + 2, p2 - p1 - 2);
355
+ pImpl->webview->Navigate(
356
+ std::wstring(url.begin(), url.end())
357
+ .c_str());
358
+ }
359
+ success = true;
360
+ } else if (browserMethod == "goBack") {
361
+ pImpl->webview->GoBack();
362
+ success = true;
363
+ } else if (browserMethod == "goForward") {
364
+ pImpl->webview->GoForward();
365
+ success = true;
366
+ } else if (browserMethod == "reload") {
367
+ pImpl->webview->Reload();
368
+ success = true;
369
+ } else if (browserMethod == "stop") {
370
+ pImpl->webview->Stop();
371
+ success = true;
372
+ } else if (browserMethod == "getUrl") {
373
+ result = "\"" + pImpl->currentURL + "\"";
374
+ success = true;
375
+ } else if (browserMethod == "getTitle") {
376
+ result =
377
+ "\"" + pImpl->currentTitle + "\"";
378
+ success = true;
379
+ } else if (browserMethod == "canGoBack") {
380
+ BOOL canBack;
381
+ pImpl->webview->get_CanGoBack(&canBack);
382
+ result = canBack ? "true" : "false";
383
+ success = true;
384
+ } else if (browserMethod ==
385
+ "canGoForward") {
386
+ BOOL canForward;
387
+ pImpl->webview->get_CanGoForward(
388
+ &canForward);
389
+ result = canForward ? "true" : "false";
390
+ success = true;
391
+ }
392
+ } else if (method.find("app.") == 0) {
393
+ std::string appMethod = method.substr(4);
394
+ if (appMethod == "quit") {
395
+ pImpl->windowManager->close();
396
+ PostQuitMessage(0);
397
+ success = true;
398
+ }
399
+ } else if (method.find("display.") == 0) {
400
+ std::string displayMethod =
401
+ method.substr(8);
402
+ if (displayMethod == "getAll") {
403
+ result =
404
+ "[{\"id\":1,\"name\":\"Primary\","
405
+ "\"x\":0,\"y\":0,\"width\":1920,"
406
+ "\"height\":1080,\"scale\":1,"
407
+ "\"isPrimary\":true}]";
408
+ success = true;
409
+ } else if (displayMethod == "getPrimary") {
410
+ result = "{\"id\":1,\"name\":\"Primary\","
411
+ "\"x\":0,\"y\":0,\"width\":1920,"
412
+ "\"height\":1080,\"scale\":1,"
413
+ "\"isPrimary\":true}";
414
+ success = true;
415
+ } else if (displayMethod == "getCurrent") {
416
+ result = "{\"id\":1,\"name\":\"Primary\","
417
+ "\"x\":0,\"y\":0,\"width\":1920,"
418
+ "\"height\":1080,\"scale\":1,"
419
+ "\"isPrimary\":true}";
420
+ success = true;
421
+ }
422
+ } else if (method.find("clipboard.") == 0) {
423
+ std::string clipMethod = method.substr(10);
424
+ if (clipMethod == "writeText") {
425
+ size_t p1 = msg.find("[\"");
426
+ size_t p2 = msg.find("\"]");
427
+ if (p1 != std::string::npos &&
428
+ p2 != std::string::npos) {
429
+ // TODO: implement clipboard write
430
+ }
431
+ success = true;
432
+ } else if (clipMethod == "readText") {
433
+ result = "\"\"";
434
+ success = true;
435
+ } else if (clipMethod == "clear") {
436
+ success = true;
437
+ }
438
+ } else if (method.find("keyboard.") == 0) {
439
+ // Keyboard API routing
440
+ std::string kbMethod = method.substr(9);
441
+ if (kbMethod == "isKeyPressed") {
442
+ result = "false";
443
+ success = true;
444
+ } else if (kbMethod == "registerShortcut") {
445
+ // TODO: integrate with ShortcutManager
446
+ result = "true";
447
+ success = true;
448
+ } else if (kbMethod ==
449
+ "unregisterShortcut") {
450
+ result = "true";
451
+ success = true;
452
+ } else if (kbMethod == "clearShortcuts") {
453
+ success = true;
454
+ } else if (kbMethod == "setAutoRepeat") {
455
+ success = true;
456
+ } else if (kbMethod == "getAutoRepeat") {
457
+ result = "true";
458
+ success = true;
459
+ } else {
460
+ std::cout << "[PlusUI] Unknown keyboard "
461
+ "method: "
462
+ << kbMethod << std::endl;
463
+ }
464
+ } else if (method.find("menu.") == 0) {
465
+ // Menu API routing
466
+ std::string menuMethod = method.substr(5);
467
+ if (menuMethod == "create") {
468
+ // TODO: integrate with
469
+ // MenuBindings/ContextMenuManager
470
+ result = "\"menu_" + id + "\"";
471
+ success = true;
472
+ } else if (menuMethod == "popup") {
473
+ success = true;
474
+ } else if (menuMethod == "popupAtCursor") {
475
+ success = true;
476
+ } else if (menuMethod == "close") {
477
+ success = true;
478
+ } else if (menuMethod == "destroy") {
479
+ success = true;
480
+ } else if (menuMethod ==
481
+ "setApplicationMenu") {
482
+ // TODO: integrate with MenuBar
483
+ success = true;
484
+ } else if (menuMethod ==
485
+ "getApplicationMenu") {
486
+ result = "[]";
487
+ success = true;
488
+ } else if (menuMethod ==
489
+ "appendToMenuBar") {
490
+ success = true;
491
+ } else {
492
+ std::cout
493
+ << "[PlusUI] Unknown menu method: "
494
+ << menuMethod << std::endl;
495
+ }
496
+ } else if (method.find("webgpu.") == 0) {
497
+ // WebGPU API routing
498
+ std::string webgpuMethod = method.substr(
499
+ 7); // Remove "webgpu." prefix
500
+
501
+ if (webgpuMethod == "requestAdapter") {
502
+ result = pImpl->webgpu.requestAdapter(
503
+ msg.substr(msg.find("["),
504
+ msg.rfind("]") -
505
+ msg.find("[") + 1));
506
+ success = true;
507
+ } else if (webgpuMethod ==
508
+ "requestDevice") {
509
+ // Extract adapterId and descriptor from
510
+ // params
511
+ size_t p1 = msg.find("[");
512
+ size_t p2 = msg.find("]");
513
+ if (p1 != std::string::npos &&
514
+ p2 != std::string::npos) {
515
+ std::string params =
516
+ msg.substr(p1 + 1, p2 - p1 - 1);
517
+ // TODO: Parse adapterId and descriptor
518
+ // properly
519
+ }
520
+ success = true;
521
+ } else if (webgpuMethod == "createBuffer") {
522
+ success = true;
523
+ } else if (webgpuMethod ==
524
+ "createTexture") {
525
+ success = true;
526
+ } else if (webgpuMethod ==
527
+ "createShaderModule") {
528
+ success = true;
529
+ } else if (webgpuMethod ==
530
+ "createRenderPipeline") {
531
+ success = true;
532
+ } else if (webgpuMethod ==
533
+ "createCommandEncoder") {
534
+ success = true;
535
+ } else if (webgpuMethod ==
536
+ "submitCommands") {
537
+ success = true;
538
+ } else {
539
+ // Unknown WebGPU method
540
+ std::cout
541
+ << "[PlusUI] Unknown WebGPU method: "
542
+ << webgpuMethod << std::endl;
543
+ }
544
+ }
545
+
546
+ // Send response back to JS (matches
547
+ // plusui-native-core SDK bridge)
548
+ std::string response =
549
+ "window.__response__(\"" + id + "\", " +
550
+ result + ");";
551
+ pImpl->webview->ExecuteScript(
552
+ std::wstring(response.begin(),
553
+ response.end())
554
+ .c_str(),
555
+ nullptr);
556
+
557
+ return S_OK;
558
+ })
559
+ .Get(),
560
+ nullptr);
561
+
562
+ // Process pending scripts
563
+ for (const auto &script : pImpl->pendingScripts) {
564
+ pImpl->webview->ExecuteScript(
565
+ std::wstring(script.begin(), script.end())
566
+ .c_str(),
567
+ nullptr);
568
+ }
569
+ pImpl->pendingScripts.clear();
570
+
571
+ // Process pending navigation
572
+ if (!pImpl->pendingNavigation.empty()) {
573
+ pImpl->webview->Navigate(
574
+ std::wstring(pImpl->pendingNavigation.begin(),
575
+ pImpl->pendingNavigation.end())
576
+ .c_str());
577
+ pImpl->pendingNavigation.clear();
578
+ }
579
+
580
+ // Process pending HTML
581
+ if (!pImpl->pendingHTML.empty()) {
582
+ pImpl->webview->NavigateToString(
583
+ std::wstring(pImpl->pendingHTML.begin(),
584
+ pImpl->pendingHTML.end())
585
+ .c_str());
586
+ pImpl->pendingHTML.clear();
587
+ }
588
+
589
+ // Process pending File
590
+ if (!pImpl->pendingFile.empty()) {
591
+ // For now, loadFile is implemented via navigate in
592
+ // some versions or direct file reading. We'll handle
593
+ // it via navigate for simplicity if it's already
594
+ // implemented that way.
595
+ }
596
+ }
597
+ return S_OK;
598
+ })
599
+ .Get());
600
+ return S_OK;
601
+ })
602
+ .Get());
603
+
604
+ #elif defined(__APPLE__)
605
+ WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
606
+ config.preferences.javaScriptEnabled = YES;
607
+
608
+ if (wv.pImpl->config.devtools) {
609
+ [config.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
610
+ }
611
+
612
+ NSView *parentView = (__bridge NSView *)windowHandle;
613
+ wv.pImpl->wkWebView =
614
+ [[WKWebView alloc] initWithFrame:parentView.bounds configuration:config];
615
+ [parentView addSubview:wv.pImpl->wkWebView];
616
+
617
+ wv.pImpl->nativeWebView = (__bridge void *)wv.pImpl->wkWebView;
618
+
619
+ #endif
620
+
621
+ wv.pImpl->windowManager = std::make_unique<WindowManager>(windowHandle);
622
+ wv.pImpl->trayManager = std::make_unique<TrayManager>();
623
+ wv.pImpl->trayManager->setWindowHandle(windowHandle);
624
+
625
+ return wv;
626
+ }
627
+
628
+ TrayManager &WebView::tray() { return *pImpl->trayManager; }
629
+ WindowManager &WebView::window() { return *pImpl->windowManager; }
630
+
631
+ void WebView::setWindow(std::shared_ptr<Window> win) {
632
+ pImpl->window = win;
633
+ if (!win)
634
+ return;
635
+
636
+ std::weak_ptr<Impl> weak_pImpl = pImpl;
637
+ win->onResize([weak_pImpl](int w, int h) {
638
+ if (auto pImpl = weak_pImpl.lock()) {
639
+ #ifdef _WIN32
640
+ if (pImpl->controller) {
641
+ RECT bounds = {0, 0, w, h};
642
+ pImpl->controller->put_Bounds(bounds);
643
+ }
644
+ #elif defined(__APPLE__)
645
+ if (pImpl->wkWebView) {
646
+ NSView *view = (__bridge NSView *)pImpl->wkWebView;
647
+ NSView *parent = [view superview];
648
+ if (parent) {
649
+ [view setFrame:[parent bounds]];
650
+ }
651
+ }
652
+ #else
653
+ if (pImpl->gtkWebView) {
654
+ // GTK usually handles this if added to a container with expand=TRUE
655
+ // but we can ensure it here if it's a fixed layout parent
656
+ gtk_widget_set_size_request(GTK_WIDGET(pImpl->gtkWebView), w, h);
657
+ }
658
+ #endif
659
+ }
660
+ });
661
+
662
+ // Trigger initial resize
663
+ int w, h;
664
+ win->getSize(w, h);
665
+ #ifdef _WIN32
666
+ if (pImpl->controller) {
667
+ RECT bounds = {0, 0, w, h};
668
+ pImpl->controller->put_Bounds(bounds);
669
+ }
670
+ #endif
671
+ }
672
+
673
+ void WebView::on(const std::string &event,
674
+ std::function<void(const std::string &)> callback) {
675
+ pImpl->events[event] = callback;
676
+ }
677
+
678
+ void WebView::emit(const std::string &event, const std::string &data) {
679
+ std::string js = "window.dispatchEvent(new CustomEvent('" + event +
680
+ "', {detail: " + data + "}))";
681
+ executeScript(js);
682
+ }
683
+
684
+ void WebView::navigate(const std::string &url) {
685
+ pImpl->currentURL = url;
686
+ pImpl->loading = true;
687
+
688
+ #ifdef _WIN32
689
+ if (pImpl->ready && pImpl->webview) {
690
+ pImpl->webview->Navigate(std::wstring(url.begin(), url.end()).c_str());
691
+ } else {
692
+ pImpl->pendingNavigation = url;
693
+ }
694
+ #elif defined(__APPLE__)
695
+ if (pImpl->wkWebView) {
696
+ NSURL *nsurl =
697
+ [NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]];
698
+ NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
699
+ [pImpl->wkWebView loadRequest:request];
700
+ }
701
+ #else
702
+ if (pImpl->gtkWebView) {
703
+ webkit_web_view_load_uri(pImpl->gtkWebView, url.c_str());
704
+ }
705
+ #endif
706
+ }
707
+
708
+ void WebView::loadHTML(const std::string &html) {
709
+ loadHTML(html, "about:blank");
710
+ }
711
+
712
+ void WebView::loadHTML(const std::string &html, const std::string &baseURL) {
713
+ pImpl->loading = true;
714
+
715
+ #ifdef _WIN32
716
+ if (pImpl->ready && pImpl->webview) {
717
+ pImpl->webview->NavigateToString(
718
+ std::wstring(html.begin(), html.end()).c_str());
719
+ } else {
720
+ pImpl->pendingHTML = html;
721
+ }
722
+ #elif defined(__APPLE__)
723
+ if (pImpl->wkWebView) {
724
+ NSString *htmlString = [NSString stringWithUTF8String:html.c_str()];
725
+ NSURL *base =
726
+ [NSURL URLWithString:[NSString stringWithUTF8String:baseURL.c_str()]];
727
+ [pImpl->wkWebView loadHTMLString:htmlString baseURL:base];
728
+ }
729
+ #else
730
+ if (pImpl->gtkWebView) {
731
+ webkit_web_view_load_html(pImpl->gtkWebView, html.c_str(), baseURL.c_str());
732
+ }
733
+ #endif
734
+ }
735
+
736
+ void WebView::loadFile(const std::string &filePath) {
737
+ std::ifstream file(filePath);
738
+ if (!file) {
739
+ std::cerr << "PlusUI: Failed to open file: " << filePath << std::endl;
740
+ return;
741
+ }
742
+
743
+ std::stringstream buffer;
744
+ buffer << file.rdbuf();
745
+
746
+ // Use file:// URL as base for relative paths
747
+ std::string baseURL =
748
+ "file://" + filePath.substr(0, filePath.find_last_of("/\\") + 1);
749
+ loadHTML(buffer.str(), baseURL);
750
+ }
751
+
752
+ void WebView::reload() {
753
+ #ifdef _WIN32
754
+ if (pImpl->webview) {
755
+ pImpl->webview->Reload();
756
+ }
757
+ #elif defined(__APPLE__)
758
+ if (pImpl->wkWebView) {
759
+ [pImpl->wkWebView reload];
760
+ }
761
+ #else
762
+ if (pImpl->gtkWebView) {
763
+ webkit_web_view_reload(pImpl->gtkWebView);
764
+ }
765
+ #endif
766
+ }
767
+
768
+ void WebView::stop() {
769
+ #ifdef _WIN32
770
+ if (pImpl->webview) {
771
+ pImpl->webview->Stop();
772
+ }
773
+ #elif defined(__APPLE__)
774
+ if (pImpl->wkWebView) {
775
+ [pImpl->wkWebView stopLoading];
776
+ }
777
+ #else
778
+ if (pImpl->gtkWebView) {
779
+ webkit_web_view_stop_loading(pImpl->gtkWebView);
780
+ }
781
+ #endif
782
+ }
783
+
784
+ void WebView::goBack() {
785
+ #ifdef _WIN32
786
+ if (pImpl->webview) {
787
+ pImpl->webview->GoBack();
788
+ }
789
+ #elif defined(__APPLE__)
790
+ if (pImpl->wkWebView) {
791
+ [pImpl->wkWebView goBack];
792
+ }
793
+ #else
794
+ if (pImpl->gtkWebView) {
795
+ webkit_web_view_go_back(pImpl->gtkWebView);
796
+ }
797
+ #endif
798
+ }
799
+
800
+ void WebView::goForward() {
801
+ #ifdef _WIN32
802
+ if (pImpl->webview) {
803
+ pImpl->webview->GoForward();
804
+ }
805
+ #elif defined(__APPLE__)
806
+ if (pImpl->wkWebView) {
807
+ [pImpl->wkWebView goForward];
808
+ }
809
+ #else
810
+ if (pImpl->gtkWebView) {
811
+ webkit_web_view_go_forward(pImpl->gtkWebView);
812
+ }
813
+ #endif
814
+ }
815
+
816
+ bool WebView::canGoBack() const {
817
+ #ifdef _WIN32
818
+ if (pImpl->webview) {
819
+ BOOL canGoBack;
820
+ pImpl->webview->get_CanGoBack(&canGoBack);
821
+ return canGoBack;
822
+ }
823
+ #elif defined(__APPLE__)
824
+ if (pImpl->wkWebView) {
825
+ return [pImpl->wkWebView canGoBack];
826
+ }
827
+ #else
828
+ if (pImpl->gtkWebView) {
829
+ return webkit_web_view_can_go_back(pImpl->gtkWebView);
830
+ }
831
+ #endif
832
+ return false;
833
+ }
834
+
835
+ bool WebView::canGoForward() const {
836
+ #ifdef _WIN32
837
+ if (pImpl->webview) {
838
+ BOOL canGoForward;
839
+ pImpl->webview->get_CanGoForward(&canGoForward);
840
+ return canGoForward;
841
+ }
842
+ #elif defined(__APPLE__)
843
+ if (pImpl->wkWebView) {
844
+ return [pImpl->wkWebView canGoForward];
845
+ }
846
+ #else
847
+ if (pImpl->gtkWebView) {
848
+ return webkit_web_view_can_go_forward(pImpl->gtkWebView);
849
+ }
850
+ #endif
851
+ return false;
852
+ }
853
+
854
+ void WebView::executeScript(const std::string &script) {
855
+ #ifdef _WIN32
856
+ if (pImpl->ready && pImpl->webview) {
857
+ pImpl->webview->ExecuteScript(
858
+ std::wstring(script.begin(), script.end()).c_str(), nullptr);
859
+ } else {
860
+ pImpl->pendingScripts.push_back(script);
861
+ }
862
+ #elif defined(__APPLE__)
863
+ if (pImpl->wkWebView) {
864
+ NSString *js = [NSString stringWithUTF8String:script.c_str()];
865
+ [pImpl->wkWebView evaluateJavaScript:js completionHandler:nil];
866
+ }
867
+ #else
868
+ if (pImpl->gtkWebView) {
869
+ webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
870
+ nullptr, nullptr);
871
+ }
872
+ #endif
873
+ }
874
+
875
+ void WebView::executeScript(const std::string &script,
876
+ std::function<void(const std::string &)> callback) {
877
+ #ifdef _WIN32
878
+ if (pImpl->webview) {
879
+ pImpl->webview->ExecuteScript(
880
+ std::wstring(script.begin(), script.end()).c_str(),
881
+ Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
882
+ [callback](HRESULT error, LPCWSTR result) -> HRESULT {
883
+ if (result && callback) {
884
+ std::wstring wstr(result);
885
+ callback(std::string(wstr.begin(), wstr.end()));
886
+ }
887
+ return S_OK;
888
+ })
889
+ .Get());
890
+ }
891
+ #elif defined(__APPLE__)
892
+ if (pImpl->wkWebView) {
893
+ NSString *js = [NSString stringWithUTF8String:script.c_str()];
894
+ [pImpl->wkWebView evaluateJavaScript:js
895
+ completionHandler:^(id result, NSError *error) {
896
+ if (result && callback) {
897
+ NSString *resultStr =
898
+ [NSString stringWithFormat:@"%@", result];
899
+ callback([resultStr UTF8String]);
900
+ }
901
+ }];
902
+ }
903
+ #else
904
+ if (pImpl->gtkWebView) {
905
+ // GTK WebKit callback handling would go here
906
+ webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
907
+ nullptr, nullptr);
908
+ }
909
+ #endif
910
+ }
911
+
912
+ void WebView::bind(const std::string &name, JSCallback callback) {
913
+ pImpl->bindings[name] = callback;
914
+
915
+ // Inject bridge code to expose function to JavaScript
916
+ std::string bridgeScript = R"(
917
+ window.)" + name + R"( = function(...args) {
918
+ return window.plusui.invoke('webview.)" +
919
+ name + R"(', args);
920
+ };
921
+ )";
922
+
923
+ executeScript(bridgeScript);
924
+ }
925
+
926
+ void WebView::unbind(const std::string &name) {
927
+ pImpl->bindings.erase(name);
928
+
929
+ std::string script = "delete window." + name + ";";
930
+ executeScript(script);
931
+ }
932
+
933
+ void WebView::openDevTools() {
934
+ #ifdef _WIN32
935
+ if (pImpl->webview) {
936
+ pImpl->webview->OpenDevToolsWindow();
937
+ }
938
+ #elif defined(__APPLE__)
939
+ // macOS: Dev tools open in Safari's Web Inspector
940
+ #else
941
+ if (pImpl->gtkWebView) {
942
+ WebKitWebInspector *inspector =
943
+ webkit_web_view_get_inspector(pImpl->gtkWebView);
944
+ webkit_web_inspector_show(inspector);
945
+ }
946
+ #endif
947
+ }
948
+
949
+ void WebView::closeDevTools() {
950
+ #ifdef _WIN32
951
+ // WebView2 doesn't have explicit close
952
+ #elif defined(__APPLE__)
953
+ // macOS: Handled by Web Inspector
954
+ #else
955
+ if (pImpl->gtkWebView) {
956
+ WebKitWebInspector *inspector =
957
+ webkit_web_view_get_inspector(pImpl->gtkWebView);
958
+ webkit_web_inspector_close(inspector);
959
+ }
960
+ #endif
961
+ }
962
+
963
+ void WebView::setUserAgent(const std::string &userAgent) {
964
+ pImpl->config.userAgent = userAgent;
965
+
966
+ #ifdef _WIN32
967
+ if (pImpl->webview) {
968
+ ComPtr<ICoreWebView2Settings> settings;
969
+ pImpl->webview->get_Settings(&settings);
970
+ // WebView2 user agent requires settings2 interface
971
+ }
972
+ #elif defined(__APPLE__)
973
+ if (pImpl->wkWebView) {
974
+ [pImpl->wkWebView
975
+ setCustomUserAgent:[NSString stringWithUTF8String:userAgent.c_str()]];
976
+ }
977
+ #else
978
+ if (pImpl->gtkWebView) {
979
+ WebKitSettings *settings = webkit_web_view_get_settings(pImpl->gtkWebView);
980
+ webkit_settings_set_user_agent(settings, userAgent.c_str());
981
+ }
982
+ #endif
983
+ }
984
+
985
+ std::string WebView::getUserAgent() const { return pImpl->config.userAgent; }
986
+
987
+ void WebView::setZoom(double factor) {
988
+ pImpl->zoom = factor;
989
+
990
+ #ifdef _WIN32
991
+ if (pImpl->controller) {
992
+ pImpl->controller->put_ZoomFactor(factor);
993
+ }
994
+ #elif defined(__APPLE__)
995
+ if (pImpl->wkWebView) {
996
+ [pImpl->wkWebView setPageZoom:factor];
997
+ }
998
+ #else
999
+ if (pImpl->gtkWebView) {
1000
+ webkit_web_view_set_zoom_level(pImpl->gtkWebView, factor);
1001
+ }
1002
+ #endif
1003
+ }
1004
+
1005
+ double WebView::getZoom() const { return pImpl->zoom; }
1006
+
1007
+ void WebView::injectCSS(const std::string &css) {
1008
+ std::string script = R"(
1009
+ (function() {
1010
+ const style = document.createElement('style');
1011
+ style.textContent = `)" +
1012
+ css + R"(`;
1013
+ document.head.appendChild(style);
1014
+ })();
1015
+ )";
1016
+
1017
+ executeScript(script);
1018
+ }
1019
+
1020
+ void WebView::onNavigationStart(NavigationCallback callback) {
1021
+ pImpl->navigationCallback = callback;
1022
+ }
1023
+
1024
+ void WebView::onNavigationComplete(LoadCallback callback) {
1025
+ pImpl->navigationCompleteCallback = callback;
1026
+ }
1027
+
1028
+ void WebView::onLoadStart(LoadCallback callback) {
1029
+ pImpl->loadStartCallback = callback;
1030
+ }
1031
+
1032
+ void WebView::onLoadEnd(LoadCallback callback) {
1033
+ pImpl->loadEndCallback = callback;
1034
+ }
1035
+
1036
+ void WebView::onLoadError(ErrorCallback callback) {
1037
+ pImpl->errorCallback = callback;
1038
+ }
1039
+
1040
+ void WebView::onConsoleMessage(ConsoleCallback callback) {
1041
+ pImpl->consoleCallback = callback;
1042
+ }
1043
+
1044
+ bool WebView::isLoading() const { return pImpl->loading; }
1045
+
1046
+ std::string WebView::getURL() const { return pImpl->currentURL; }
1047
+
1048
+ std::string WebView::getTitle() const { return pImpl->currentTitle; }
1049
+
1050
+ void *WebView::nativeHandle() const { return pImpl->nativeWebView; }
1051
+
1052
+ } // namespace plusui