plusui-native-core 0.1.34 → 0.1.35
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.
package/Core/CMakeLists.txt
CHANGED
|
@@ -24,7 +24,6 @@ add_library(plusui STATIC
|
|
|
24
24
|
Features/Bindings/CustomBindings/custom_bindings.cpp
|
|
25
25
|
Features/App/app.cpp
|
|
26
26
|
Features/Window/window.cpp
|
|
27
|
-
# Features/Window/webview.cpp # TODO: Resolve duplicate symbol definitions with window.cpp
|
|
28
27
|
Features/Browser/browser.cpp
|
|
29
28
|
Features/Display/display.cpp
|
|
30
29
|
Features/Tray/tray.cpp
|
|
@@ -105,24 +105,29 @@ Window App::Builder::build() {
|
|
|
105
105
|
winConfig.decorations = config.decorations;
|
|
106
106
|
winConfig.skipTaskbar = config.skipTaskbar;
|
|
107
107
|
winConfig.enableFileDrop = config.enableFileDrop;
|
|
108
|
-
|
|
108
|
+
|
|
109
109
|
// WebView configuration (now part of WindowConfig)
|
|
110
110
|
winConfig.devtools = config.devtools;
|
|
111
111
|
winConfig.scrollbars = config.scrollbars;
|
|
112
112
|
winConfig.disableWebviewDragDrop = config.enableFileDrop; // Auto-disable webview drag when FileDrop enabled
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
// Create native window
|
|
115
|
+
auto nativeWinPtr = std::make_shared<Window>(Window::create(winConfig));
|
|
116
|
+
nativeWinPtr->show();
|
|
117
|
+
|
|
118
|
+
// Create embedded webview window inside the native window
|
|
119
|
+
Window webviewWin = Window::create(nativeWinPtr->nativeHandle(), winConfig);
|
|
120
|
+
webviewWin.setWindow(nativeWinPtr);
|
|
116
121
|
|
|
117
122
|
if (!config.trayIconPath.empty()) {
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
nativeWinPtr->tray().setIcon(config.trayIconPath);
|
|
124
|
+
nativeWinPtr->tray().setVisible(true);
|
|
120
125
|
if (!config.trayTooltip.empty()) {
|
|
121
|
-
|
|
126
|
+
nativeWinPtr->tray().setTooltip(config.trayTooltip);
|
|
122
127
|
}
|
|
123
128
|
}
|
|
124
129
|
|
|
125
|
-
return
|
|
130
|
+
return webviewWin;
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
App::Builder createApp() { return App::Builder(); }
|
|
@@ -25,50 +25,8 @@ using namespace Microsoft::WRL;
|
|
|
25
25
|
|
|
26
26
|
namespace plusui {
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
WindowConfig config;
|
|
31
|
-
std::string currentURL;
|
|
32
|
-
std::string currentTitle;
|
|
33
|
-
std::shared_ptr<Window> window; // Keep the native window alive
|
|
34
|
-
std::string userAgent; // Advanced webview setting
|
|
35
|
-
bool loading = false;
|
|
36
|
-
double zoom = 1.0;
|
|
37
|
-
bool ready = false;
|
|
38
|
-
std::vector<std::string> pendingScripts;
|
|
39
|
-
std::string pendingNavigation;
|
|
40
|
-
std::string pendingHTML;
|
|
41
|
-
std::string pendingFile;
|
|
42
|
-
|
|
43
|
-
std::map<std::string, JSCallback> bindings;
|
|
44
|
-
|
|
45
|
-
std::unique_ptr<TrayManager> trayManager;
|
|
46
|
-
std::map<std::string, std::function<void(const std::string &)>> events;
|
|
47
|
-
WebGPU webgpu; // WebGPU support
|
|
48
|
-
|
|
49
|
-
NavigationCallback navigationCallback;
|
|
50
|
-
LoadCallback loadStartCallback;
|
|
51
|
-
LoadCallback loadEndCallback;
|
|
52
|
-
LoadCallback navigationCompleteCallback;
|
|
53
|
-
ErrorCallback errorCallback;
|
|
54
|
-
ConsoleCallback consoleCallback;
|
|
55
|
-
|
|
56
|
-
#ifdef _WIN32
|
|
57
|
-
ComPtr<ICoreWebView2Controller> controller;
|
|
58
|
-
ComPtr<ICoreWebView2> webview;
|
|
59
|
-
#elif defined(__APPLE__)
|
|
60
|
-
WKWebView *wkWebView = nullptr;
|
|
61
|
-
#else
|
|
62
|
-
WebKitWebView *gtkWebView = nullptr;
|
|
63
|
-
#endif
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
Window::Window() : pImpl(std::shared_ptr<Impl>(new Impl())) {}
|
|
67
|
-
|
|
68
|
-
Window::~Window() = default;
|
|
69
|
-
|
|
70
|
-
Window::Window(Window &&other) noexcept = default;
|
|
71
|
-
Window &Window::operator=(Window &&other) noexcept = default;
|
|
28
|
+
// Note: Window::Impl is defined in window.cpp
|
|
29
|
+
// This file only provides implementations for webview-specific methods
|
|
72
30
|
|
|
73
31
|
Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
74
32
|
Window win;
|
|
@@ -1,15 +1,29 @@
|
|
|
1
|
+
#include <fstream>
|
|
2
|
+
#include <functional>
|
|
1
3
|
#include <iostream>
|
|
2
4
|
#include <map>
|
|
3
5
|
#include <plusui/display.hpp>
|
|
6
|
+
#include <plusui/tray.hpp>
|
|
7
|
+
#include <plusui/webgpu.hpp>
|
|
4
8
|
#include <plusui/window.hpp>
|
|
9
|
+
#include <regex>
|
|
10
|
+
#include <sstream>
|
|
5
11
|
|
|
6
12
|
#ifdef _WIN32
|
|
13
|
+
#pragma warning(push)
|
|
14
|
+
#pragma warning(disable: 4996)
|
|
15
|
+
#include <WebView2.h>
|
|
7
16
|
#include <windows.h>
|
|
17
|
+
#include <wrl.h>
|
|
18
|
+
using namespace Microsoft::WRL;
|
|
19
|
+
#pragma warning(pop)
|
|
8
20
|
#elif defined(__APPLE__)
|
|
9
21
|
#include <Cocoa/Cocoa.h>
|
|
10
22
|
#include <objc/objc-runtime.h>
|
|
23
|
+
#import <WebKit/WebKit.h>
|
|
11
24
|
#else
|
|
12
25
|
#include <gtk/gtk.h>
|
|
26
|
+
#include <webkit2/webkit2.h>
|
|
13
27
|
#endif
|
|
14
28
|
|
|
15
29
|
#include <stb_image.h>
|
|
@@ -18,9 +32,33 @@ namespace plusui {
|
|
|
18
32
|
|
|
19
33
|
struct Window::Impl {
|
|
20
34
|
void *nativeWindow = nullptr;
|
|
35
|
+
void *nativeWebView = nullptr;
|
|
21
36
|
WindowConfig config;
|
|
22
37
|
WindowState state;
|
|
23
38
|
|
|
39
|
+
// WebView-specific members
|
|
40
|
+
std::string currentURL;
|
|
41
|
+
std::string currentTitle;
|
|
42
|
+
std::shared_ptr<Window> window;
|
|
43
|
+
std::string userAgent;
|
|
44
|
+
bool loading = false;
|
|
45
|
+
double zoom = 1.0;
|
|
46
|
+
bool ready = false;
|
|
47
|
+
std::vector<std::string> pendingScripts;
|
|
48
|
+
std::string pendingNavigation;
|
|
49
|
+
std::string pendingHTML;
|
|
50
|
+
std::string pendingFile;
|
|
51
|
+
std::map<std::string, JSCallback> bindings;
|
|
52
|
+
std::unique_ptr<TrayManager> trayManager;
|
|
53
|
+
std::map<std::string, std::function<void(const std::string &)>> events;
|
|
54
|
+
WebGPU webgpu;
|
|
55
|
+
NavigationCallback navigationCallback;
|
|
56
|
+
LoadCallback loadStartCallback;
|
|
57
|
+
LoadCallback loadEndCallback;
|
|
58
|
+
LoadCallback navigationCompleteCallback;
|
|
59
|
+
ErrorCallback errorCallback;
|
|
60
|
+
ConsoleCallback consoleCallback;
|
|
61
|
+
|
|
24
62
|
std::vector<MoveCallback> moveCallbacks;
|
|
25
63
|
std::vector<ResizeCallback> resizeCallbacks;
|
|
26
64
|
std::vector<CloseCallback> closeCallbacks;
|
|
@@ -30,6 +68,8 @@ struct Window::Impl {
|
|
|
30
68
|
#ifdef _WIN32
|
|
31
69
|
HWND hwnd = nullptr;
|
|
32
70
|
WNDPROC originalProc = nullptr;
|
|
71
|
+
ComPtr<ICoreWebView2Controller> controller;
|
|
72
|
+
ComPtr<ICoreWebView2> webview;
|
|
33
73
|
|
|
34
74
|
static LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
|
35
75
|
Impl *impl = nullptr;
|
|
@@ -79,6 +119,10 @@ struct Window::Impl {
|
|
|
79
119
|
}
|
|
80
120
|
return DefWindowProc(hwnd, msg, wp, lp);
|
|
81
121
|
}
|
|
122
|
+
#elif defined(__APPLE__)
|
|
123
|
+
WKWebView *wkWebView = nullptr;
|
|
124
|
+
#else
|
|
125
|
+
WebKitWebView *gtkWebView = nullptr;
|
|
82
126
|
#endif
|
|
83
127
|
};
|
|
84
128
|
|
|
@@ -86,6 +130,9 @@ Window::Window() : pImpl(std::shared_ptr<Impl>(new Impl())) {}
|
|
|
86
130
|
|
|
87
131
|
Window::~Window() = default;
|
|
88
132
|
|
|
133
|
+
Window::Window(Window &&other) noexcept = default;
|
|
134
|
+
Window &Window::operator=(Window &&other) noexcept = default;
|
|
135
|
+
|
|
89
136
|
Window Window::create(const WindowConfig &config) {
|
|
90
137
|
Window w;
|
|
91
138
|
w.pImpl->config = config;
|
|
@@ -198,6 +245,18 @@ Window Window::create(const WindowConfig &config) {
|
|
|
198
245
|
w.pImpl->nativeWindow = (void *)gtkwin;
|
|
199
246
|
#endif
|
|
200
247
|
|
|
248
|
+
// Initialize tray manager for native windows
|
|
249
|
+
w.pImpl->trayManager = std::make_unique<TrayManager>();
|
|
250
|
+
#ifdef _WIN32
|
|
251
|
+
if (w.pImpl->hwnd) {
|
|
252
|
+
w.pImpl->trayManager->setWindowHandle((void *)w.pImpl->hwnd);
|
|
253
|
+
}
|
|
254
|
+
#elif defined(__APPLE__)
|
|
255
|
+
w.pImpl->trayManager->setWindowHandle(w.pImpl->nativeWindow);
|
|
256
|
+
#else
|
|
257
|
+
w.pImpl->trayManager->setWindowHandle(w.pImpl->nativeWindow);
|
|
258
|
+
#endif
|
|
259
|
+
|
|
201
260
|
return w;
|
|
202
261
|
}
|
|
203
262
|
|
|
@@ -220,7 +279,10 @@ void Window::setTitle(const std::string &title) {
|
|
|
220
279
|
#endif
|
|
221
280
|
}
|
|
222
281
|
|
|
223
|
-
std::string Window::getTitle() const {
|
|
282
|
+
std::string Window::getTitle() const {
|
|
283
|
+
if (!pImpl->currentTitle.empty()) return pImpl->currentTitle;
|
|
284
|
+
return pImpl->config.title;
|
|
285
|
+
}
|
|
224
286
|
|
|
225
287
|
void Window::setSize(int width, int height) {
|
|
226
288
|
pImpl->config.width = width;
|
|
@@ -677,4 +739,979 @@ void Window::onStateChange(StateCallback callback) {
|
|
|
677
739
|
|
|
678
740
|
void *Window::nativeHandle() const { return pImpl->nativeWindow; }
|
|
679
741
|
|
|
742
|
+
// WebView-specific implementations
|
|
743
|
+
Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
744
|
+
Window win;
|
|
745
|
+
win.pImpl->config = config;
|
|
746
|
+
|
|
747
|
+
#ifdef _WIN32
|
|
748
|
+
HWND hwnd = static_cast<HWND>(windowHandle);
|
|
749
|
+
|
|
750
|
+
// Create WebView2 environment and controller
|
|
751
|
+
auto pImpl = win.pImpl;
|
|
752
|
+
CreateCoreWebView2EnvironmentWithOptions(
|
|
753
|
+
nullptr, nullptr, nullptr,
|
|
754
|
+
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
|
|
755
|
+
[hwnd, pImpl](HRESULT result,
|
|
756
|
+
ICoreWebView2Environment *env) -> HRESULT {
|
|
757
|
+
if (FAILED(result) || !env)
|
|
758
|
+
return result;
|
|
759
|
+
env->CreateCoreWebView2Controller(
|
|
760
|
+
hwnd,
|
|
761
|
+
Callback<
|
|
762
|
+
ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
|
|
763
|
+
[pImpl](HRESULT result,
|
|
764
|
+
ICoreWebView2Controller *controller) -> HRESULT {
|
|
765
|
+
(void)result; // Suppress unused warning
|
|
766
|
+
if (controller != nullptr) {
|
|
767
|
+
pImpl->controller = controller;
|
|
768
|
+
controller->get_CoreWebView2(&pImpl->webview);
|
|
769
|
+
|
|
770
|
+
RECT bounds;
|
|
771
|
+
HWND parentHwnd;
|
|
772
|
+
controller->get_ParentWindow(&parentHwnd);
|
|
773
|
+
GetClientRect(parentHwnd, &bounds);
|
|
774
|
+
controller->put_Bounds(bounds);
|
|
775
|
+
|
|
776
|
+
pImpl->nativeWebView = pImpl->webview.Get();
|
|
777
|
+
pImpl->ready = true;
|
|
778
|
+
|
|
779
|
+
// Inject bridge script that runs on EVERY document
|
|
780
|
+
// (survives navigation)
|
|
781
|
+
std::string bridgeScript = R"(
|
|
782
|
+
window.__native_invoke__ = function(request) {
|
|
783
|
+
if (window.chrome && window.chrome.webview) {
|
|
784
|
+
window.chrome.webview.postMessage(request);
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
)";
|
|
788
|
+
pImpl->webview->AddScriptToExecuteOnDocumentCreated(
|
|
789
|
+
std::wstring(bridgeScript.begin(),
|
|
790
|
+
bridgeScript.end())
|
|
791
|
+
.c_str(),
|
|
792
|
+
nullptr);
|
|
793
|
+
|
|
794
|
+
// Inject scrollbar hiding CSS if disabled
|
|
795
|
+
if (!pImpl->config.scrollbars) {
|
|
796
|
+
std::string scrollbarScript = R"(
|
|
797
|
+
(function() {
|
|
798
|
+
var css = 'html, body { overflow: hidden !important; margin: 0 !important; padding: 0 !important; } ::-webkit-scrollbar { display: none !important; width: 0 !important; height: 0 !important; }';
|
|
799
|
+
var style = document.createElement('style');
|
|
800
|
+
style.type = 'text/css';
|
|
801
|
+
style.appendChild(document.createTextNode(css));
|
|
802
|
+
(document.head || document.documentElement).appendChild(style);
|
|
803
|
+
})();
|
|
804
|
+
)";
|
|
805
|
+
pImpl->webview->AddScriptToExecuteOnDocumentCreated(
|
|
806
|
+
std::wstring(scrollbarScript.begin(),
|
|
807
|
+
scrollbarScript.end())
|
|
808
|
+
.c_str(),
|
|
809
|
+
nullptr);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Disable webview drag & drop behavior if requested
|
|
813
|
+
// This prevents files from being loaded in the browser
|
|
814
|
+
// and allows the native FileDrop API to handle them instead
|
|
815
|
+
if (pImpl->config.disableWebviewDragDrop) {
|
|
816
|
+
std::string disableDragDropScript = R"(
|
|
817
|
+
(function() {
|
|
818
|
+
// Prevent default drag and drop behavior on document/window
|
|
819
|
+
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(function(eventName) {
|
|
820
|
+
document.addEventListener(eventName, function(e) {
|
|
821
|
+
// Only prevent default on document.body or document.documentElement
|
|
822
|
+
// to allow custom drop zones to still work
|
|
823
|
+
if (e.target === document.body || e.target === document.documentElement || e.target === document) {
|
|
824
|
+
e.preventDefault();
|
|
825
|
+
e.stopPropagation();
|
|
826
|
+
}
|
|
827
|
+
}, true);
|
|
828
|
+
|
|
829
|
+
window.addEventListener(eventName, function(e) {
|
|
830
|
+
e.preventDefault();
|
|
831
|
+
e.stopPropagation();
|
|
832
|
+
}, true);
|
|
833
|
+
});
|
|
834
|
+
})();
|
|
835
|
+
)";
|
|
836
|
+
pImpl->webview->AddScriptToExecuteOnDocumentCreated(
|
|
837
|
+
std::wstring(disableDragDropScript.begin(),
|
|
838
|
+
disableDragDropScript.end())
|
|
839
|
+
.c_str(),
|
|
840
|
+
nullptr);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Set up WebMessageReceived handler for JS->C++ bridge
|
|
844
|
+
pImpl->webview->add_WebMessageReceived(
|
|
845
|
+
Callback<
|
|
846
|
+
ICoreWebView2WebMessageReceivedEventHandler>(
|
|
847
|
+
[pImpl](ICoreWebView2 *sender,
|
|
848
|
+
ICoreWebView2WebMessageReceivedEventArgs
|
|
849
|
+
*args) -> HRESULT {
|
|
850
|
+
(void)sender; // Suppress unused warning
|
|
851
|
+
LPWSTR message = nullptr;
|
|
852
|
+
args->TryGetWebMessageAsString(&message);
|
|
853
|
+
if (!message)
|
|
854
|
+
return S_OK;
|
|
855
|
+
std::wstring wmsg(message);
|
|
856
|
+
#pragma warning(push)
|
|
857
|
+
#pragma warning(disable: 4244) // Suppress wchar_t to char conversion warning
|
|
858
|
+
std::string msg(wmsg.begin(), wmsg.end());
|
|
859
|
+
#pragma warning(pop)
|
|
860
|
+
CoTaskMemFree(message);
|
|
861
|
+
|
|
862
|
+
// Debug: log received message
|
|
863
|
+
std::cout << "[PlusUI] Received: " << msg
|
|
864
|
+
<< std::endl;
|
|
865
|
+
|
|
866
|
+
// Parse JSON-RPC: {"id":"...",
|
|
867
|
+
// "method":"window.minimize", "params":[...]}
|
|
868
|
+
std::string id, method;
|
|
869
|
+
std::string result = "null";
|
|
870
|
+
bool success = false;
|
|
871
|
+
|
|
872
|
+
// Simple JSON parsing
|
|
873
|
+
auto getId = [&msg]() {
|
|
874
|
+
size_t pos = msg.find("\"id\"");
|
|
875
|
+
if (pos == std::string::npos)
|
|
876
|
+
return std::string();
|
|
877
|
+
pos = msg.find(":", pos);
|
|
878
|
+
if (pos == std::string::npos)
|
|
879
|
+
return std::string();
|
|
880
|
+
size_t start = msg.find("\"", pos);
|
|
881
|
+
if (start == std::string::npos)
|
|
882
|
+
return std::string();
|
|
883
|
+
size_t end = msg.find("\"", start + 1);
|
|
884
|
+
if (end == std::string::npos)
|
|
885
|
+
return std::string();
|
|
886
|
+
return msg.substr(start + 1,
|
|
887
|
+
end - start - 1);
|
|
888
|
+
};
|
|
889
|
+
auto getMethod = [&msg]() {
|
|
890
|
+
size_t pos = msg.find("\"method\"");
|
|
891
|
+
if (pos == std::string::npos)
|
|
892
|
+
return std::string();
|
|
893
|
+
pos = msg.find(":", pos);
|
|
894
|
+
if (pos == std::string::npos)
|
|
895
|
+
return std::string();
|
|
896
|
+
size_t start = msg.find("\"", pos);
|
|
897
|
+
if (start == std::string::npos)
|
|
898
|
+
return std::string();
|
|
899
|
+
size_t end = msg.find("\"", start + 1);
|
|
900
|
+
if (end == std::string::npos)
|
|
901
|
+
return std::string();
|
|
902
|
+
return msg.substr(start + 1,
|
|
903
|
+
end - start - 1);
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
id = getId();
|
|
907
|
+
method = getMethod();
|
|
908
|
+
|
|
909
|
+
// Route to handlers
|
|
910
|
+
if (method.find("window.") == 0) {
|
|
911
|
+
std::string winMethod = method.substr(7);
|
|
912
|
+
if (winMethod == "minimize") {
|
|
913
|
+
if (pImpl->window) pImpl->window->minimize();
|
|
914
|
+
success = true;
|
|
915
|
+
} else if (winMethod == "maximize") {
|
|
916
|
+
if (pImpl->window) pImpl->window->maximize();
|
|
917
|
+
success = true;
|
|
918
|
+
} else if (winMethod == "restore") {
|
|
919
|
+
if (pImpl->window) pImpl->window->restore();
|
|
920
|
+
success = true;
|
|
921
|
+
} else if (winMethod == "close") {
|
|
922
|
+
if (pImpl->window) pImpl->window->close();
|
|
923
|
+
success = true;
|
|
924
|
+
} else if (winMethod == "show") {
|
|
925
|
+
if (pImpl->window) pImpl->window->show();
|
|
926
|
+
success = true;
|
|
927
|
+
} else if (winMethod == "hide") {
|
|
928
|
+
if (pImpl->window) pImpl->window->hide();
|
|
929
|
+
success = true;
|
|
930
|
+
} else if (winMethod == "getSize") {
|
|
931
|
+
if (pImpl->window) {
|
|
932
|
+
int w, h;
|
|
933
|
+
pImpl->window->getSize(w, h);
|
|
934
|
+
result = "{\"width\":" +
|
|
935
|
+
std::to_string(w) +
|
|
936
|
+
",\"height\":" +
|
|
937
|
+
std::to_string(h) +
|
|
938
|
+
"}";
|
|
939
|
+
}
|
|
940
|
+
success = true;
|
|
941
|
+
} else if (winMethod == "getPosition") {
|
|
942
|
+
if (pImpl->window) {
|
|
943
|
+
int x, y;
|
|
944
|
+
pImpl->window->getPosition(x, y);
|
|
945
|
+
result =
|
|
946
|
+
"{\"x\":" + std::to_string(x) +
|
|
947
|
+
",\"y\":" + std::to_string(y) +
|
|
948
|
+
"}";
|
|
949
|
+
}
|
|
950
|
+
success = true;
|
|
951
|
+
} else if (winMethod == "setSize") {
|
|
952
|
+
// Parse params [width, height]
|
|
953
|
+
size_t p1 = msg.find("[");
|
|
954
|
+
size_t p2 = msg.find("]");
|
|
955
|
+
if (p1 != std::string::npos &&
|
|
956
|
+
p2 != std::string::npos) {
|
|
957
|
+
std::string params =
|
|
958
|
+
msg.substr(p1 + 1, p2 - p1 - 1);
|
|
959
|
+
int w = 0, h = 0;
|
|
960
|
+
sscanf(params.c_str(), "%d, %d", &w,
|
|
961
|
+
&h);
|
|
962
|
+
if (pImpl->window) pImpl->window->setSize(w, h);
|
|
963
|
+
}
|
|
964
|
+
success = true;
|
|
965
|
+
} else if (winMethod == "setPosition") {
|
|
966
|
+
size_t p1 = msg.find("[");
|
|
967
|
+
size_t p2 = msg.find("]");
|
|
968
|
+
if (p1 != std::string::npos &&
|
|
969
|
+
p2 != std::string::npos) {
|
|
970
|
+
std::string params =
|
|
971
|
+
msg.substr(p1 + 1, p2 - p1 - 1);
|
|
972
|
+
int x = 0, y = 0;
|
|
973
|
+
sscanf(params.c_str(), "%d, %d", &x,
|
|
974
|
+
&y);
|
|
975
|
+
if (pImpl->window) pImpl->window->setPosition(x, y);
|
|
976
|
+
}
|
|
977
|
+
success = true;
|
|
978
|
+
} else if (winMethod == "setTitle") {
|
|
979
|
+
size_t p1 = msg.find("[\"");
|
|
980
|
+
size_t p2 = msg.find("\"]");
|
|
981
|
+
if (p1 != std::string::npos &&
|
|
982
|
+
p2 != std::string::npos) {
|
|
983
|
+
std::string title =
|
|
984
|
+
msg.substr(p1 + 2, p2 - p1 - 2);
|
|
985
|
+
if (pImpl->window) pImpl->window->setTitle(title);
|
|
986
|
+
}
|
|
987
|
+
success = true;
|
|
988
|
+
} else if (winMethod == "setFullscreen") {
|
|
989
|
+
size_t p1 = msg.find("[");
|
|
990
|
+
size_t p2 = msg.find("]");
|
|
991
|
+
if (p1 != std::string::npos &&
|
|
992
|
+
p2 != std::string::npos) {
|
|
993
|
+
std::string params =
|
|
994
|
+
msg.substr(p1 + 1, p2 - p1 - 1);
|
|
995
|
+
if (pImpl->window) pImpl->window->setFullscreen(
|
|
996
|
+
params.find("true") !=
|
|
997
|
+
std::string::npos);
|
|
998
|
+
}
|
|
999
|
+
success = true;
|
|
1000
|
+
} else if (winMethod == "setAlwaysOnTop") {
|
|
1001
|
+
size_t p1 = msg.find("[");
|
|
1002
|
+
size_t p2 = msg.find("]");
|
|
1003
|
+
if (p1 != std::string::npos &&
|
|
1004
|
+
p2 != std::string::npos) {
|
|
1005
|
+
std::string params =
|
|
1006
|
+
msg.substr(p1 + 1, p2 - p1 - 1);
|
|
1007
|
+
if (pImpl->window) pImpl->window->setAlwaysOnTop(
|
|
1008
|
+
params.find("true") !=
|
|
1009
|
+
std::string::npos);
|
|
1010
|
+
}
|
|
1011
|
+
success = true;
|
|
1012
|
+
} else if (winMethod == "setResizable") {
|
|
1013
|
+
size_t p1 = msg.find("[");
|
|
1014
|
+
size_t p2 = msg.find("]");
|
|
1015
|
+
if (p1 != std::string::npos &&
|
|
1016
|
+
p2 != std::string::npos) {
|
|
1017
|
+
std::string params =
|
|
1018
|
+
msg.substr(p1 + 1, p2 - p1 - 1);
|
|
1019
|
+
if (pImpl->window) pImpl->window->setResizable(
|
|
1020
|
+
params.find("true") !=
|
|
1021
|
+
std::string::npos);
|
|
1022
|
+
}
|
|
1023
|
+
success = true;
|
|
1024
|
+
} else if (winMethod == "isMaximized") {
|
|
1025
|
+
result =
|
|
1026
|
+
(pImpl->window && pImpl->window->isMaximized())
|
|
1027
|
+
? "true"
|
|
1028
|
+
: "false";
|
|
1029
|
+
success = true;
|
|
1030
|
+
} else if (winMethod == "isMinimized") {
|
|
1031
|
+
result =
|
|
1032
|
+
(pImpl->window && pImpl->window->isMinimized())
|
|
1033
|
+
? "true"
|
|
1034
|
+
: "false";
|
|
1035
|
+
success = true;
|
|
1036
|
+
} else if (winMethod == "isVisible") {
|
|
1037
|
+
result = (pImpl->window && pImpl->window->isVisible())
|
|
1038
|
+
? "true"
|
|
1039
|
+
: "false";
|
|
1040
|
+
success = true;
|
|
1041
|
+
} else if (winMethod == "center") {
|
|
1042
|
+
if (pImpl->window) pImpl->window->center();
|
|
1043
|
+
success = true;
|
|
1044
|
+
}
|
|
1045
|
+
} else if (method.find("browser.") == 0) {
|
|
1046
|
+
std::string browserMethod =
|
|
1047
|
+
method.substr(8);
|
|
1048
|
+
if (browserMethod == "navigate") {
|
|
1049
|
+
size_t p1 = msg.find("[\"");
|
|
1050
|
+
size_t p2 = msg.find("\"]");
|
|
1051
|
+
if (p1 != std::string::npos &&
|
|
1052
|
+
p2 != std::string::npos) {
|
|
1053
|
+
std::string url =
|
|
1054
|
+
msg.substr(p1 + 2, p2 - p1 - 2);
|
|
1055
|
+
pImpl->webview->Navigate(
|
|
1056
|
+
std::wstring(url.begin(), url.end())
|
|
1057
|
+
.c_str());
|
|
1058
|
+
}
|
|
1059
|
+
success = true;
|
|
1060
|
+
} else if (browserMethod == "goBack") {
|
|
1061
|
+
pImpl->webview->GoBack();
|
|
1062
|
+
success = true;
|
|
1063
|
+
} else if (browserMethod == "goForward") {
|
|
1064
|
+
pImpl->webview->GoForward();
|
|
1065
|
+
success = true;
|
|
1066
|
+
} else if (browserMethod == "reload") {
|
|
1067
|
+
pImpl->webview->Reload();
|
|
1068
|
+
success = true;
|
|
1069
|
+
} else if (browserMethod == "stop") {
|
|
1070
|
+
pImpl->webview->Stop();
|
|
1071
|
+
success = true;
|
|
1072
|
+
} else if (browserMethod == "getUrl") {
|
|
1073
|
+
result = "\"" + pImpl->currentURL + "\"";
|
|
1074
|
+
success = true;
|
|
1075
|
+
} else if (browserMethod == "getTitle") {
|
|
1076
|
+
result =
|
|
1077
|
+
"\"" + pImpl->currentTitle + "\"";
|
|
1078
|
+
success = true;
|
|
1079
|
+
} else if (browserMethod == "canGoBack") {
|
|
1080
|
+
BOOL canBack;
|
|
1081
|
+
pImpl->webview->get_CanGoBack(&canBack);
|
|
1082
|
+
result = canBack ? "true" : "false";
|
|
1083
|
+
success = true;
|
|
1084
|
+
} else if (browserMethod ==
|
|
1085
|
+
"canGoForward") {
|
|
1086
|
+
BOOL canForward;
|
|
1087
|
+
pImpl->webview->get_CanGoForward(
|
|
1088
|
+
&canForward);
|
|
1089
|
+
result = canForward ? "true" : "false";
|
|
1090
|
+
success = true;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// Send response back to JS (matches
|
|
1095
|
+
// plusui-native-core SDK bridge)
|
|
1096
|
+
std::string response =
|
|
1097
|
+
"window.__response__(\"" + id + "\", " +
|
|
1098
|
+
result + ");";
|
|
1099
|
+
pImpl->webview->ExecuteScript(
|
|
1100
|
+
std::wstring(response.begin(),
|
|
1101
|
+
response.end())
|
|
1102
|
+
.c_str(),
|
|
1103
|
+
nullptr);
|
|
1104
|
+
|
|
1105
|
+
return S_OK;
|
|
1106
|
+
})
|
|
1107
|
+
.Get(),
|
|
1108
|
+
nullptr);
|
|
1109
|
+
|
|
1110
|
+
// Process pending scripts
|
|
1111
|
+
for (const auto &script : pImpl->pendingScripts) {
|
|
1112
|
+
pImpl->webview->ExecuteScript(
|
|
1113
|
+
std::wstring(script.begin(), script.end())
|
|
1114
|
+
.c_str(),
|
|
1115
|
+
nullptr);
|
|
1116
|
+
}
|
|
1117
|
+
pImpl->pendingScripts.clear();
|
|
1118
|
+
|
|
1119
|
+
// Process pending navigation
|
|
1120
|
+
if (!pImpl->pendingNavigation.empty()) {
|
|
1121
|
+
pImpl->webview->Navigate(
|
|
1122
|
+
std::wstring(pImpl->pendingNavigation.begin(),
|
|
1123
|
+
pImpl->pendingNavigation.end())
|
|
1124
|
+
.c_str());
|
|
1125
|
+
pImpl->pendingNavigation.clear();
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Process pending HTML
|
|
1129
|
+
if (!pImpl->pendingHTML.empty()) {
|
|
1130
|
+
pImpl->webview->NavigateToString(
|
|
1131
|
+
std::wstring(pImpl->pendingHTML.begin(),
|
|
1132
|
+
pImpl->pendingHTML.end())
|
|
1133
|
+
.c_str());
|
|
1134
|
+
pImpl->pendingHTML.clear();
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Process pending File
|
|
1138
|
+
if (!pImpl->pendingFile.empty()) {
|
|
1139
|
+
// For now, loadFile is implemented via navigate in
|
|
1140
|
+
// some versions or direct file reading. We'll handle
|
|
1141
|
+
// it via navigate for simplicity if it's already
|
|
1142
|
+
// implemented that way.
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return S_OK;
|
|
1146
|
+
})
|
|
1147
|
+
.Get());
|
|
1148
|
+
return S_OK;
|
|
1149
|
+
})
|
|
1150
|
+
.Get());
|
|
1151
|
+
|
|
1152
|
+
#elif defined(__APPLE__)
|
|
1153
|
+
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
|
|
1154
|
+
config.preferences.javaScriptEnabled = YES;
|
|
1155
|
+
|
|
1156
|
+
if (win.pImpl->config.devtools) {
|
|
1157
|
+
[config.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// Disable webview drag & drop behavior to allow native FileDrop API
|
|
1161
|
+
if (win.pImpl->config.disableWebviewDragDrop) {
|
|
1162
|
+
NSString *disableDragDropScript = @"(function() {"
|
|
1163
|
+
"['dragenter', 'dragover', 'dragleave', 'drop'].forEach(function(eventName) {"
|
|
1164
|
+
"document.addEventListener(eventName, function(e) {"
|
|
1165
|
+
"if (e.target === document.body || e.target === document.documentElement || e.target === document) {"
|
|
1166
|
+
"e.preventDefault();"
|
|
1167
|
+
"e.stopPropagation();"
|
|
1168
|
+
"}"
|
|
1169
|
+
"}, true);"
|
|
1170
|
+
"window.addEventListener(eventName, function(e) {"
|
|
1171
|
+
"e.preventDefault();"
|
|
1172
|
+
"e.stopPropagation();"
|
|
1173
|
+
"}, true);"
|
|
1174
|
+
"});"
|
|
1175
|
+
"})();";
|
|
1176
|
+
|
|
1177
|
+
WKUserScript *userScript = [[WKUserScript alloc]
|
|
1178
|
+
initWithSource:disableDragDropScript
|
|
1179
|
+
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
|
|
1180
|
+
forMainFrameOnly:NO];
|
|
1181
|
+
[config.userContentController addUserScript:userScript];
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// Hide scrollbars if disabled
|
|
1185
|
+
if (!win.pImpl->config.scrollbars) {
|
|
1186
|
+
NSString *scrollbarScript = @"(function() {"
|
|
1187
|
+
"var css = 'html, body { overflow: hidden !important; margin: 0 !important; padding: 0 !important; } ::-webkit-scrollbar { display: none !important; width: 0 !important; height: 0 !important; }';"
|
|
1188
|
+
"var style = document.createElement('style');"
|
|
1189
|
+
"style.type = 'text/css';"
|
|
1190
|
+
"style.appendChild(document.createTextNode(css));"
|
|
1191
|
+
"(document.head || document.documentElement).appendChild(style);"
|
|
1192
|
+
"})();";
|
|
1193
|
+
|
|
1194
|
+
WKUserScript *scrollScript = [[WKUserScript alloc]
|
|
1195
|
+
initWithSource:scrollbarScript
|
|
1196
|
+
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
|
|
1197
|
+
forMainFrameOnly:NO];
|
|
1198
|
+
[config.userContentController addUserScript:scrollScript];
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
NSView *parentView = (__bridge NSView *)windowHandle;
|
|
1202
|
+
win.pImpl->wkWebView =
|
|
1203
|
+
[[WKWebView alloc] initWithFrame:parentView.bounds configuration:config];
|
|
1204
|
+
[parentView addSubview:win.pImpl->wkWebView];
|
|
1205
|
+
|
|
1206
|
+
win.pImpl->nativeWebView = (__bridge void *)win.pImpl->wkWebView;
|
|
1207
|
+
|
|
1208
|
+
#endif
|
|
1209
|
+
|
|
1210
|
+
win.pImpl->trayManager = std::make_unique<TrayManager>();
|
|
1211
|
+
win.pImpl->trayManager->setWindowHandle(windowHandle);
|
|
1212
|
+
|
|
1213
|
+
return win;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
TrayManager &Window::tray() { return *pImpl->trayManager; }
|
|
1217
|
+
|
|
1218
|
+
void Window::setWindow(std::shared_ptr<Window> win) {
|
|
1219
|
+
pImpl->window = win;
|
|
1220
|
+
if (!win)
|
|
1221
|
+
return;
|
|
1222
|
+
|
|
1223
|
+
std::weak_ptr<Impl> weak_pImpl = pImpl;
|
|
1224
|
+
win->onResize([weak_pImpl](int w, int h) {
|
|
1225
|
+
if (auto pImpl = weak_pImpl.lock()) {
|
|
1226
|
+
#ifdef _WIN32
|
|
1227
|
+
if (pImpl->controller) {
|
|
1228
|
+
RECT bounds = {0, 0, w, h};
|
|
1229
|
+
pImpl->controller->put_Bounds(bounds);
|
|
1230
|
+
}
|
|
1231
|
+
#elif defined(__APPLE__)
|
|
1232
|
+
if (pImpl->wkWebView) {
|
|
1233
|
+
NSView *view = (__bridge NSView *)pImpl->wkWebView;
|
|
1234
|
+
NSView *parent = [view superview];
|
|
1235
|
+
if (parent) {
|
|
1236
|
+
[view setFrame:[parent bounds]];
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
#else
|
|
1240
|
+
if (pImpl->gtkWebView) {
|
|
1241
|
+
// GTK usually handles this if added to a container with expand=TRUE
|
|
1242
|
+
// but we can ensure it here if it's a fixed layout parent
|
|
1243
|
+
gtk_widget_set_size_request(GTK_WIDGET(pImpl->gtkWebView), w, h);
|
|
1244
|
+
}
|
|
1245
|
+
#endif
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
// Trigger initial resize
|
|
1250
|
+
int w, h;
|
|
1251
|
+
win->getSize(w, h);
|
|
1252
|
+
#ifdef _WIN32
|
|
1253
|
+
if (pImpl->controller) {
|
|
1254
|
+
RECT bounds = {0, 0, w, h};
|
|
1255
|
+
pImpl->controller->put_Bounds(bounds);
|
|
1256
|
+
}
|
|
1257
|
+
#endif
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
void Window::on(const std::string &event,
|
|
1261
|
+
std::function<void(const std::string &)> callback) {
|
|
1262
|
+
pImpl->events[event] = callback;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
void Window::emit(const std::string &event, const std::string &data) {
|
|
1266
|
+
std::string js = "window.dispatchEvent(new CustomEvent('" + event +
|
|
1267
|
+
"', {detail: " + data + "}))";
|
|
1268
|
+
executeScript(js);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
void Window::navigate(const std::string &url) {
|
|
1272
|
+
pImpl->currentURL = url;
|
|
1273
|
+
pImpl->loading = true;
|
|
1274
|
+
|
|
1275
|
+
#ifdef _WIN32
|
|
1276
|
+
if (pImpl->ready && pImpl->webview) {
|
|
1277
|
+
pImpl->webview->Navigate(std::wstring(url.begin(), url.end()).c_str());
|
|
1278
|
+
} else {
|
|
1279
|
+
pImpl->pendingNavigation = url;
|
|
1280
|
+
}
|
|
1281
|
+
#elif defined(__APPLE__)
|
|
1282
|
+
if (pImpl->wkWebView) {
|
|
1283
|
+
NSURL *nsurl =
|
|
1284
|
+
[NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]];
|
|
1285
|
+
NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
|
|
1286
|
+
[pImpl->wkWebView loadRequest:request];
|
|
1287
|
+
}
|
|
1288
|
+
#else
|
|
1289
|
+
if (pImpl->gtkWebView) {
|
|
1290
|
+
webkit_web_view_load_uri(pImpl->gtkWebView, url.c_str());
|
|
1291
|
+
}
|
|
1292
|
+
#endif
|
|
1293
|
+
|
|
1294
|
+
// Apply scrollbar hiding CSS if configured
|
|
1295
|
+
if (!pImpl->config.scrollbars) {
|
|
1296
|
+
std::string hideScrollbars = R"(
|
|
1297
|
+
(function() {
|
|
1298
|
+
document.documentElement.style.overflow = 'hidden';
|
|
1299
|
+
document.documentElement.style.margin = '0';
|
|
1300
|
+
document.documentElement.style.padding = '0';
|
|
1301
|
+
document.body.style.overflow = 'hidden';
|
|
1302
|
+
document.body.style.margin = '0';
|
|
1303
|
+
document.body.style.padding = '0';
|
|
1304
|
+
})();
|
|
1305
|
+
)";
|
|
1306
|
+
// Schedule this to run after a short delay to ensure DOM is ready
|
|
1307
|
+
#ifdef _WIN32
|
|
1308
|
+
if (pImpl->ready && pImpl->webview) {
|
|
1309
|
+
pImpl->webview->ExecuteScript(
|
|
1310
|
+
std::wstring(hideScrollbars.begin(), hideScrollbars.end()).c_str(),
|
|
1311
|
+
nullptr);
|
|
1312
|
+
}
|
|
1313
|
+
#elif defined(__APPLE__)
|
|
1314
|
+
if (pImpl->wkWebView) {
|
|
1315
|
+
NSString *js = [NSString stringWithUTF8String:hideScrollbars.c_str()];
|
|
1316
|
+
[pImpl->wkWebView evaluateJavaScript:js completionHandler:nil];
|
|
1317
|
+
}
|
|
1318
|
+
#else
|
|
1319
|
+
if (pImpl->gtkWebView) {
|
|
1320
|
+
webkit_web_view_run_javascript(pImpl->gtkWebView, hideScrollbars.c_str(),
|
|
1321
|
+
nullptr, nullptr, nullptr);
|
|
1322
|
+
}
|
|
1323
|
+
#endif
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
void Window::loadURL(const std::string &url) {
|
|
1328
|
+
navigate(url);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
void Window::loadHTML(const std::string &html) {
|
|
1332
|
+
loadHTML(html, "about:blank");
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
void Window::loadHTML(const std::string &html, const std::string &baseURL) {
|
|
1336
|
+
pImpl->loading = true;
|
|
1337
|
+
|
|
1338
|
+
#ifdef _WIN32
|
|
1339
|
+
(void)baseURL; // Not used on Windows
|
|
1340
|
+
if (pImpl->ready && pImpl->webview) {
|
|
1341
|
+
pImpl->webview->NavigateToString(
|
|
1342
|
+
std::wstring(html.begin(), html.end()).c_str());
|
|
1343
|
+
} else {
|
|
1344
|
+
pImpl->pendingHTML = html;
|
|
1345
|
+
}
|
|
1346
|
+
#elif defined(__APPLE__)
|
|
1347
|
+
if (pImpl->wkWebView) {
|
|
1348
|
+
NSString *htmlString = [NSString stringWithUTF8String:html.c_str()];
|
|
1349
|
+
NSURL *base =
|
|
1350
|
+
[NSURL URLWithString:[NSString stringWithUTF8String:baseURL.c_str()]];
|
|
1351
|
+
[pImpl->wkWebView loadHTMLString:htmlString baseURL:base];
|
|
1352
|
+
}
|
|
1353
|
+
#else
|
|
1354
|
+
if (pImpl->gtkWebView) {
|
|
1355
|
+
webkit_web_view_load_html(pImpl->gtkWebView, html.c_str(), baseURL.c_str());
|
|
1356
|
+
}
|
|
1357
|
+
#endif
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
void Window::loadFile(const std::string &filePath) {
|
|
1361
|
+
std::ifstream file(filePath);
|
|
1362
|
+
if (!file) {
|
|
1363
|
+
std::cerr << "PlusUI: Failed to open file: " << filePath << std::endl;
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
std::stringstream buffer;
|
|
1368
|
+
buffer << file.rdbuf();
|
|
1369
|
+
|
|
1370
|
+
// Use file:// URL as base for relative paths
|
|
1371
|
+
std::string baseURL =
|
|
1372
|
+
"file://" + filePath.substr(0, filePath.find_last_of("/\\") + 1);
|
|
1373
|
+
loadHTML(buffer.str(), baseURL);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
void Window::reload() {
|
|
1377
|
+
#ifdef _WIN32
|
|
1378
|
+
if (pImpl->webview) {
|
|
1379
|
+
pImpl->webview->Reload();
|
|
1380
|
+
}
|
|
1381
|
+
#elif defined(__APPLE__)
|
|
1382
|
+
if (pImpl->wkWebView) {
|
|
1383
|
+
[pImpl->wkWebView reload];
|
|
1384
|
+
}
|
|
1385
|
+
#else
|
|
1386
|
+
if (pImpl->gtkWebView) {
|
|
1387
|
+
webkit_web_view_reload(pImpl->gtkWebView);
|
|
1388
|
+
}
|
|
1389
|
+
#endif
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
void Window::stop() {
|
|
1393
|
+
#ifdef _WIN32
|
|
1394
|
+
if (pImpl->webview) {
|
|
1395
|
+
pImpl->webview->Stop();
|
|
1396
|
+
}
|
|
1397
|
+
#elif defined(__APPLE__)
|
|
1398
|
+
if (pImpl->wkWebView) {
|
|
1399
|
+
[pImpl->wkWebView stopLoading];
|
|
1400
|
+
}
|
|
1401
|
+
#else
|
|
1402
|
+
if (pImpl->gtkWebView) {
|
|
1403
|
+
webkit_web_view_stop_loading(pImpl->gtkWebView);
|
|
1404
|
+
}
|
|
1405
|
+
#endif
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
void Window::goBack() {
|
|
1409
|
+
#ifdef _WIN32
|
|
1410
|
+
if (pImpl->webview) {
|
|
1411
|
+
pImpl->webview->GoBack();
|
|
1412
|
+
}
|
|
1413
|
+
#elif defined(__APPLE__)
|
|
1414
|
+
if (pImpl->wkWebView) {
|
|
1415
|
+
[pImpl->wkWebView goBack];
|
|
1416
|
+
}
|
|
1417
|
+
#else
|
|
1418
|
+
if (pImpl->gtkWebView) {
|
|
1419
|
+
webkit_web_view_go_back(pImpl->gtkWebView);
|
|
1420
|
+
}
|
|
1421
|
+
#endif
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
void Window::goForward() {
|
|
1425
|
+
#ifdef _WIN32
|
|
1426
|
+
if (pImpl->webview) {
|
|
1427
|
+
pImpl->webview->GoForward();
|
|
1428
|
+
}
|
|
1429
|
+
#elif defined(__APPLE__)
|
|
1430
|
+
if (pImpl->wkWebView) {
|
|
1431
|
+
[pImpl->wkWebView goForward];
|
|
1432
|
+
}
|
|
1433
|
+
#else
|
|
1434
|
+
if (pImpl->gtkWebView) {
|
|
1435
|
+
webkit_web_view_go_forward(pImpl->gtkWebView);
|
|
1436
|
+
}
|
|
1437
|
+
#endif
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
bool Window::canGoBack() const {
|
|
1441
|
+
#ifdef _WIN32
|
|
1442
|
+
if (pImpl->webview) {
|
|
1443
|
+
BOOL canGoBack;
|
|
1444
|
+
pImpl->webview->get_CanGoBack(&canGoBack);
|
|
1445
|
+
return canGoBack;
|
|
1446
|
+
}
|
|
1447
|
+
#elif defined(__APPLE__)
|
|
1448
|
+
if (pImpl->wkWebView) {
|
|
1449
|
+
return [pImpl->wkWebView canGoBack];
|
|
1450
|
+
}
|
|
1451
|
+
#else
|
|
1452
|
+
if (pImpl->gtkWebView) {
|
|
1453
|
+
return webkit_web_view_can_go_back(pImpl->gtkWebView);
|
|
1454
|
+
}
|
|
1455
|
+
#endif
|
|
1456
|
+
return false;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
bool Window::canGoForward() const {
|
|
1460
|
+
#ifdef _WIN32
|
|
1461
|
+
if (pImpl->webview) {
|
|
1462
|
+
BOOL canGoForward;
|
|
1463
|
+
pImpl->webview->get_CanGoForward(&canGoForward);
|
|
1464
|
+
return canGoForward;
|
|
1465
|
+
}
|
|
1466
|
+
#elif defined(__APPLE__)
|
|
1467
|
+
if (pImpl->wkWebView) {
|
|
1468
|
+
return [pImpl->wkWebView canGoForward];
|
|
1469
|
+
}
|
|
1470
|
+
#else
|
|
1471
|
+
if (pImpl->gtkWebView) {
|
|
1472
|
+
return webkit_web_view_can_go_forward(pImpl->gtkWebView);
|
|
1473
|
+
}
|
|
1474
|
+
#endif
|
|
1475
|
+
return false;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
void Window::executeScript(const std::string &script) {
|
|
1479
|
+
#ifdef _WIN32
|
|
1480
|
+
if (pImpl->ready && pImpl->webview) {
|
|
1481
|
+
pImpl->webview->ExecuteScript(
|
|
1482
|
+
std::wstring(script.begin(), script.end()).c_str(), nullptr);
|
|
1483
|
+
} else {
|
|
1484
|
+
pImpl->pendingScripts.push_back(script);
|
|
1485
|
+
}
|
|
1486
|
+
#elif defined(__APPLE__)
|
|
1487
|
+
if (pImpl->wkWebView) {
|
|
1488
|
+
NSString *js = [NSString stringWithUTF8String:script.c_str()];
|
|
1489
|
+
[pImpl->wkWebView evaluateJavaScript:js completionHandler:nil];
|
|
1490
|
+
}
|
|
1491
|
+
#else
|
|
1492
|
+
if (pImpl->gtkWebView) {
|
|
1493
|
+
webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
|
|
1494
|
+
nullptr, nullptr);
|
|
1495
|
+
}
|
|
1496
|
+
#endif
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
void Window::executeScript(const std::string &script,
|
|
1500
|
+
std::function<void(const std::string &)> callback) {
|
|
1501
|
+
#ifdef _WIN32
|
|
1502
|
+
if (pImpl->webview) {
|
|
1503
|
+
pImpl->webview->ExecuteScript(
|
|
1504
|
+
std::wstring(script.begin(), script.end()).c_str(),
|
|
1505
|
+
Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
|
|
1506
|
+
[callback](HRESULT error, LPCWSTR result) -> HRESULT {
|
|
1507
|
+
(void)error; // Suppress unused warning
|
|
1508
|
+
if (result && callback) {
|
|
1509
|
+
std::wstring wstr(result);
|
|
1510
|
+
callback(std::string(wstr.begin(), wstr.end()));
|
|
1511
|
+
}
|
|
1512
|
+
return S_OK;
|
|
1513
|
+
})
|
|
1514
|
+
.Get());
|
|
1515
|
+
}
|
|
1516
|
+
#elif defined(__APPLE__)
|
|
1517
|
+
if (pImpl->wkWebView) {
|
|
1518
|
+
NSString *js = [NSString stringWithUTF8String:script.c_str()];
|
|
1519
|
+
[pImpl->wkWebView evaluateJavaScript:js
|
|
1520
|
+
completionHandler:^(id result, NSError *error) {
|
|
1521
|
+
if (result && callback) {
|
|
1522
|
+
NSString *resultStr =
|
|
1523
|
+
[NSString stringWithFormat:@"%@", result];
|
|
1524
|
+
callback([resultStr UTF8String]);
|
|
1525
|
+
}
|
|
1526
|
+
}];
|
|
1527
|
+
}
|
|
1528
|
+
#else
|
|
1529
|
+
if (pImpl->gtkWebView) {
|
|
1530
|
+
// GTK WebKit callback handling would go here
|
|
1531
|
+
webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
|
|
1532
|
+
nullptr, nullptr);
|
|
1533
|
+
}
|
|
1534
|
+
#endif
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
void Window::bind(const std::string &name, JSCallback callback) {
|
|
1538
|
+
pImpl->bindings[name] = callback;
|
|
1539
|
+
|
|
1540
|
+
// Inject bridge code to expose function to JavaScript
|
|
1541
|
+
std::string bridgeScript = R"(
|
|
1542
|
+
window.)" + name + R"( = function(...args) {
|
|
1543
|
+
return window.plusui.invoke('webview.)" +
|
|
1544
|
+
name + R"(', args);
|
|
1545
|
+
};
|
|
1546
|
+
)";
|
|
1547
|
+
|
|
1548
|
+
executeScript(bridgeScript);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
void Window::unbind(const std::string &name) {
|
|
1552
|
+
pImpl->bindings.erase(name);
|
|
1553
|
+
|
|
1554
|
+
std::string script = "delete window." + name + ";";
|
|
1555
|
+
executeScript(script);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
void Window::openDevTools() {
|
|
1559
|
+
#ifdef _WIN32
|
|
1560
|
+
if (pImpl->webview) {
|
|
1561
|
+
pImpl->webview->OpenDevToolsWindow();
|
|
1562
|
+
}
|
|
1563
|
+
#elif defined(__APPLE__)
|
|
1564
|
+
// macOS: Dev tools open in Safari's Web Inspector
|
|
1565
|
+
#else
|
|
1566
|
+
if (pImpl->gtkWebView) {
|
|
1567
|
+
WebKitWebInspector *inspector =
|
|
1568
|
+
webkit_web_view_get_inspector(pImpl->gtkWebView);
|
|
1569
|
+
webkit_web_inspector_show(inspector);
|
|
1570
|
+
}
|
|
1571
|
+
#endif
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
void Window::closeDevTools() {
|
|
1575
|
+
#ifdef _WIN32
|
|
1576
|
+
// WebView2 doesn't have explicit close
|
|
1577
|
+
#elif defined(__APPLE__)
|
|
1578
|
+
// macOS: Handled by Web Inspector
|
|
1579
|
+
#else
|
|
1580
|
+
if (pImpl->gtkWebView) {
|
|
1581
|
+
WebKitWebInspector *inspector =
|
|
1582
|
+
webkit_web_view_get_inspector(pImpl->gtkWebView);
|
|
1583
|
+
webkit_web_inspector_close(inspector);
|
|
1584
|
+
}
|
|
1585
|
+
#endif
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
void Window::setUserAgent(const std::string &userAgent) {
|
|
1589
|
+
pImpl->userAgent = userAgent;
|
|
1590
|
+
|
|
1591
|
+
#ifdef _WIN32
|
|
1592
|
+
if (pImpl->webview) {
|
|
1593
|
+
ComPtr<ICoreWebView2Settings> settings;
|
|
1594
|
+
pImpl->webview->get_Settings(&settings);
|
|
1595
|
+
// WebView2 user agent requires settings2 interface
|
|
1596
|
+
}
|
|
1597
|
+
#elif defined(__APPLE__)
|
|
1598
|
+
if (pImpl->wkWebView) {
|
|
1599
|
+
[pImpl->wkWebView
|
|
1600
|
+
setCustomUserAgent:[NSString stringWithUTF8String:userAgent.c_str()]];
|
|
1601
|
+
}
|
|
1602
|
+
#else
|
|
1603
|
+
if (pImpl->gtkWebView) {
|
|
1604
|
+
WebKitSettings *settings = webkit_web_view_get_settings(pImpl->gtkWebView);
|
|
1605
|
+
webkit_settings_set_user_agent(settings, userAgent.c_str());
|
|
1606
|
+
}
|
|
1607
|
+
#endif
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
std::string Window::getUserAgent() const { return pImpl->userAgent; }
|
|
1611
|
+
|
|
1612
|
+
void Window::setZoom(double factor) {
|
|
1613
|
+
pImpl->zoom = factor;
|
|
1614
|
+
|
|
1615
|
+
#ifdef _WIN32
|
|
1616
|
+
if (pImpl->controller) {
|
|
1617
|
+
pImpl->controller->put_ZoomFactor(factor);
|
|
1618
|
+
}
|
|
1619
|
+
#elif defined(__APPLE__)
|
|
1620
|
+
if (pImpl->wkWebView) {
|
|
1621
|
+
[pImpl->wkWebView setPageZoom:factor];
|
|
1622
|
+
}
|
|
1623
|
+
#else
|
|
1624
|
+
if (pImpl->gtkWebView) {
|
|
1625
|
+
webkit_web_view_set_zoom_level(pImpl->gtkWebView, factor);
|
|
1626
|
+
}
|
|
1627
|
+
#endif
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
double Window::getZoom() const { return pImpl->zoom; }
|
|
1631
|
+
|
|
1632
|
+
void Window::injectCSS(const std::string &css) {
|
|
1633
|
+
std::string script = R"(
|
|
1634
|
+
(function() {
|
|
1635
|
+
const style = document.createElement('style');
|
|
1636
|
+
style.id = 'plusui-injected-css-' + Date.now();
|
|
1637
|
+
style.textContent = `)" +
|
|
1638
|
+
css + R"(`;
|
|
1639
|
+
if (document.head) {
|
|
1640
|
+
document.head.appendChild(style);
|
|
1641
|
+
} else {
|
|
1642
|
+
document.documentElement.appendChild(style);
|
|
1643
|
+
}
|
|
1644
|
+
})();
|
|
1645
|
+
)";
|
|
1646
|
+
|
|
1647
|
+
executeScript(script);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
void Window::setWebviewDragDropEnabled(bool enabled) {
|
|
1651
|
+
std::string script;
|
|
1652
|
+
|
|
1653
|
+
if (enabled) {
|
|
1654
|
+
// Enable webview drag-drop by removing our prevention handlers
|
|
1655
|
+
script = R"(
|
|
1656
|
+
(function() {
|
|
1657
|
+
if (window.__plusui_dragDropDisabled) {
|
|
1658
|
+
delete window.__plusui_dragDropDisabled;
|
|
1659
|
+
}
|
|
1660
|
+
})();
|
|
1661
|
+
)";
|
|
1662
|
+
} else {
|
|
1663
|
+
// Disable webview drag-drop by preventing default behavior
|
|
1664
|
+
script = R"(
|
|
1665
|
+
(function() {
|
|
1666
|
+
if (window.__plusui_dragDropDisabled) return; // Already disabled
|
|
1667
|
+
window.__plusui_dragDropDisabled = true;
|
|
1668
|
+
|
|
1669
|
+
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(function(eventName) {
|
|
1670
|
+
document.addEventListener(eventName, function(e) {
|
|
1671
|
+
if (e.target === document.body || e.target === document.documentElement || e.target === document) {
|
|
1672
|
+
e.preventDefault();
|
|
1673
|
+
e.stopPropagation();
|
|
1674
|
+
}
|
|
1675
|
+
}, true);
|
|
1676
|
+
|
|
1677
|
+
window.addEventListener(eventName, function(e) {
|
|
1678
|
+
e.preventDefault();
|
|
1679
|
+
e.stopPropagation();
|
|
1680
|
+
}, true);
|
|
1681
|
+
});
|
|
1682
|
+
})();
|
|
1683
|
+
)";
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
executeScript(script);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
void Window::onNavigationStart(NavigationCallback callback) {
|
|
1690
|
+
pImpl->navigationCallback = callback;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
void Window::onNavigationComplete(LoadCallback callback) {
|
|
1694
|
+
pImpl->navigationCompleteCallback = callback;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
void Window::onLoadStart(LoadCallback callback) {
|
|
1698
|
+
pImpl->loadStartCallback = callback;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
void Window::onLoadEnd(LoadCallback callback) {
|
|
1702
|
+
pImpl->loadEndCallback = callback;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
void Window::onLoadError(ErrorCallback callback) {
|
|
1706
|
+
pImpl->errorCallback = callback;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
void Window::onConsoleMessage(ConsoleCallback callback) {
|
|
1710
|
+
pImpl->consoleCallback = callback;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
bool Window::isLoading() const { return pImpl->loading; }
|
|
1714
|
+
|
|
1715
|
+
std::string Window::getURL() const { return pImpl->currentURL; }
|
|
1716
|
+
|
|
680
1717
|
} // namespace plusui
|