plusui-native-core 0.1.105 → 0.1.107
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Core/.cache/clangd/index/api.hpp.016B34C8046EF490.idx +0 -0
- package/Core/.cache/clangd/index/app.cpp.1E6FAF043D496421.idx +0 -0
- package/Core/.cache/clangd/index/app.hpp.FA0E5D412C4E6148.idx +0 -0
- package/Core/.cache/clangd/index/browser.cpp.9461A2CAF129F1D9.idx +0 -0
- package/Core/.cache/clangd/index/browser.hpp.BE40AE80881B3107.idx +0 -0
- package/Core/.cache/clangd/index/clipboard.cpp.2399913537B2A7AD.idx +0 -0
- package/Core/.cache/clangd/index/clipboard.hpp.C1095DDACD7149E9.idx +0 -0
- package/Core/.cache/clangd/index/connect.cpp.518C66C7C28B30A9.idx +0 -0
- package/Core/.cache/clangd/index/connect.hpp.08E2F7CD13B78601.idx +0 -0
- package/Core/.cache/clangd/index/connection.hpp.849FAEF1523BF2C3.idx +0 -0
- package/Core/.cache/clangd/index/display.cpp.F6F6D932BF9F8D8E.idx +0 -0
- package/Core/.cache/clangd/index/display.hpp.0C1A9CAD11EE4404.idx +0 -0
- package/Core/.cache/clangd/index/filedrop.cpp.669B524B3C501C52.idx +0 -0
- package/Core/.cache/clangd/index/filedrop.hpp.48460099C3F35F2D.idx +0 -0
- package/Core/.cache/clangd/index/keyboard.cpp.DC6D34E4A4F798DD.idx +0 -0
- package/Core/.cache/clangd/index/keyboard.hpp.F016CB68D7DE5A46.idx +0 -0
- package/Core/.cache/clangd/index/keyboard_linux.cpp.B403FDCEA7A6CA53.idx +0 -0
- package/Core/.cache/clangd/index/menu.cpp.3059F08D8D2DF265.idx +0 -0
- package/Core/.cache/clangd/index/menu.hpp.8716DCCC573910D4.idx +0 -0
- package/Core/.cache/clangd/index/plusui.hpp.8CFCDFDC2E3F41DD.idx +0 -0
- package/Core/.cache/clangd/index/router.cpp.EAC9EAD34C59E573.idx +0 -0
- package/Core/.cache/clangd/index/router.hpp.2B06E2EE9998468D.idx +0 -0
- package/Core/.cache/clangd/index/stb_image.h.E26CF48FE089A0D3.idx +0 -0
- package/Core/.cache/clangd/index/tray.cpp.92F244E7E1D7F0EC.idx +0 -0
- package/Core/.cache/clangd/index/tray.hpp.0D881B0601BBBD25.idx +0 -0
- package/Core/.cache/clangd/index/webgpu.cpp.FC656FA8BE10FE15.idx +0 -0
- package/Core/.cache/clangd/index/webgpu.hpp.5AF1A5E9DF9E5AE0.idx +0 -0
- package/Core/.cache/clangd/index/window.cpp.191D8C9ADF874B22.idx +0 -0
- package/Core/.cache/clangd/index/window.hpp.B9811B43AA295697.idx +0 -0
- package/Core/.claude/settings.local.json +7 -0
- package/Core/CMakeLists.txt +1 -1
- package/Core/Features/API/app-api.ts +28 -28
- package/Core/Features/API/browser-api.ts +38 -38
- package/Core/Features/API/clipboard-api.ts +21 -21
- package/Core/Features/API/display-api.ts +33 -33
- package/Core/Features/API/keyboard-api.ts +33 -33
- package/Core/Features/API/menu-api.ts +39 -39
- package/Core/Features/API/router-api.ts +23 -23
- package/Core/Features/API/tray-api.ts +22 -22
- package/Core/Features/API/webgpu-api.ts +55 -55
- package/Core/Features/App/app.cpp +135 -102
- package/Core/Features/Browser/browser.cpp +227 -227
- package/Core/Features/Browser/browser.ts +161 -161
- package/Core/Features/Clipboard/clipboard.cpp +235 -235
- package/Core/Features/Display/display.cpp +212 -212
- package/Core/Features/FileDrop/filedrop.cpp +448 -324
- package/Core/Features/FileDrop/filedrop.css +421 -421
- package/Core/Features/FileDrop/filedrop.ts +0 -7
- package/Core/Features/Keyboard/keyboard_linux.cpp +4 -0
- package/Core/Features/Router/router.cpp +62 -62
- package/Core/Features/Router/router.ts +113 -113
- package/Core/Features/Tray/tray.cpp +437 -324
- package/Core/Features/WebGPU/webgpu.cpp +948 -948
- package/Core/Features/Window/webview.cpp +1009 -1009
- package/Core/Features/Window/webview.ts +516 -516
- package/Core/Features/Window/window.cpp +2240 -1986
- package/Core/include/plusui/api.hpp +237 -237
- package/Core/include/plusui/app.hpp +36 -34
- package/Core/include/plusui/browser.hpp +67 -67
- package/Core/include/plusui/clipboard.hpp +41 -41
- package/Core/include/plusui/connect.hpp +340 -340
- package/Core/include/plusui/connection.hpp +3 -3
- package/Core/include/plusui/display.hpp +90 -90
- package/Core/include/plusui/filedrop.hpp +92 -77
- package/Core/include/plusui/keyboard.hpp +112 -112
- package/Core/include/plusui/menu.hpp +153 -153
- package/Core/include/plusui/plusui +18 -18
- package/Core/include/plusui/router.hpp +42 -42
- package/Core/include/plusui/tray.hpp +94 -94
- package/Core/include/plusui/webgpu.hpp +434 -434
- package/Core/include/plusui/window.hpp +180 -177
- package/Core/scripts/generate-umbrella-header.mjs +77 -77
- package/Core/vendor/WebView2EnvironmentOptions.h +406 -406
- package/Core/vendor/webview.h +487 -487
- package/Core/vendor/webview2.h +52079 -52079
- package/README.md +19 -19
- package/package.json +1 -1
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
#include <fstream>
|
|
2
|
-
#include <functional>
|
|
3
|
-
#include <iostream>
|
|
4
|
-
#include <map>
|
|
5
|
-
#include <plusui/display.hpp>
|
|
6
|
-
#include <plusui/tray.hpp>
|
|
7
|
-
#include <plusui/webgpu.hpp>
|
|
8
|
-
#include <plusui/window.hpp>
|
|
9
|
-
#include <regex>
|
|
10
|
-
#include <sstream>
|
|
11
|
-
|
|
12
|
-
#ifdef _WIN32
|
|
13
|
-
#pragma warning(push)
|
|
14
|
-
#pragma warning(disable : 4996)
|
|
15
|
-
#include <WebView2.h>
|
|
16
|
-
#include <windows.h>
|
|
17
|
-
#include <wrl.h>
|
|
18
|
-
|
|
19
|
-
#
|
|
1
|
+
#include <fstream>
|
|
2
|
+
#include <functional>
|
|
3
|
+
#include <iostream>
|
|
4
|
+
#include <map>
|
|
5
|
+
#include <plusui/display.hpp>
|
|
6
|
+
#include <plusui/tray.hpp>
|
|
7
|
+
#include <plusui/webgpu.hpp>
|
|
8
|
+
#include <plusui/window.hpp>
|
|
9
|
+
#include <regex>
|
|
10
|
+
#include <sstream>
|
|
11
|
+
|
|
12
|
+
#ifdef _WIN32
|
|
13
|
+
#pragma warning(push)
|
|
14
|
+
#pragma warning(disable : 4996)
|
|
15
|
+
#include <WebView2.h>
|
|
16
|
+
#include <windows.h>
|
|
17
|
+
#include <wrl.h>
|
|
18
|
+
#include <shlobj.h>
|
|
19
|
+
#include <oleidl.h>
|
|
20
|
+
#include <shellapi.h>
|
|
21
|
+
using namespace Microsoft::WRL;
|
|
22
|
+
#pragma warning(pop)
|
|
20
23
|
#elif defined(__APPLE__)
|
|
21
24
|
#include <Cocoa/Cocoa.h>
|
|
22
25
|
#import <AppKit/AppKit.h>
|
|
@@ -25,66 +28,67 @@ using namespace Microsoft::WRL;
|
|
|
25
28
|
#include <objc/objc-runtime.h>
|
|
26
29
|
|
|
27
30
|
#else
|
|
31
|
+
#include <cstdlib>
|
|
28
32
|
#include <gdk/gdk.h>
|
|
29
33
|
#include <gtk/gtk.h>
|
|
30
34
|
#include <webkit2/webkit2.h>
|
|
31
35
|
#endif
|
|
32
|
-
|
|
33
|
-
#include <stb_image.h>
|
|
34
|
-
|
|
35
|
-
namespace plusui {
|
|
36
|
-
|
|
37
|
-
namespace {
|
|
38
|
-
constexpr char kHideScrollbarsScript[] = R"(
|
|
39
|
-
(function() {
|
|
40
|
-
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;}";
|
|
41
|
-
var styleId = "__plusui_hide_scrollbars";
|
|
42
|
-
var ensureStyle = function() {
|
|
43
|
-
if (document.getElementById(styleId)) return;
|
|
44
|
-
var style = document.createElement("style");
|
|
45
|
-
style.id = styleId;
|
|
46
|
-
style.type = "text/css";
|
|
47
|
-
style.appendChild(document.createTextNode(css));
|
|
48
|
-
var container = document.head || document.documentElement || document.body;
|
|
49
|
-
if (container) {
|
|
50
|
-
container.appendChild(style);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
ensureStyle();
|
|
54
|
-
document.addEventListener("DOMContentLoaded", ensureStyle);
|
|
55
|
-
window.addEventListener("load", ensureStyle);
|
|
56
|
-
})();
|
|
57
|
-
)";
|
|
58
|
-
|
|
59
|
-
#ifdef _WIN32
|
|
60
|
-
static std::string jsonEscape(const std::string &input) {
|
|
61
|
-
std::string out;
|
|
62
|
-
out.reserve(input.size() + 8);
|
|
63
|
-
for (char c : input) {
|
|
64
|
-
switch (c) {
|
|
65
|
-
case '\\':
|
|
66
|
-
out += "\\\\";
|
|
67
|
-
break;
|
|
68
|
-
case '"':
|
|
69
|
-
out += "\\\"";
|
|
70
|
-
break;
|
|
71
|
-
case '\n':
|
|
72
|
-
out += "\\n";
|
|
73
|
-
break;
|
|
74
|
-
case '\r':
|
|
75
|
-
out += "\\r";
|
|
76
|
-
break;
|
|
77
|
-
case '\t':
|
|
78
|
-
out += "\\t";
|
|
79
|
-
break;
|
|
80
|
-
default:
|
|
81
|
-
out += c;
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return out;
|
|
86
|
-
}
|
|
87
|
-
|
|
36
|
+
|
|
37
|
+
#include <stb_image.h>
|
|
38
|
+
|
|
39
|
+
namespace plusui {
|
|
40
|
+
|
|
41
|
+
namespace {
|
|
42
|
+
constexpr char kHideScrollbarsScript[] = R"(
|
|
43
|
+
(function() {
|
|
44
|
+
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;}";
|
|
45
|
+
var styleId = "__plusui_hide_scrollbars";
|
|
46
|
+
var ensureStyle = function() {
|
|
47
|
+
if (document.getElementById(styleId)) return;
|
|
48
|
+
var style = document.createElement("style");
|
|
49
|
+
style.id = styleId;
|
|
50
|
+
style.type = "text/css";
|
|
51
|
+
style.appendChild(document.createTextNode(css));
|
|
52
|
+
var container = document.head || document.documentElement || document.body;
|
|
53
|
+
if (container) {
|
|
54
|
+
container.appendChild(style);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
ensureStyle();
|
|
58
|
+
document.addEventListener("DOMContentLoaded", ensureStyle);
|
|
59
|
+
window.addEventListener("load", ensureStyle);
|
|
60
|
+
})();
|
|
61
|
+
)";
|
|
62
|
+
|
|
63
|
+
#ifdef _WIN32
|
|
64
|
+
static std::string jsonEscape(const std::string &input) {
|
|
65
|
+
std::string out;
|
|
66
|
+
out.reserve(input.size() + 8);
|
|
67
|
+
for (char c : input) {
|
|
68
|
+
switch (c) {
|
|
69
|
+
case '\\':
|
|
70
|
+
out += "\\\\";
|
|
71
|
+
break;
|
|
72
|
+
case '"':
|
|
73
|
+
out += "\\\"";
|
|
74
|
+
break;
|
|
75
|
+
case '\n':
|
|
76
|
+
out += "\\n";
|
|
77
|
+
break;
|
|
78
|
+
case '\r':
|
|
79
|
+
out += "\\r";
|
|
80
|
+
break;
|
|
81
|
+
case '\t':
|
|
82
|
+
out += "\\t";
|
|
83
|
+
break;
|
|
84
|
+
default:
|
|
85
|
+
out += c;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
|
|
88
92
|
// Map Windows VK code to PlusUI KeyCode (GLFW-style values)
|
|
89
93
|
static int vkToPlusUIKeyCode(WPARAM vk) {
|
|
90
94
|
if (vk >= 'A' && vk <= 'Z') return (int)vk; // A=65…Z=90
|
|
@@ -121,31 +125,41 @@ static int currentKeyMods() {
|
|
|
121
125
|
return mods;
|
|
122
126
|
}
|
|
123
127
|
|
|
124
|
-
static std::string mimeTypeFromPath(const std::string &path) {
|
|
125
|
-
size_t dot = path.find_last_of('.');
|
|
126
|
-
if (dot == std::string::npos)
|
|
127
|
-
return "application/octet-stream";
|
|
128
|
-
std::string ext = path.substr(dot);
|
|
129
|
-
for (char &ch : ext)
|
|
130
|
-
ch = static_cast<char>(tolower(static_cast<unsigned char>(ch)));
|
|
131
|
-
if (ext == ".png")
|
|
132
|
-
return "image/png";
|
|
133
|
-
if (ext == ".jpg" || ext == ".jpeg")
|
|
134
|
-
return "image/jpeg";
|
|
135
|
-
if (ext == ".gif")
|
|
136
|
-
return "image/gif";
|
|
137
|
-
if (ext == ".webp")
|
|
138
|
-
return "image/webp";
|
|
139
|
-
if (ext == ".svg")
|
|
140
|
-
return "image/svg+xml";
|
|
141
|
-
if (ext == ".txt")
|
|
142
|
-
return "text/plain";
|
|
143
|
-
if (ext == ".json")
|
|
144
|
-
return "application/json";
|
|
145
|
-
if (ext == ".pdf")
|
|
146
|
-
return "application/pdf";
|
|
128
|
+
static std::string mimeTypeFromPath(const std::string &path) {
|
|
129
|
+
size_t dot = path.find_last_of('.');
|
|
130
|
+
if (dot == std::string::npos)
|
|
131
|
+
return "application/octet-stream";
|
|
132
|
+
std::string ext = path.substr(dot);
|
|
133
|
+
for (char &ch : ext)
|
|
134
|
+
ch = static_cast<char>(tolower(static_cast<unsigned char>(ch)));
|
|
135
|
+
if (ext == ".png")
|
|
136
|
+
return "image/png";
|
|
137
|
+
if (ext == ".jpg" || ext == ".jpeg")
|
|
138
|
+
return "image/jpeg";
|
|
139
|
+
if (ext == ".gif")
|
|
140
|
+
return "image/gif";
|
|
141
|
+
if (ext == ".webp")
|
|
142
|
+
return "image/webp";
|
|
143
|
+
if (ext == ".svg")
|
|
144
|
+
return "image/svg+xml";
|
|
145
|
+
if (ext == ".txt")
|
|
146
|
+
return "text/plain";
|
|
147
|
+
if (ext == ".json")
|
|
148
|
+
return "application/json";
|
|
149
|
+
if (ext == ".pdf")
|
|
150
|
+
return "application/pdf";
|
|
147
151
|
return "application/octet-stream";
|
|
148
152
|
}
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
//
|
|
155
|
+
// WebView2 has a design problem: AllowExternalDrop(TRUE) makes it consume
|
|
156
|
+
// drops so WM_DROPFILES never fires; AllowExternalDrop(FALSE) makes it
|
|
157
|
+
// reject drags entirely so even WM_DROPFILES never fires. The solution is
|
|
158
|
+
// to keep AllowExternalDrop(FALSE) and register our own IDropTarget on the
|
|
159
|
+
// parent HWND via RegisterDragDrop. This intercepts drops before WebView2
|
|
160
|
+
// ever sees them, giving us full control over visual feedback AND file data.
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
149
163
|
#endif // _WIN32
|
|
150
164
|
|
|
151
165
|
#if !defined(_WIN32)
|
|
@@ -181,200 +195,85 @@ static std::string buildShortcutScript(const std::string& id) {
|
|
|
181
195
|
#endif // !_WIN32
|
|
182
196
|
|
|
183
197
|
} // namespace
|
|
184
|
-
|
|
185
|
-
struct Window::Impl {
|
|
186
|
-
void *nativeWindow = nullptr;
|
|
187
|
-
void *nativeWebView = nullptr;
|
|
188
|
-
WindowConfig config;
|
|
189
|
-
WindowState state;
|
|
190
|
-
|
|
191
|
-
// WebView-specific members
|
|
192
|
-
std::string currentURL;
|
|
193
|
-
std::string currentTitle;
|
|
194
|
-
std::shared_ptr<Window> window;
|
|
195
|
-
std::string userAgent;
|
|
196
|
-
bool loading = false;
|
|
197
|
-
double zoom = 1.0;
|
|
198
|
-
bool ready = false;
|
|
199
|
-
std::vector<std::string> pendingScripts;
|
|
200
|
-
std::string pendingNavigation;
|
|
201
|
-
std::string pendingHTML;
|
|
202
|
-
std::string pendingFile;
|
|
203
|
-
std::unique_ptr<TrayManager> trayManager;
|
|
204
|
-
WebGPU webgpu;
|
|
205
|
-
NavigationCallback navigationCallback;
|
|
206
|
-
LoadCallback loadStartCallback;
|
|
207
|
-
LoadCallback loadEndCallback;
|
|
208
|
-
LoadCallback navigationCompleteCallback;
|
|
209
|
-
ErrorCallback errorCallback;
|
|
210
|
-
ConsoleCallback consoleCallback;
|
|
211
|
-
MessageCallback messageCallback;
|
|
198
|
+
|
|
199
|
+
struct Window::Impl {
|
|
200
|
+
void *nativeWindow = nullptr;
|
|
201
|
+
void *nativeWebView = nullptr;
|
|
202
|
+
WindowConfig config;
|
|
203
|
+
WindowState state;
|
|
204
|
+
|
|
205
|
+
// WebView-specific members
|
|
206
|
+
std::string currentURL;
|
|
207
|
+
std::string currentTitle;
|
|
208
|
+
std::shared_ptr<Window> window;
|
|
209
|
+
std::string userAgent;
|
|
210
|
+
bool loading = false;
|
|
211
|
+
double zoom = 1.0;
|
|
212
|
+
bool ready = false;
|
|
213
|
+
std::vector<std::string> pendingScripts;
|
|
214
|
+
std::string pendingNavigation;
|
|
215
|
+
std::string pendingHTML;
|
|
216
|
+
std::string pendingFile;
|
|
217
|
+
std::unique_ptr<TrayManager> trayManager;
|
|
218
|
+
WebGPU webgpu;
|
|
219
|
+
NavigationCallback navigationCallback;
|
|
220
|
+
LoadCallback loadStartCallback;
|
|
221
|
+
LoadCallback loadEndCallback;
|
|
222
|
+
LoadCallback navigationCompleteCallback;
|
|
223
|
+
ErrorCallback errorCallback;
|
|
224
|
+
ConsoleCallback consoleCallback;
|
|
225
|
+
MessageCallback messageCallback;
|
|
212
226
|
FileDropCallback fileDropCallback;
|
|
213
227
|
std::map<std::string, JSCallback> bindings;
|
|
214
228
|
std::map<int, std::string> hotkeys; // hotkey id -> shortcut id
|
|
215
|
-
|
|
216
|
-
std::vector<MoveCallback> moveCallbacks;
|
|
217
|
-
std::vector<ResizeCallback> resizeCallbacks;
|
|
218
|
-
std::vector<CloseCallback> closeCallbacks;
|
|
219
|
-
std::vector<FocusCallback> focusCallbacks;
|
|
220
|
-
std::vector<StateCallback> stateCallbacks;
|
|
221
|
-
|
|
222
|
-
#ifdef _WIN32
|
|
223
|
-
HWND hwnd = nullptr;
|
|
224
|
-
WNDPROC originalProc = nullptr;
|
|
225
|
-
ComPtr<ICoreWebView2Controller> controller;
|
|
226
|
-
ComPtr<ICoreWebView2> webview;
|
|
227
|
-
static std::map<HWND, Impl *> embeddedWebviewByParent;
|
|
228
|
-
|
|
229
|
-
static LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
|
230
|
-
Impl *impl = nullptr;
|
|
231
|
-
if (msg == WM_NCCREATE) {
|
|
232
|
-
auto cs = (LPCREATESTRUCT)lp;
|
|
233
|
-
impl = (Impl *)cs->lpCreateParams;
|
|
234
|
-
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)impl);
|
|
235
|
-
} else {
|
|
236
|
-
impl = (Impl *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (impl) {
|
|
240
|
-
switch (msg) {
|
|
241
|
-
case WM_MOVE: {
|
|
242
|
-
int x = (int)(short)LOWORD(lp);
|
|
243
|
-
int y = (int)(short)HIWORD(lp);
|
|
244
|
-
impl->state.x = x;
|
|
245
|
-
impl->state.y = y;
|
|
246
|
-
for (auto &cb : impl->moveCallbacks)
|
|
247
|
-
cb(x, y);
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
case WM_SIZE: {
|
|
251
|
-
int width = LOWORD(lp);
|
|
252
|
-
int height = HIWORD(lp);
|
|
253
|
-
impl->state.width = width;
|
|
254
|
-
impl->state.height = height;
|
|
255
|
-
for (auto &cb : impl->resizeCallbacks)
|
|
256
|
-
cb(width, height);
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
case WM_CLOSE:
|
|
260
|
-
for (auto &cb : impl->closeCallbacks)
|
|
261
|
-
cb();
|
|
262
|
-
break;
|
|
263
|
-
case WM_DROPFILES: {
|
|
264
|
-
HDROP hDrop = reinterpret_cast<HDROP>(wp);
|
|
265
|
-
Impl *targetImpl = impl;
|
|
266
|
-
if (!targetImpl->webview) {
|
|
267
|
-
auto it = embeddedWebviewByParent.find(hwnd);
|
|
268
|
-
if (it != embeddedWebviewByParent.end()) {
|
|
269
|
-
targetImpl = it->second;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (!targetImpl || !targetImpl->config.fileDrop ||
|
|
274
|
-
!targetImpl->webview) {
|
|
275
|
-
DragFinish(hDrop);
|
|
276
|
-
return 0;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
UINT fileCount = DragQueryFileW(hDrop, 0xFFFFFFFF, nullptr, 0);
|
|
280
|
-
std::string filesJson = "[";
|
|
281
|
-
|
|
282
|
-
for (UINT i = 0; i < fileCount; ++i) {
|
|
283
|
-
UINT pathLen = DragQueryFileW(hDrop, i, nullptr, 0);
|
|
284
|
-
std::wstring wpath(pathLen + 1, L'\0');
|
|
285
|
-
DragQueryFileW(hDrop, i, &wpath[0], pathLen + 1);
|
|
286
|
-
wpath.resize(pathLen);
|
|
287
|
-
|
|
288
|
-
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wpath.c_str(), -1,
|
|
289
|
-
nullptr, 0, nullptr, nullptr);
|
|
290
|
-
std::string path;
|
|
291
|
-
if (utf8Len > 0) {
|
|
292
|
-
path.resize(static_cast<size_t>(utf8Len - 1));
|
|
293
|
-
WideCharToMultiByte(CP_UTF8, 0, wpath.c_str(), -1, &path[0],
|
|
294
|
-
utf8Len, nullptr, nullptr);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
std::string name = path;
|
|
298
|
-
size_t lastSlash = path.find_last_of("\\/");
|
|
299
|
-
if (lastSlash != std::string::npos) {
|
|
300
|
-
name = path.substr(lastSlash + 1);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
unsigned long long sizeBytes = 0;
|
|
304
|
-
WIN32_FILE_ATTRIBUTE_DATA fileAttr;
|
|
305
|
-
if (GetFileAttributesExW(wpath.c_str(), GetFileExInfoStandard,
|
|
306
|
-
&fileAttr)) {
|
|
307
|
-
ULARGE_INTEGER size;
|
|
308
|
-
size.HighPart = fileAttr.nFileSizeHigh;
|
|
309
|
-
size.LowPart = fileAttr.nFileSizeLow;
|
|
310
|
-
sizeBytes = size.QuadPart;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (i > 0)
|
|
314
|
-
filesJson += ",";
|
|
315
|
-
filesJson += "{\"path\":\"" + jsonEscape(path) + "\",\"name\":\"" +
|
|
316
|
-
jsonEscape(name) + "\",\"type\":\"" +
|
|
317
|
-
jsonEscape(mimeTypeFromPath(path)) +
|
|
318
|
-
"\",\"size\":" + std::to_string(sizeBytes) + "}";
|
|
319
|
-
}
|
|
320
|
-
filesJson += "]";
|
|
321
|
-
|
|
322
|
-
// Must query drop point BEFORE DragFinish invalidates hDrop
|
|
323
|
-
POINT dropPoint = {0, 0};
|
|
324
|
-
DragQueryPoint(hDrop, &dropPoint);
|
|
325
|
-
|
|
326
|
-
DragFinish(hDrop);
|
|
327
|
-
|
|
328
|
-
// Always fire the C++ callback regardless of zone
|
|
329
|
-
if (targetImpl->fileDropCallback) {
|
|
330
|
-
targetImpl->fileDropCallback(filesJson);
|
|
331
|
-
}
|
|
332
229
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
UINT dpi = GetDpiForWindow(hwnd);
|
|
339
|
-
if (dpi > 0) dpiScale = static_cast<double>(dpi) / 96.0;
|
|
230
|
+
std::vector<MoveCallback> moveCallbacks;
|
|
231
|
+
std::vector<ResizeCallback> resizeCallbacks;
|
|
232
|
+
std::vector<CloseCallback> closeCallbacks;
|
|
233
|
+
std::vector<FocusCallback> focusCallbacks;
|
|
234
|
+
std::vector<StateCallback> stateCallbacks;
|
|
340
235
|
|
|
341
|
-
|
|
342
|
-
|
|
236
|
+
#ifdef _WIN32
|
|
237
|
+
HWND hwnd = nullptr;
|
|
238
|
+
WNDPROC originalProc = nullptr;
|
|
239
|
+
ComPtr<ICoreWebView2Controller> controller;
|
|
240
|
+
ComPtr<ICoreWebView2> webview;
|
|
241
|
+
static std::map<HWND, Impl *> embeddedWebviewByParent;
|
|
343
242
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
// Global event — always fires
|
|
354
|
-
"window.dispatchEvent(new "
|
|
355
|
-
"CustomEvent('plusui:fileDrop.filesDropped',"
|
|
356
|
-
" { detail: { files: files } }));"
|
|
357
|
-
// Zone-specific delivery via DPI-corrected hit test
|
|
358
|
-
"var el=document.elementFromPoint(" +
|
|
359
|
-
std::to_string(dpx) + "," + std::to_string(dpy) +
|
|
360
|
-
");"
|
|
361
|
-
"var zone=el&&el.closest?el.closest('[data-dropzone]'):null;"
|
|
362
|
-
"var zoneName=zone?zone.getAttribute('data-dropzone'):null;"
|
|
363
|
-
"if(zoneName&&window.__plusui_fileDrop__){"
|
|
364
|
-
"window.__plusui_fileDrop__(zoneName,files);"
|
|
365
|
-
"}"
|
|
366
|
-
// If no zone matched deliver to all registered zones so a
|
|
367
|
-
// single-zone app always works regardless of hit-test accuracy.
|
|
368
|
-
"if(!zoneName&&window.__plusui_fileDrop_default__){"
|
|
369
|
-
"window.__plusui_fileDrop_default__(files);"
|
|
370
|
-
"}"
|
|
371
|
-
"})();";
|
|
243
|
+
static LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
|
244
|
+
Impl *impl = nullptr;
|
|
245
|
+
if (msg == WM_NCCREATE) {
|
|
246
|
+
auto cs = (LPCREATESTRUCT)lp;
|
|
247
|
+
impl = (Impl *)cs->lpCreateParams;
|
|
248
|
+
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)impl);
|
|
249
|
+
} else {
|
|
250
|
+
impl = (Impl *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
251
|
+
}
|
|
372
252
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
253
|
+
if (impl) {
|
|
254
|
+
switch (msg) {
|
|
255
|
+
case WM_MOVE: {
|
|
256
|
+
int x = (int)(short)LOWORD(lp);
|
|
257
|
+
int y = (int)(short)HIWORD(lp);
|
|
258
|
+
impl->state.x = x;
|
|
259
|
+
impl->state.y = y;
|
|
260
|
+
for (auto &cb : impl->moveCallbacks)
|
|
261
|
+
cb(x, y);
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
case WM_SIZE: {
|
|
265
|
+
int width = LOWORD(lp);
|
|
266
|
+
int height = HIWORD(lp);
|
|
267
|
+
impl->state.width = width;
|
|
268
|
+
impl->state.height = height;
|
|
269
|
+
for (auto &cb : impl->resizeCallbacks)
|
|
270
|
+
cb(width, height);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case WM_CLOSE:
|
|
274
|
+
for (auto &cb : impl->closeCallbacks)
|
|
275
|
+
cb();
|
|
276
|
+
break;
|
|
378
277
|
case WM_HOTKEY: {
|
|
379
278
|
int hotKeyId = (int)wp;
|
|
380
279
|
auto it = impl->hotkeys.find(hotKeyId);
|
|
@@ -439,683 +338,719 @@ struct Window::Impl {
|
|
|
439
338
|
}
|
|
440
339
|
return DefWindowProc(hwnd, msg, wp, lp);
|
|
441
340
|
}
|
|
442
|
-
#elif defined(__APPLE__)
|
|
443
|
-
WKWebView *wkWebView = nullptr;
|
|
444
|
-
#else
|
|
445
|
-
WebKitWebView *gtkWebView = nullptr;
|
|
446
|
-
#endif
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
#
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
wc
|
|
470
|
-
wc.
|
|
471
|
-
wc.
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
if (!config.
|
|
484
|
-
style &= ~
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
if (config.
|
|
497
|
-
exStyle
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
if (
|
|
564
|
-
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
w.pImpl->trayManager
|
|
582
|
-
#
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
pImpl->
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
pImpl->
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
pImpl->config.
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
if (
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
pImpl->
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
#
|
|
756
|
-
if (pImpl->nativeWindow) {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
pImpl->state.
|
|
775
|
-
pImpl->state.
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
bool wasMaximized = pImpl->state.isMaximized;
|
|
800
|
-
pImpl->state.
|
|
801
|
-
pImpl->state.
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
bool
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
pImpl->
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
void Window::
|
|
856
|
-
bool
|
|
857
|
-
pImpl->state.isVisible =
|
|
858
|
-
pImpl->state.isHidden =
|
|
859
|
-
|
|
860
|
-
#ifdef _WIN32
|
|
861
|
-
if (pImpl->hwnd)
|
|
862
|
-
ShowWindow(pImpl->hwnd,
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
if (pImpl->
|
|
870
|
-
|
|
871
|
-
}
|
|
872
|
-
#
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
#
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
#
|
|
906
|
-
if (pImpl->
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
#else
|
|
934
|
-
if (pImpl->nativeWindow) {
|
|
935
|
-
|
|
936
|
-
}
|
|
937
|
-
#endif
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
}
|
|
954
|
-
#else
|
|
955
|
-
if (pImpl->nativeWindow) {
|
|
956
|
-
|
|
957
|
-
}
|
|
958
|
-
#endif
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
void Window::
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
pImpl->
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
341
|
+
#elif defined(__APPLE__)
|
|
342
|
+
WKWebView *wkWebView = nullptr;
|
|
343
|
+
#else
|
|
344
|
+
WebKitWebView *gtkWebView = nullptr;
|
|
345
|
+
#endif
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
#ifdef _WIN32
|
|
349
|
+
|
|
350
|
+
#endif // _WIN32
|
|
351
|
+
|
|
352
|
+
Window::Window() : pImpl(std::shared_ptr<Impl>(new Impl())) {}
|
|
353
|
+
|
|
354
|
+
#ifdef _WIN32
|
|
355
|
+
std::map<HWND, Window::Impl *> Window::Impl::embeddedWebviewByParent;
|
|
356
|
+
#endif
|
|
357
|
+
|
|
358
|
+
Window::~Window() = default;
|
|
359
|
+
|
|
360
|
+
Window::Window(Window &&other) noexcept = default;
|
|
361
|
+
Window &Window::operator=(Window &&other) noexcept = default;
|
|
362
|
+
|
|
363
|
+
Window Window::create(const WindowConfig &config) {
|
|
364
|
+
Window w;
|
|
365
|
+
w.pImpl->config = config;
|
|
366
|
+
|
|
367
|
+
#ifdef _WIN32
|
|
368
|
+
WNDCLASSEXW wc = {};
|
|
369
|
+
wc.cbSize = sizeof(WNDCLASSEXW);
|
|
370
|
+
wc.lpfnWndProc = Impl::wndProc;
|
|
371
|
+
wc.hInstance = GetModuleHandle(nullptr);
|
|
372
|
+
wc.lpszClassName = L"PLUSUI_WINDOW";
|
|
373
|
+
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
|
374
|
+
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
|
375
|
+
RegisterClassExW(&wc);
|
|
376
|
+
|
|
377
|
+
DWORD style = 0;
|
|
378
|
+
DWORD exStyle = 0;
|
|
379
|
+
|
|
380
|
+
if (config.decorations) {
|
|
381
|
+
style = WS_OVERLAPPEDWINDOW;
|
|
382
|
+
if (!config.resizable)
|
|
383
|
+
style &= ~WS_THICKFRAME;
|
|
384
|
+
if (!config.minimizable)
|
|
385
|
+
style &= ~WS_MINIMIZEBOX;
|
|
386
|
+
if (!config.closable)
|
|
387
|
+
style &= ~WS_SYSMENU;
|
|
388
|
+
} else {
|
|
389
|
+
// Frameless window
|
|
390
|
+
style = WS_POPUP;
|
|
391
|
+
if (config.resizable)
|
|
392
|
+
style |= WS_THICKFRAME;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (config.transparent) {
|
|
396
|
+
exStyle = WS_EX_LAYERED;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (config.skipTaskbar) {
|
|
400
|
+
exStyle |= WS_EX_TOOLWINDOW;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
std::wstring wideTitle(config.title.begin(), config.title.end());
|
|
404
|
+
w.pImpl->hwnd = CreateWindowExW(
|
|
405
|
+
exStyle, L"PLUSUI_WINDOW", wideTitle.c_str(), style,
|
|
406
|
+
config.x >= 0 ? config.x : CW_USEDEFAULT,
|
|
407
|
+
config.y >= 0 ? config.y : CW_USEDEFAULT, config.width, config.height,
|
|
408
|
+
nullptr, nullptr, GetModuleHandle(nullptr), w.pImpl.get());
|
|
409
|
+
|
|
410
|
+
w.pImpl->nativeWindow = (void *)w.pImpl->hwnd;
|
|
411
|
+
w.pImpl->state.width = config.width;
|
|
412
|
+
w.pImpl->state.height = config.height;
|
|
413
|
+
|
|
414
|
+
if (config.center) {
|
|
415
|
+
RECT screen;
|
|
416
|
+
SystemParametersInfo(SPI_GETWORKAREA, 0, &screen, 0);
|
|
417
|
+
int x = (screen.right - config.width) / 2;
|
|
418
|
+
int y = (screen.bottom - config.height) / 2;
|
|
419
|
+
SetWindowPos(w.pImpl->hwnd, nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (config.alwaysOnTop) {
|
|
423
|
+
SetWindowPos(w.pImpl->hwnd, HWND_TOPMOST, 0, 0, 0, 0,
|
|
424
|
+
SWP_NOMOVE | SWP_NOSIZE);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
#elif defined(__APPLE__)
|
|
428
|
+
NSWindow *nswin = [[NSWindow alloc]
|
|
429
|
+
initWithContentRect:NSMakeRect(config.x >= 0 ? config.x : 100,
|
|
430
|
+
config.y >= 0 ? config.y : 100,
|
|
431
|
+
config.width, config.height)
|
|
432
|
+
styleMask:(config.resizable ? NSWindowStyleMaskResizable : 0) |
|
|
433
|
+
NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
|
434
|
+
(config.minimizable ? NSWindowStyleMaskMiniaturizable
|
|
435
|
+
: 0)backing:NSBackingStoreBuffered
|
|
436
|
+
defer:NO];
|
|
437
|
+
|
|
438
|
+
[nswin setTitle:[NSString stringWithUTF8String:config.title.c_str()]];
|
|
439
|
+
[nswin setReleasedWhenClosed:NO];
|
|
440
|
+
|
|
441
|
+
if (config.center) {
|
|
442
|
+
[nswin center];
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (config.alwaysOnTop) {
|
|
446
|
+
[nswin setLevel:NSFloatingWindowLevel];
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
w.pImpl->nativeWindow = (__bridge void *)nswin;
|
|
450
|
+
|
|
451
|
+
#else
|
|
452
|
+
// Force the X11 GDK backend so window positioning and XGrabKey shortcuts work.
|
|
453
|
+
// On a Wayland session this uses XWayland transparently.
|
|
454
|
+
// Must be set before gtk_init and must override any compositor-set GDK_BACKEND.
|
|
455
|
+
setenv("GDK_BACKEND", "x11", 1);
|
|
456
|
+
gtk_init_check(0, nullptr);
|
|
457
|
+
|
|
458
|
+
GtkWindow *gtkwin = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
|
|
459
|
+
gtk_window_set_title(gtkwin, config.title.c_str());
|
|
460
|
+
gtk_window_set_default_size(gtkwin, config.width, config.height);
|
|
461
|
+
|
|
462
|
+
if (config.x >= 0 && config.y >= 0) {
|
|
463
|
+
gtk_window_move(gtkwin, config.x, config.y);
|
|
464
|
+
} else if (config.center) {
|
|
465
|
+
gtk_window_set_position(gtkwin, GTK_WIN_POS_CENTER);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (!config.resizable) {
|
|
469
|
+
gtk_window_set_resizable(gtkwin, FALSE);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (config.alwaysOnTop) {
|
|
473
|
+
gtk_window_set_keep_above(gtkwin, TRUE);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
w.pImpl->nativeWindow = (void *)gtkwin;
|
|
477
|
+
#endif
|
|
478
|
+
|
|
479
|
+
// Initialize tray manager for native windows
|
|
480
|
+
w.pImpl->trayManager = std::make_unique<TrayManager>();
|
|
481
|
+
#ifdef _WIN32
|
|
482
|
+
if (w.pImpl->hwnd) {
|
|
483
|
+
w.pImpl->trayManager->setWindowHandle((void *)w.pImpl->hwnd);
|
|
484
|
+
}
|
|
485
|
+
#elif defined(__APPLE__)
|
|
486
|
+
w.pImpl->trayManager->setWindowHandle(w.pImpl->nativeWindow);
|
|
487
|
+
#else
|
|
488
|
+
w.pImpl->trayManager->setWindowHandle(w.pImpl->nativeWindow);
|
|
489
|
+
#endif
|
|
490
|
+
|
|
491
|
+
return w;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
void Window::setTitle(const std::string &title) {
|
|
495
|
+
pImpl->config.title = title;
|
|
496
|
+
#ifdef _WIN32
|
|
497
|
+
if (pImpl->hwnd) {
|
|
498
|
+
std::wstring wideTitle(title.begin(), title.end());
|
|
499
|
+
SetWindowTextW(pImpl->hwnd, wideTitle.c_str());
|
|
500
|
+
}
|
|
501
|
+
#elif defined(__APPLE__)
|
|
502
|
+
if (pImpl->nativeWindow) {
|
|
503
|
+
NSWindow *nswin = (__bridge NSWindow *)pImpl->nativeWindow;
|
|
504
|
+
[nswin setTitle:[NSString stringWithUTF8String:title.c_str()]];
|
|
505
|
+
}
|
|
506
|
+
#else
|
|
507
|
+
if (pImpl->nativeWindow) {
|
|
508
|
+
gtk_window_set_title(GTK_WINDOW(pImpl->nativeWindow), title.c_str());
|
|
509
|
+
}
|
|
510
|
+
#endif
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
std::string Window::getTitle() const {
|
|
514
|
+
if (!pImpl->currentTitle.empty())
|
|
515
|
+
return pImpl->currentTitle;
|
|
516
|
+
return pImpl->config.title;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
void Window::setSize(int width, int height) {
|
|
520
|
+
pImpl->config.width = width;
|
|
521
|
+
pImpl->config.height = height;
|
|
522
|
+
#ifdef _WIN32
|
|
523
|
+
if (pImpl->hwnd)
|
|
524
|
+
SetWindowPos(pImpl->hwnd, nullptr, 0, 0, width, height,
|
|
525
|
+
SWP_NOZORDER | SWP_NOMOVE);
|
|
526
|
+
#elif defined(__APPLE__)
|
|
527
|
+
if (pImpl->nativeWindow) {
|
|
528
|
+
NSWindow *nswin = (__bridge NSWindow *)pImpl->nativeWindow;
|
|
529
|
+
[nswin setContentSize:NSMakeSize(width, height)];
|
|
530
|
+
}
|
|
531
|
+
#else
|
|
532
|
+
if (pImpl->nativeWindow) {
|
|
533
|
+
gtk_window_resize(GTK_WINDOW(pImpl->nativeWindow), width, height);
|
|
534
|
+
}
|
|
535
|
+
#endif
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
void Window::getSize(int &width, int &height) const {
|
|
539
|
+
width = pImpl->state.width;
|
|
540
|
+
height = pImpl->state.height;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
void Window::setMinSize(int minWidth, int minHeight) {
|
|
544
|
+
pImpl->config.minWidth = minWidth;
|
|
545
|
+
pImpl->config.minHeight = minHeight;
|
|
546
|
+
#ifdef _WIN32
|
|
547
|
+
if (pImpl->hwnd) {
|
|
548
|
+
SetWindowLongPtr(pImpl->hwnd, GWL_STYLE,
|
|
549
|
+
GetWindowLong(pImpl->hwnd, GWL_STYLE) | WS_THICKFRAME);
|
|
550
|
+
}
|
|
551
|
+
#endif
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
void Window::setMaxSize(int maxWidth, int maxHeight) {
|
|
555
|
+
pImpl->config.maxWidth = maxWidth;
|
|
556
|
+
pImpl->config.maxHeight = maxHeight;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
void Window::setPosition(int x, int y) {
|
|
560
|
+
pImpl->config.x = x;
|
|
561
|
+
pImpl->config.y = y;
|
|
562
|
+
#ifdef _WIN32
|
|
563
|
+
if (pImpl->hwnd)
|
|
564
|
+
SetWindowPos(pImpl->hwnd, nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
|
|
565
|
+
#elif defined(__APPLE__)
|
|
566
|
+
if (pImpl->nativeWindow) {
|
|
567
|
+
NSWindow *nswin = (__bridge NSWindow *)pImpl->nativeWindow;
|
|
568
|
+
[nswin setFrameOrigin:NSMakePoint(x, y)];
|
|
569
|
+
}
|
|
570
|
+
#else
|
|
571
|
+
if (pImpl->nativeWindow) {
|
|
572
|
+
gtk_window_move(GTK_WINDOW(pImpl->nativeWindow), x, y);
|
|
573
|
+
}
|
|
574
|
+
#endif
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
void Window::getPosition(int &x, int &y) const {
|
|
578
|
+
x = pImpl->state.x;
|
|
579
|
+
y = pImpl->state.y;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
void Window::center() {
|
|
583
|
+
#ifdef _WIN32
|
|
584
|
+
if (pImpl->hwnd) {
|
|
585
|
+
RECT rc, screen;
|
|
586
|
+
GetWindowRect(pImpl->hwnd, &rc);
|
|
587
|
+
SystemParametersInfo(SPI_GETWORKAREA, 0, &screen, 0);
|
|
588
|
+
int x = (screen.right - (rc.right - rc.left)) / 2;
|
|
589
|
+
int y = (screen.bottom - (rc.bottom - rc.top)) / 2;
|
|
590
|
+
SetWindowPos(pImpl->hwnd, nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
|
|
591
|
+
}
|
|
592
|
+
#elif defined(__APPLE__)
|
|
593
|
+
if (pImpl->nativeWindow) {
|
|
594
|
+
NSWindow *nswin = (__bridge NSWindow *)pImpl->nativeWindow;
|
|
595
|
+
[nswin center];
|
|
596
|
+
}
|
|
597
|
+
#else
|
|
598
|
+
// gtk_window_set_position(GTK_WIN_POS_CENTER) is a map-time hint only —
|
|
599
|
+
// it does nothing on an already-visible window. Use GDK geometry to
|
|
600
|
+
// calculate the centre and move the window explicitly, matching Windows.
|
|
601
|
+
{
|
|
602
|
+
GtkWindow *gtkwin = nullptr;
|
|
603
|
+
if (pImpl->nativeWindow)
|
|
604
|
+
gtkwin = GTK_WINDOW(pImpl->nativeWindow);
|
|
605
|
+
else if (pImpl->window && pImpl->window->pImpl->nativeWindow)
|
|
606
|
+
gtkwin = GTK_WINDOW(pImpl->window->pImpl->nativeWindow);
|
|
607
|
+
if (gtkwin) {
|
|
608
|
+
GdkDisplay *display = gdk_display_get_default();
|
|
609
|
+
GdkMonitor *monitor = gdk_display_get_primary_monitor(display);
|
|
610
|
+
if (!monitor) monitor = gdk_display_get_monitor(display, 0);
|
|
611
|
+
if (monitor) {
|
|
612
|
+
GdkRectangle geo;
|
|
613
|
+
gdk_monitor_get_geometry(monitor, &geo);
|
|
614
|
+
int w = 0, h = 0;
|
|
615
|
+
gtk_window_get_size(gtkwin, &w, &h);
|
|
616
|
+
gtk_window_move(gtkwin,
|
|
617
|
+
geo.x + (geo.width - w) / 2,
|
|
618
|
+
geo.y + (geo.height - h) / 2);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
#endif
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
void Window::setFullscreen(bool enabled) {
|
|
626
|
+
bool wasFullscreen = pImpl->state.isFullscreen;
|
|
627
|
+
pImpl->config.fullscreen = enabled;
|
|
628
|
+
pImpl->state.isFullscreen = enabled;
|
|
629
|
+
pImpl->state.isHidden = false;
|
|
630
|
+
|
|
631
|
+
#ifdef _WIN32
|
|
632
|
+
if (pImpl->hwnd) {
|
|
633
|
+
if (enabled) {
|
|
634
|
+
SetWindowLongPtr(pImpl->hwnd, GWL_STYLE,
|
|
635
|
+
GetWindowLong(pImpl->hwnd, GWL_STYLE) &
|
|
636
|
+
~WS_OVERLAPPEDWINDOW);
|
|
637
|
+
SetWindowPos(pImpl->hwnd, HWND_TOP, 0, 0, GetSystemMetrics(SM_CXSCREEN),
|
|
638
|
+
GetSystemMetrics(SM_CYSCREEN), SWP_SHOWWINDOW);
|
|
639
|
+
} else {
|
|
640
|
+
SetWindowLongPtr(pImpl->hwnd, GWL_STYLE,
|
|
641
|
+
GetWindowLong(pImpl->hwnd, GWL_STYLE) |
|
|
642
|
+
WS_OVERLAPPEDWINDOW);
|
|
643
|
+
SetWindowPos(pImpl->hwnd, nullptr, 0, 0, 0, 0,
|
|
644
|
+
SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
#elif defined(__APPLE__)
|
|
648
|
+
if (pImpl->nativeWindow) {
|
|
649
|
+
NSWindow *nswin = (__bridge NSWindow *)pImpl->nativeWindow;
|
|
650
|
+
if (enabled) {
|
|
651
|
+
[nswin toggleFullScreen:nil];
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
#else
|
|
655
|
+
if (pImpl->nativeWindow) {
|
|
656
|
+
if (enabled) {
|
|
657
|
+
gtk_window_fullscreen(GTK_WINDOW(pImpl->nativeWindow));
|
|
658
|
+
} else {
|
|
659
|
+
gtk_window_unfullscreen(GTK_WINDOW(pImpl->nativeWindow));
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
#endif
|
|
663
|
+
|
|
664
|
+
if (wasFullscreen != enabled) {
|
|
665
|
+
for (auto &cb : pImpl->stateCallbacks)
|
|
666
|
+
cb(pImpl->state);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
bool Window::isFullscreen() const { return pImpl->state.isFullscreen; }
|
|
671
|
+
|
|
672
|
+
void Window::minimize() {
|
|
673
|
+
bool wasMinimized = pImpl->state.isMinimized;
|
|
674
|
+
pImpl->state.isMinimized = true;
|
|
675
|
+
pImpl->state.isHidden = false;
|
|
676
|
+
|
|
677
|
+
#ifdef _WIN32
|
|
678
|
+
if (pImpl->hwnd)
|
|
679
|
+
ShowWindow(pImpl->hwnd, SW_MINIMIZE);
|
|
680
|
+
#elif defined(__APPLE__)
|
|
681
|
+
if (pImpl->nativeWindow) {
|
|
682
|
+
NSWindow *nswin = (__bridge NSWindow *)pImpl->nativeWindow;
|
|
683
|
+
[nswin miniaturize:nil];
|
|
684
|
+
}
|
|
685
|
+
#else
|
|
686
|
+
if (pImpl->nativeWindow) {
|
|
687
|
+
gtk_window_iconify(GTK_WINDOW(pImpl->nativeWindow));
|
|
688
|
+
}
|
|
689
|
+
#endif
|
|
690
|
+
|
|
691
|
+
if (!wasMinimized) {
|
|
692
|
+
for (auto &cb : pImpl->stateCallbacks)
|
|
693
|
+
cb(pImpl->state);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
void Window::maximize() {
|
|
698
|
+
bool wasMaximized = pImpl->state.isMaximized;
|
|
699
|
+
pImpl->state.isMaximized = true;
|
|
700
|
+
pImpl->state.isHidden = false;
|
|
701
|
+
|
|
702
|
+
#ifdef _WIN32
|
|
703
|
+
if (pImpl->hwnd)
|
|
704
|
+
ShowWindow(pImpl->hwnd, SW_MAXIMIZE);
|
|
705
|
+
#elif defined(__APPLE__)
|
|
706
|
+
if (pImpl->nativeWindow) {
|
|
707
|
+
NSWindow *nswin = (__bridge NSWindow *)pImpl->nativeWindow;
|
|
708
|
+
[nswin zoom:nil];
|
|
709
|
+
}
|
|
710
|
+
#else
|
|
711
|
+
if (pImpl->nativeWindow) {
|
|
712
|
+
gtk_window_maximize(GTK_WINDOW(pImpl->nativeWindow));
|
|
713
|
+
}
|
|
714
|
+
#endif
|
|
715
|
+
|
|
716
|
+
if (!wasMaximized) {
|
|
717
|
+
for (auto &cb : pImpl->stateCallbacks)
|
|
718
|
+
cb(pImpl->state);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
void Window::restore() {
|
|
723
|
+
bool wasMinimized = pImpl->state.isMinimized;
|
|
724
|
+
bool wasMaximized = pImpl->state.isMaximized;
|
|
725
|
+
pImpl->state.isMinimized = false;
|
|
726
|
+
pImpl->state.isMaximized = false;
|
|
727
|
+
pImpl->state.isHidden = false;
|
|
728
|
+
|
|
729
|
+
#ifdef _WIN32
|
|
730
|
+
if (pImpl->hwnd)
|
|
731
|
+
ShowWindow(pImpl->hwnd, SW_RESTORE);
|
|
732
|
+
#elif defined(__APPLE__)
|
|
733
|
+
if (pImpl->nativeWindow) {
|
|
734
|
+
NSWindow *nswin = (__bridge NSWindow *)pImpl->nativeWindow;
|
|
735
|
+
[nswin deminiaturize:nil];
|
|
736
|
+
}
|
|
737
|
+
#else
|
|
738
|
+
if (pImpl->nativeWindow) {
|
|
739
|
+
gtk_window_unmaximize(GTK_WINDOW(pImpl->nativeWindow));
|
|
740
|
+
gtk_window_deiconify(GTK_WINDOW(pImpl->nativeWindow));
|
|
741
|
+
}
|
|
742
|
+
#endif
|
|
743
|
+
|
|
744
|
+
if (wasMinimized || wasMaximized) {
|
|
745
|
+
for (auto &cb : pImpl->stateCallbacks)
|
|
746
|
+
cb(pImpl->state);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
bool Window::isMaximized() const { return pImpl->state.isMaximized; }
|
|
751
|
+
|
|
752
|
+
bool Window::isMinimized() const { return pImpl->state.isMinimized; }
|
|
753
|
+
|
|
754
|
+
void Window::show() {
|
|
755
|
+
bool wasHidden = pImpl->state.isHidden;
|
|
756
|
+
pImpl->state.isVisible = true;
|
|
757
|
+
pImpl->state.isHidden = false;
|
|
758
|
+
|
|
759
|
+
#ifdef _WIN32
|
|
760
|
+
if (pImpl->hwnd)
|
|
761
|
+
ShowWindow(pImpl->hwnd, SW_SHOW);
|
|
762
|
+
else if (pImpl->window)
|
|
763
|
+
pImpl->window->show();
|
|
764
|
+
#elif defined(__APPLE__)
|
|
765
|
+
if (pImpl->nativeWindow) {
|
|
766
|
+
NSWindow *nswin = (__bridge NSWindow *)pImpl->nativeWindow;
|
|
767
|
+
[nswin orderFront:nil];
|
|
768
|
+
} else if (pImpl->window) {
|
|
769
|
+
pImpl->window->show();
|
|
770
|
+
}
|
|
771
|
+
#else
|
|
772
|
+
if (pImpl->nativeWindow) {
|
|
773
|
+
// show_all reveals the window and all child widgets (e.g. the webview)
|
|
774
|
+
gtk_widget_show_all(GTK_WIDGET(pImpl->nativeWindow));
|
|
775
|
+
} else if (pImpl->window) {
|
|
776
|
+
pImpl->window->show();
|
|
777
|
+
}
|
|
778
|
+
#endif
|
|
779
|
+
|
|
780
|
+
// Fire state change event if hidden state changed
|
|
781
|
+
if (wasHidden) {
|
|
782
|
+
for (auto &cb : pImpl->stateCallbacks)
|
|
783
|
+
cb(pImpl->state);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
void Window::hide() {
|
|
788
|
+
bool wasVisible = pImpl->state.isVisible;
|
|
789
|
+
pImpl->state.isVisible = false;
|
|
790
|
+
pImpl->state.isHidden = true;
|
|
791
|
+
|
|
792
|
+
#ifdef _WIN32
|
|
793
|
+
if (pImpl->hwnd)
|
|
794
|
+
ShowWindow(pImpl->hwnd, SW_HIDE);
|
|
795
|
+
else if (pImpl->window)
|
|
796
|
+
pImpl->window->hide();
|
|
797
|
+
#elif defined(__APPLE__)
|
|
798
|
+
if (pImpl->nativeWindow) {
|
|
799
|
+
NSWindow *nswin = (__bridge NSWindow *)pImpl->nativeWindow;
|
|
800
|
+
[nswin orderOut:nil];
|
|
801
|
+
} else if (pImpl->window) {
|
|
802
|
+
pImpl->window->hide();
|
|
803
|
+
}
|
|
804
|
+
#else
|
|
805
|
+
if (pImpl->nativeWindow) {
|
|
806
|
+
gtk_widget_hide(GTK_WIDGET(pImpl->nativeWindow));
|
|
807
|
+
} else if (pImpl->window) {
|
|
808
|
+
pImpl->window->hide();
|
|
809
|
+
}
|
|
810
|
+
#endif
|
|
811
|
+
|
|
812
|
+
// Fire state change event if visibility changed
|
|
813
|
+
if (wasVisible) {
|
|
814
|
+
for (auto &cb : pImpl->stateCallbacks)
|
|
815
|
+
cb(pImpl->state);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
bool Window::isVisible() const { return pImpl->state.isVisible; }
|
|
820
|
+
|
|
821
|
+
bool Window::isHidden() const { return pImpl->state.isHidden; }
|
|
822
|
+
|
|
823
|
+
void Window::focus() {
|
|
824
|
+
#ifdef _WIN32
|
|
825
|
+
if (pImpl->hwnd)
|
|
826
|
+
SetFocus(pImpl->hwnd);
|
|
827
|
+
#elif defined(__APPLE__)
|
|
828
|
+
if (pImpl->nativeWindow) {
|
|
829
|
+
NSWindow *nswin = (__bridge NSWindow *)pImpl->nativeWindow;
|
|
830
|
+
[nswin makeKeyAndOrderFront:nil];
|
|
831
|
+
}
|
|
832
|
+
#else
|
|
833
|
+
if (pImpl->nativeWindow) {
|
|
834
|
+
gtk_window_present(GTK_WINDOW(pImpl->nativeWindow));
|
|
835
|
+
}
|
|
836
|
+
#endif
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
bool Window::isFocused() const { return pImpl->state.isFocused; }
|
|
840
|
+
|
|
841
|
+
void Window::setAlwaysOnTop(bool enabled) {
|
|
842
|
+
pImpl->config.alwaysOnTop = enabled;
|
|
843
|
+
#ifdef _WIN32
|
|
844
|
+
if (pImpl->hwnd) {
|
|
845
|
+
SetWindowPos(pImpl->hwnd, enabled ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0,
|
|
846
|
+
0, SWP_NOMOVE | SWP_NOSIZE);
|
|
847
|
+
}
|
|
848
|
+
#elif defined(__APPLE__)
|
|
849
|
+
if (pImpl->nativeWindow) {
|
|
850
|
+
NSWindow *nswin = (__bridge NSWindow *)pImpl->nativeWindow;
|
|
851
|
+
[nswin setLevel:enabled ? NSFloatingWindowLevel : NSNormalWindowLevel];
|
|
852
|
+
}
|
|
853
|
+
#else
|
|
854
|
+
if (pImpl->nativeWindow) {
|
|
855
|
+
gtk_window_set_keep_above(GTK_WINDOW(pImpl->nativeWindow), enabled);
|
|
856
|
+
}
|
|
857
|
+
#endif
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
void Window::setResizable(bool enabled) {
|
|
861
|
+
pImpl->config.resizable = enabled;
|
|
862
|
+
#ifdef _WIN32
|
|
863
|
+
if (pImpl->hwnd) {
|
|
864
|
+
SetWindowLongPtr(
|
|
865
|
+
pImpl->hwnd, GWL_STYLE,
|
|
866
|
+
enabled ? GetWindowLong(pImpl->hwnd, GWL_STYLE) | WS_THICKFRAME
|
|
867
|
+
: GetWindowLong(pImpl->hwnd, GWL_STYLE) & ~WS_THICKFRAME);
|
|
868
|
+
}
|
|
869
|
+
#elif defined(__APPLE__)
|
|
870
|
+
// Handled in create
|
|
871
|
+
#else
|
|
872
|
+
if (pImpl->nativeWindow) {
|
|
873
|
+
gtk_window_set_resizable(GTK_WINDOW(pImpl->nativeWindow), enabled);
|
|
874
|
+
}
|
|
875
|
+
#endif
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
void Window::setDecorations(bool enabled) {
|
|
879
|
+
#ifdef _WIN32
|
|
880
|
+
if (pImpl->hwnd) {
|
|
881
|
+
SetWindowLongPtr(pImpl->hwnd, GWL_STYLE,
|
|
882
|
+
enabled
|
|
883
|
+
? GetWindowLong(pImpl->hwnd, GWL_STYLE) | WS_CAPTION
|
|
884
|
+
: GetWindowLong(pImpl->hwnd, GWL_STYLE) & ~WS_CAPTION);
|
|
885
|
+
}
|
|
886
|
+
#elif defined(__APPLE__)
|
|
887
|
+
if (pImpl->nativeWindow) {
|
|
888
|
+
NSWindow *nswin = (__bridge NSWindow *)pImpl->nativeWindow;
|
|
889
|
+
[nswin setStyleMask:enabled ? [nswin styleMask] | NSWindowStyleMaskTitled
|
|
890
|
+
: [nswin styleMask] & ~NSWindowStyleMaskTitled];
|
|
891
|
+
}
|
|
892
|
+
#else
|
|
893
|
+
if (pImpl->nativeWindow) {
|
|
894
|
+
// GTK handles this differently
|
|
895
|
+
}
|
|
896
|
+
#endif
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
void Window::setSkipTaskbar(bool enabled) {
|
|
900
|
+
#ifdef _WIN32
|
|
901
|
+
if (pImpl->hwnd) {
|
|
902
|
+
SetWindowLongPtr(
|
|
903
|
+
pImpl->hwnd, GWL_EXSTYLE,
|
|
904
|
+
enabled ? GetWindowLong(pImpl->hwnd, GWL_EXSTYLE) | WS_EX_TOOLWINDOW
|
|
905
|
+
: GetWindowLong(pImpl->hwnd, GWL_EXSTYLE) & ~WS_EX_TOOLWINDOW);
|
|
906
|
+
}
|
|
907
|
+
#endif
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
void Window::setOpacity(double opacity) {
|
|
911
|
+
pImpl->config.opacity = opacity;
|
|
912
|
+
#ifdef _WIN32
|
|
913
|
+
if (pImpl->hwnd) {
|
|
914
|
+
BYTE alpha = static_cast<BYTE>(opacity * 255);
|
|
915
|
+
SetWindowLongPtr(pImpl->hwnd, GWL_EXSTYLE,
|
|
916
|
+
GetWindowLong(pImpl->hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
|
|
917
|
+
SetLayeredWindowAttributes(pImpl->hwnd, 0, alpha, LWA_ALPHA);
|
|
918
|
+
}
|
|
919
|
+
#endif
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
void Window::setIconFromMemory(const unsigned char *data, size_t size) {
|
|
923
|
+
#ifdef _WIN32
|
|
924
|
+
if (pImpl->hwnd) {
|
|
925
|
+
int width, height, channels;
|
|
926
|
+
unsigned char *pixels =
|
|
927
|
+
stbi_load_from_memory(data, (int)size, &width, &height, &channels, 4);
|
|
928
|
+
if (pixels) {
|
|
929
|
+
BITMAPINFO bmi = {};
|
|
930
|
+
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
931
|
+
bmi.bmiHeader.biWidth = width;
|
|
932
|
+
bmi.bmiHeader.biHeight = -height;
|
|
933
|
+
bmi.bmiHeader.biPlanes = 1;
|
|
934
|
+
bmi.bmiHeader.biBitCount = 32;
|
|
935
|
+
bmi.bmiHeader.biCompression = BI_RGB;
|
|
936
|
+
|
|
937
|
+
void *bits = nullptr;
|
|
938
|
+
HBITMAP hbm =
|
|
939
|
+
CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0);
|
|
940
|
+
if (hbm && bits) {
|
|
941
|
+
// Convert RGBA to BGRA
|
|
942
|
+
for (int i = 0; i < width * height; ++i) {
|
|
943
|
+
((uint8_t *)bits)[i * 4 + 0] = pixels[i * 4 + 2];
|
|
944
|
+
((uint8_t *)bits)[i * 4 + 1] = pixels[i * 4 + 1];
|
|
945
|
+
((uint8_t *)bits)[i * 4 + 2] = pixels[i * 4 + 0];
|
|
946
|
+
((uint8_t *)bits)[i * 4 + 3] = pixels[i * 4 + 3];
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
ICONINFO ii = {};
|
|
950
|
+
ii.fIcon = TRUE;
|
|
951
|
+
ii.hbmColor = hbm;
|
|
952
|
+
ii.hbmMask = hbm;
|
|
953
|
+
|
|
954
|
+
HICON hIcon = CreateIconIndirect(&ii);
|
|
955
|
+
if (hIcon) {
|
|
956
|
+
SendMessage(pImpl->hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
|
|
957
|
+
SendMessage(pImpl->hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
|
|
958
|
+
}
|
|
959
|
+
DeleteObject(hbm);
|
|
960
|
+
}
|
|
961
|
+
stbi_image_free(pixels);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
#endif
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
WindowState Window::getState() const { return pImpl->state; }
|
|
968
|
+
|
|
969
|
+
void Window::close() {
|
|
970
|
+
#ifdef _WIN32
|
|
971
|
+
if (pImpl->hwnd)
|
|
972
|
+
PostMessage(pImpl->hwnd, WM_CLOSE, 0, 0);
|
|
973
|
+
#elif defined(__APPLE__)
|
|
974
|
+
if (pImpl->nativeWindow) {
|
|
975
|
+
NSWindow *nswin = (__bridge NSWindow *)pImpl->nativeWindow;
|
|
976
|
+
[nswin close];
|
|
977
|
+
}
|
|
978
|
+
#else
|
|
979
|
+
if (pImpl->nativeWindow) {
|
|
980
|
+
gtk_widget_destroy(GTK_WIDGET(pImpl->nativeWindow));
|
|
981
|
+
}
|
|
982
|
+
#endif
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
void Window::onMove(MoveCallback callback) {
|
|
986
|
+
pImpl->moveCallbacks.push_back(callback);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
void Window::onResize(ResizeCallback callback) {
|
|
990
|
+
pImpl->resizeCallbacks.push_back(callback);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
void Window::onClose(CloseCallback callback) {
|
|
994
|
+
pImpl->closeCallbacks.push_back(callback);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
void Window::onFocus(FocusCallback callback) {
|
|
998
|
+
pImpl->focusCallbacks.push_back(callback);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
void Window::onStateChange(StateCallback callback) {
|
|
1002
|
+
pImpl->stateCallbacks.push_back(callback);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
void *Window::nativeHandle() const { return pImpl->nativeWindow; }
|
|
1006
|
+
void *Window::nativeWebView() const { return pImpl->nativeWebView; }
|
|
1007
|
+
|
|
1008
|
+
// WebView-specific implementations
|
|
1009
|
+
Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
1010
|
+
Window win;
|
|
1011
|
+
win.pImpl->config = config;
|
|
1012
|
+
|
|
1013
|
+
// Pure native file-drop mode: when native FileDrop is enabled,
|
|
1014
|
+
// fully disable browser/WebView drag-drop handling.
|
|
1015
|
+
if (win.pImpl->config.fileDrop) {
|
|
1016
|
+
win.pImpl->config.disableWebviewDragDrop = true;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
#ifdef _WIN32
|
|
1020
|
+
HWND hwnd = static_cast<HWND>(windowHandle);
|
|
1021
|
+
|
|
1022
|
+
// Create WebView2 environment and controller
|
|
1023
|
+
auto pImpl = win.pImpl;
|
|
1024
|
+
CreateCoreWebView2EnvironmentWithOptions(
|
|
1025
|
+
nullptr, nullptr, nullptr,
|
|
1026
|
+
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
|
|
1027
|
+
[hwnd, pImpl](HRESULT result,
|
|
1028
|
+
ICoreWebView2Environment *env) -> HRESULT {
|
|
1029
|
+
if (FAILED(result) || !env)
|
|
1030
|
+
return result;
|
|
1031
|
+
env->CreateCoreWebView2Controller(
|
|
1032
|
+
hwnd,
|
|
1033
|
+
Callback<
|
|
1034
|
+
ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
|
|
1035
|
+
[pImpl](HRESULT result,
|
|
1036
|
+
ICoreWebView2Controller *controller) -> HRESULT {
|
|
1037
|
+
(void)result; // Suppress unused warning
|
|
1038
|
+
if (controller != nullptr) {
|
|
1039
|
+
pImpl->controller = controller;
|
|
1040
|
+
controller->get_CoreWebView2(&pImpl->webview);
|
|
1041
|
+
|
|
1042
|
+
RECT bounds;
|
|
1043
|
+
HWND parentHwnd;
|
|
1044
|
+
controller->get_ParentWindow(&parentHwnd);
|
|
1045
|
+
GetClientRect(parentHwnd, &bounds);
|
|
1046
|
+
controller->put_Bounds(bounds);
|
|
1047
|
+
Window::Impl::embeddedWebviewByParent[parentHwnd] =
|
|
1048
|
+
pImpl.get();
|
|
1049
|
+
|
|
1050
|
+
// AllowExternalDrop must be TRUE so that we can handle
|
|
1051
|
+
// drops via WebView2's DOM events + chrome.webview messaging.
|
|
1052
|
+
// The JS side catches the drop event and sends file data
|
|
1053
|
+
// to native via window.__native_invoke__.
|
|
1119
1054
|
ComPtr<ICoreWebView2Controller4> controller4;
|
|
1120
1055
|
if (controller &&
|
|
1121
1056
|
SUCCEEDED(controller->QueryInterface(
|
|
@@ -1123,35 +1058,35 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1123
1058
|
controller4) {
|
|
1124
1059
|
controller4->put_AllowExternalDrop(TRUE);
|
|
1125
1060
|
}
|
|
1126
|
-
|
|
1127
|
-
pImpl->nativeWebView = pImpl->webview.Get();
|
|
1128
|
-
pImpl->ready = true;
|
|
1129
|
-
|
|
1130
|
-
// Inject bridge script that runs on EVERY document
|
|
1131
|
-
// (survives navigation)
|
|
1132
|
-
std::string bridgeScript = R"(
|
|
1133
|
-
window.__native_invoke__ = function(request) {
|
|
1134
|
-
if (window.chrome && window.chrome.webview) {
|
|
1135
|
-
window.chrome.webview.postMessage(request);
|
|
1136
|
-
}
|
|
1137
|
-
};
|
|
1138
|
-
)";
|
|
1139
|
-
pImpl->webview->AddScriptToExecuteOnDocumentCreated(
|
|
1140
|
-
std::wstring(bridgeScript.begin(),
|
|
1141
|
-
bridgeScript.end())
|
|
1142
|
-
.c_str(),
|
|
1143
|
-
nullptr);
|
|
1144
|
-
|
|
1145
|
-
// Inject scrollbar hiding CSS if disabled
|
|
1146
|
-
if (!pImpl->config.scrollbars) {
|
|
1147
|
-
std::string scrollbarScript = kHideScrollbarsScript;
|
|
1148
|
-
pImpl->webview->AddScriptToExecuteOnDocumentCreated(
|
|
1149
|
-
std::wstring(scrollbarScript.begin(),
|
|
1150
|
-
scrollbarScript.end())
|
|
1151
|
-
.c_str(),
|
|
1152
|
-
nullptr);
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1061
|
+
|
|
1062
|
+
pImpl->nativeWebView = pImpl->webview.Get();
|
|
1063
|
+
pImpl->ready = true;
|
|
1064
|
+
|
|
1065
|
+
// Inject bridge script that runs on EVERY document
|
|
1066
|
+
// (survives navigation)
|
|
1067
|
+
std::string bridgeScript = R"(
|
|
1068
|
+
window.__native_invoke__ = function(request) {
|
|
1069
|
+
if (window.chrome && window.chrome.webview) {
|
|
1070
|
+
window.chrome.webview.postMessage(request);
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
)";
|
|
1074
|
+
pImpl->webview->AddScriptToExecuteOnDocumentCreated(
|
|
1075
|
+
std::wstring(bridgeScript.begin(),
|
|
1076
|
+
bridgeScript.end())
|
|
1077
|
+
.c_str(),
|
|
1078
|
+
nullptr);
|
|
1079
|
+
|
|
1080
|
+
// Inject scrollbar hiding CSS if disabled
|
|
1081
|
+
if (!pImpl->config.scrollbars) {
|
|
1082
|
+
std::string scrollbarScript = kHideScrollbarsScript;
|
|
1083
|
+
pImpl->webview->AddScriptToExecuteOnDocumentCreated(
|
|
1084
|
+
std::wstring(scrollbarScript.begin(),
|
|
1085
|
+
scrollbarScript.end())
|
|
1086
|
+
.c_str(),
|
|
1087
|
+
nullptr);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1155
1090
|
// Block browser default drag-drop behavior (prevents
|
|
1156
1091
|
// the browser from navigating to the dropped file) while
|
|
1157
1092
|
// still allowing visual feedback on drop zones.
|
|
@@ -1227,6 +1162,43 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1227
1162
|
e.preventDefault();
|
|
1228
1163
|
dragDepth = 0;
|
|
1229
1164
|
updateActiveZone(null);
|
|
1165
|
+
|
|
1166
|
+
// Process dropped files and send to native
|
|
1167
|
+
if (e.dataTransfer && e.dataTransfer.files) {
|
|
1168
|
+
var files = [];
|
|
1169
|
+
for (var i = 0; i < e.dataTransfer.files.length; i++) {
|
|
1170
|
+
var file = e.dataTransfer.files[i];
|
|
1171
|
+
files.push({
|
|
1172
|
+
name: file.name,
|
|
1173
|
+
size: file.size,
|
|
1174
|
+
type: file.type || 'application/octet-stream'
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// Send file info to native via the bridge
|
|
1179
|
+
if (window.__native_invoke__) {
|
|
1180
|
+
var request = JSON.stringify({
|
|
1181
|
+
method: 'plusui:fileDrop',
|
|
1182
|
+
params: { files: files }
|
|
1183
|
+
});
|
|
1184
|
+
window.__native_invoke__(request);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// Also dispatch custom event for JS listeners
|
|
1188
|
+
window.dispatchEvent(new CustomEvent('plusui:fileDrop.filesDropped', {
|
|
1189
|
+
detail: { files: files }
|
|
1190
|
+
}));
|
|
1191
|
+
|
|
1192
|
+
// Call zone-specific handler if exists
|
|
1193
|
+
var zone = findDropZone(e);
|
|
1194
|
+
var zoneName = zone ? zone.getAttribute('data-dropzone') : null;
|
|
1195
|
+
if (zoneName && window.__plusui_fileDrop__) {
|
|
1196
|
+
window.__plusui_fileDrop__(zoneName, files);
|
|
1197
|
+
}
|
|
1198
|
+
if (!zoneName && window.__plusui_fileDrop_default__) {
|
|
1199
|
+
window.__plusui_fileDrop_default__(files);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1230
1202
|
}, true);
|
|
1231
1203
|
|
|
1232
1204
|
// Also block at window level as a safety net
|
|
@@ -1240,428 +1212,450 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1240
1212
|
.c_str(),
|
|
1241
1213
|
nullptr);
|
|
1242
1214
|
|
|
1243
|
-
// Set up WebMessageReceived handler for JS->C++ bridge
|
|
1244
|
-
pImpl->webview->add_WebMessageReceived(
|
|
1245
|
-
Callback<
|
|
1246
|
-
ICoreWebView2WebMessageReceivedEventHandler>(
|
|
1247
|
-
[pImpl](ICoreWebView2 *sender,
|
|
1248
|
-
ICoreWebView2WebMessageReceivedEventArgs
|
|
1249
|
-
*args) -> HRESULT {
|
|
1250
|
-
(void)sender; // Suppress unused warning
|
|
1251
|
-
LPWSTR message = nullptr;
|
|
1252
|
-
args->TryGetWebMessageAsString(&message);
|
|
1253
|
-
if (!message)
|
|
1254
|
-
return S_OK;
|
|
1255
|
-
std::wstring wmsg(message);
|
|
1256
|
-
#pragma warning(push)
|
|
1257
|
-
#pragma warning(disable : 4244) // Suppress wchar_t to char conversion warning
|
|
1258
|
-
std::string msg(wmsg.begin(), wmsg.end());
|
|
1259
|
-
#pragma warning(pop)
|
|
1260
|
-
CoTaskMemFree(message);
|
|
1261
|
-
|
|
1262
|
-
// Debug: log received message
|
|
1263
|
-
std::cout << "[PlusUI] Received: " << msg
|
|
1264
|
-
<< std::endl;
|
|
1265
|
-
|
|
1266
|
-
bool handledByMessageCallback = false;
|
|
1267
|
-
|
|
1268
|
-
// New Generic Message Handler
|
|
1269
|
-
if (pImpl->messageCallback) {
|
|
1270
|
-
pImpl->messageCallback(msg);
|
|
1271
|
-
if (msg.find("\"kind\"") !=
|
|
1272
|
-
std::string::npos) {
|
|
1273
|
-
handledByMessageCallback = true;
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
// Parse JSON-RPC: {"id":"...",
|
|
1278
|
-
// "method":"window.minimize", "params":[...]}
|
|
1279
|
-
std::string id, method;
|
|
1280
|
-
std::string result = "null";
|
|
1281
|
-
bool success = false;
|
|
1282
|
-
|
|
1283
|
-
//
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
auto
|
|
1321
|
-
size_t
|
|
1322
|
-
if (
|
|
1323
|
-
return std::string(
|
|
1324
|
-
|
|
1325
|
-
if (
|
|
1326
|
-
return std::string(
|
|
1327
|
-
size_t
|
|
1328
|
-
if (
|
|
1329
|
-
return std::string(
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
if (
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
if (pImpl->window)
|
|
1463
|
-
pImpl->window->
|
|
1464
|
-
success = true;
|
|
1465
|
-
} else if (winMethod == "
|
|
1466
|
-
if (pImpl->window)
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
if (
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
success = true;
|
|
1601
|
-
} else if (
|
|
1602
|
-
pImpl->
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
pImpl->webview->
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
pImpl->webview->
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
if (
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1215
|
+
// Set up WebMessageReceived handler for JS->C++ bridge
|
|
1216
|
+
pImpl->webview->add_WebMessageReceived(
|
|
1217
|
+
Callback<
|
|
1218
|
+
ICoreWebView2WebMessageReceivedEventHandler>(
|
|
1219
|
+
[pImpl](ICoreWebView2 *sender,
|
|
1220
|
+
ICoreWebView2WebMessageReceivedEventArgs
|
|
1221
|
+
*args) -> HRESULT {
|
|
1222
|
+
(void)sender; // Suppress unused warning
|
|
1223
|
+
LPWSTR message = nullptr;
|
|
1224
|
+
args->TryGetWebMessageAsString(&message);
|
|
1225
|
+
if (!message)
|
|
1226
|
+
return S_OK;
|
|
1227
|
+
std::wstring wmsg(message);
|
|
1228
|
+
#pragma warning(push)
|
|
1229
|
+
#pragma warning(disable : 4244) // Suppress wchar_t to char conversion warning
|
|
1230
|
+
std::string msg(wmsg.begin(), wmsg.end());
|
|
1231
|
+
#pragma warning(pop)
|
|
1232
|
+
CoTaskMemFree(message);
|
|
1233
|
+
|
|
1234
|
+
// Debug: log received message
|
|
1235
|
+
std::cout << "[PlusUI] Received: " << msg
|
|
1236
|
+
<< std::endl;
|
|
1237
|
+
|
|
1238
|
+
bool handledByMessageCallback = false;
|
|
1239
|
+
|
|
1240
|
+
// New Generic Message Handler
|
|
1241
|
+
if (pImpl->messageCallback) {
|
|
1242
|
+
pImpl->messageCallback(msg);
|
|
1243
|
+
if (msg.find("\"kind\"") !=
|
|
1244
|
+
std::string::npos) {
|
|
1245
|
+
handledByMessageCallback = true;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Parse JSON-RPC: {"id":"...",
|
|
1250
|
+
// "method":"window.minimize", "params":[...]}
|
|
1251
|
+
std::string id, method;
|
|
1252
|
+
std::string result = "null";
|
|
1253
|
+
bool success = false;
|
|
1254
|
+
|
|
1255
|
+
// Handle plusui:fileDrop message from JS
|
|
1256
|
+
if (msg.find("plusui:fileDrop") != std::string::npos) {
|
|
1257
|
+
// Extract files from the message
|
|
1258
|
+
size_t filesStart = msg.find("\"files\"");
|
|
1259
|
+
if (filesStart != std::string::npos) {
|
|
1260
|
+
size_t bracketStart = msg.find("[", filesStart);
|
|
1261
|
+
size_t bracketEnd = msg.find("]", bracketStart);
|
|
1262
|
+
if (bracketStart != std::string::npos && bracketEnd != std::string::npos) {
|
|
1263
|
+
std::string filesJson = msg.substr(bracketStart, bracketEnd - bracketStart + 1);
|
|
1264
|
+
|
|
1265
|
+
// Fire the global event in JS
|
|
1266
|
+
std::string eventScript =
|
|
1267
|
+
"window.dispatchEvent(new CustomEvent('plusui:fileDrop.filesDropped', { detail: { files: " + filesJson + " } }));";
|
|
1268
|
+
pImpl->webview->ExecuteScript(
|
|
1269
|
+
std::wstring(eventScript.begin(), eventScript.end()).c_str(), nullptr);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
success = true;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// Simple JSON parsing
|
|
1276
|
+
auto getId = [&msg]() {
|
|
1277
|
+
size_t pos = msg.find("\"id\"");
|
|
1278
|
+
if (pos == std::string::npos)
|
|
1279
|
+
return std::string();
|
|
1280
|
+
pos = msg.find(":", pos);
|
|
1281
|
+
if (pos == std::string::npos)
|
|
1282
|
+
return std::string();
|
|
1283
|
+
size_t start = msg.find("\"", pos);
|
|
1284
|
+
if (start == std::string::npos)
|
|
1285
|
+
return std::string();
|
|
1286
|
+
size_t end = msg.find("\"", start + 1);
|
|
1287
|
+
if (end == std::string::npos)
|
|
1288
|
+
return std::string();
|
|
1289
|
+
return msg.substr(start + 1,
|
|
1290
|
+
end - start - 1);
|
|
1291
|
+
};
|
|
1292
|
+
auto getMethod = [&msg]() {
|
|
1293
|
+
size_t pos = msg.find("\"method\"");
|
|
1294
|
+
if (pos == std::string::npos)
|
|
1295
|
+
return std::string();
|
|
1296
|
+
pos = msg.find(":", pos);
|
|
1297
|
+
if (pos == std::string::npos)
|
|
1298
|
+
return std::string();
|
|
1299
|
+
size_t start = msg.find("\"", pos);
|
|
1300
|
+
if (start == std::string::npos)
|
|
1301
|
+
return std::string();
|
|
1302
|
+
size_t end = msg.find("\"", start + 1);
|
|
1303
|
+
if (end == std::string::npos)
|
|
1304
|
+
return std::string();
|
|
1305
|
+
return msg.substr(start + 1,
|
|
1306
|
+
end - start - 1);
|
|
1307
|
+
};
|
|
1308
|
+
|
|
1309
|
+
id = getId();
|
|
1310
|
+
method = getMethod();
|
|
1311
|
+
|
|
1312
|
+
auto extractFirstParam = [&msg]() {
|
|
1313
|
+
size_t paramsPos = msg.find("\"params\"");
|
|
1314
|
+
if (paramsPos == std::string::npos)
|
|
1315
|
+
return std::string("null");
|
|
1316
|
+
size_t colonPos = msg.find(":", paramsPos);
|
|
1317
|
+
if (colonPos == std::string::npos)
|
|
1318
|
+
return std::string("null");
|
|
1319
|
+
size_t arrayStart = msg.find("[", colonPos);
|
|
1320
|
+
if (arrayStart == std::string::npos)
|
|
1321
|
+
return std::string("null");
|
|
1322
|
+
|
|
1323
|
+
size_t i = arrayStart + 1;
|
|
1324
|
+
while (i < msg.size() &&
|
|
1325
|
+
std::isspace(
|
|
1326
|
+
static_cast<unsigned char>(msg[i])))
|
|
1327
|
+
++i;
|
|
1328
|
+
if (i >= msg.size() || msg[i] == ']')
|
|
1329
|
+
return std::string("null");
|
|
1330
|
+
|
|
1331
|
+
size_t start = i;
|
|
1332
|
+
if (msg[i] == '"') {
|
|
1333
|
+
++i;
|
|
1334
|
+
bool escaped = false;
|
|
1335
|
+
while (i < msg.size()) {
|
|
1336
|
+
char c = msg[i];
|
|
1337
|
+
if (escaped) {
|
|
1338
|
+
escaped = false;
|
|
1339
|
+
} else if (c == '\\') {
|
|
1340
|
+
escaped = true;
|
|
1341
|
+
} else if (c == '"') {
|
|
1342
|
+
++i;
|
|
1343
|
+
break;
|
|
1344
|
+
}
|
|
1345
|
+
++i;
|
|
1346
|
+
}
|
|
1347
|
+
return msg.substr(start, i - start);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
int depth = 0;
|
|
1351
|
+
bool inString = false;
|
|
1352
|
+
bool escaped = false;
|
|
1353
|
+
while (i < msg.size()) {
|
|
1354
|
+
char c = msg[i];
|
|
1355
|
+
if (inString) {
|
|
1356
|
+
if (escaped) {
|
|
1357
|
+
escaped = false;
|
|
1358
|
+
} else if (c == '\\') {
|
|
1359
|
+
escaped = true;
|
|
1360
|
+
} else if (c == '"') {
|
|
1361
|
+
inString = false;
|
|
1362
|
+
}
|
|
1363
|
+
} else {
|
|
1364
|
+
if (c == '"') {
|
|
1365
|
+
inString = true;
|
|
1366
|
+
} else if (c == '{' || c == '[') {
|
|
1367
|
+
++depth;
|
|
1368
|
+
} else if (c == '}' || c == ']') {
|
|
1369
|
+
if (depth == 0) {
|
|
1370
|
+
break;
|
|
1371
|
+
}
|
|
1372
|
+
--depth;
|
|
1373
|
+
} else if (c == ',' && depth == 0) {
|
|
1374
|
+
break;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
++i;
|
|
1378
|
+
}
|
|
1379
|
+
return msg.substr(start, i - start);
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1382
|
+
auto decodeJsonString =
|
|
1383
|
+
[](const std::string &input) {
|
|
1384
|
+
if (input.size() < 2 ||
|
|
1385
|
+
input.front() != '"' ||
|
|
1386
|
+
input.back() != '"') {
|
|
1387
|
+
return input;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
std::string decoded;
|
|
1391
|
+
decoded.reserve(input.size() - 2);
|
|
1392
|
+
for (size_t i = 1; i + 1 < input.size();
|
|
1393
|
+
++i) {
|
|
1394
|
+
char c = input[i];
|
|
1395
|
+
if (c == '\\' && i + 1 < input.size() - 1) {
|
|
1396
|
+
char next = input[++i];
|
|
1397
|
+
switch (next) {
|
|
1398
|
+
case '"':
|
|
1399
|
+
decoded.push_back('"');
|
|
1400
|
+
break;
|
|
1401
|
+
case '\\':
|
|
1402
|
+
decoded.push_back('\\');
|
|
1403
|
+
break;
|
|
1404
|
+
case '/':
|
|
1405
|
+
decoded.push_back('/');
|
|
1406
|
+
break;
|
|
1407
|
+
case 'n':
|
|
1408
|
+
decoded.push_back('\n');
|
|
1409
|
+
break;
|
|
1410
|
+
case 'r':
|
|
1411
|
+
decoded.push_back('\r');
|
|
1412
|
+
break;
|
|
1413
|
+
case 't':
|
|
1414
|
+
decoded.push_back('\t');
|
|
1415
|
+
break;
|
|
1416
|
+
default:
|
|
1417
|
+
decoded.push_back(next);
|
|
1418
|
+
break;
|
|
1419
|
+
}
|
|
1420
|
+
} else {
|
|
1421
|
+
decoded.push_back(c);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
return decoded;
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
// Route to handlers
|
|
1428
|
+
if (handledByMessageCallback) {
|
|
1429
|
+
success = true;
|
|
1430
|
+
result = "null";
|
|
1431
|
+
} else if (method.find("window.") == 0) {
|
|
1432
|
+
std::string winMethod = method.substr(7);
|
|
1433
|
+
if (winMethod == "minimize") {
|
|
1434
|
+
if (pImpl->window)
|
|
1435
|
+
pImpl->window->minimize();
|
|
1436
|
+
success = true;
|
|
1437
|
+
} else if (winMethod == "maximize") {
|
|
1438
|
+
if (pImpl->window)
|
|
1439
|
+
pImpl->window->maximize();
|
|
1440
|
+
success = true;
|
|
1441
|
+
} else if (winMethod == "restore") {
|
|
1442
|
+
if (pImpl->window)
|
|
1443
|
+
pImpl->window->restore();
|
|
1444
|
+
success = true;
|
|
1445
|
+
} else if (winMethod == "close") {
|
|
1446
|
+
if (pImpl->window)
|
|
1447
|
+
pImpl->window->close();
|
|
1448
|
+
success = true;
|
|
1449
|
+
} else if (winMethod == "show") {
|
|
1450
|
+
if (pImpl->window)
|
|
1451
|
+
pImpl->window->show();
|
|
1452
|
+
success = true;
|
|
1453
|
+
} else if (winMethod == "hide") {
|
|
1454
|
+
if (pImpl->window)
|
|
1455
|
+
pImpl->window->hide();
|
|
1456
|
+
success = true;
|
|
1457
|
+
} else if (winMethod == "getSize") {
|
|
1458
|
+
if (pImpl->window) {
|
|
1459
|
+
int w, h;
|
|
1460
|
+
pImpl->window->getSize(w, h);
|
|
1461
|
+
result =
|
|
1462
|
+
"{\"width\":" + std::to_string(w) +
|
|
1463
|
+
",\"height\":" + std::to_string(h) +
|
|
1464
|
+
"}";
|
|
1465
|
+
}
|
|
1466
|
+
success = true;
|
|
1467
|
+
} else if (winMethod == "getPosition") {
|
|
1468
|
+
if (pImpl->window) {
|
|
1469
|
+
int x, y;
|
|
1470
|
+
pImpl->window->getPosition(x, y);
|
|
1471
|
+
result = "{\"x\":" + std::to_string(x) +
|
|
1472
|
+
",\"y\":" + std::to_string(y) +
|
|
1473
|
+
"}";
|
|
1474
|
+
}
|
|
1475
|
+
success = true;
|
|
1476
|
+
} else if (winMethod == "setSize") {
|
|
1477
|
+
// Parse params [width, height]
|
|
1478
|
+
size_t p1 = msg.find("[");
|
|
1479
|
+
size_t p2 = msg.find("]");
|
|
1480
|
+
if (p1 != std::string::npos &&
|
|
1481
|
+
p2 != std::string::npos) {
|
|
1482
|
+
std::string params =
|
|
1483
|
+
msg.substr(p1 + 1, p2 - p1 - 1);
|
|
1484
|
+
int w = 0, h = 0;
|
|
1485
|
+
sscanf(params.c_str(), "%d, %d", &w,
|
|
1486
|
+
&h);
|
|
1487
|
+
if (pImpl->window)
|
|
1488
|
+
pImpl->window->setSize(w, h);
|
|
1489
|
+
}
|
|
1490
|
+
success = true;
|
|
1491
|
+
} else if (winMethod == "setPosition") {
|
|
1492
|
+
size_t p1 = msg.find("[");
|
|
1493
|
+
size_t p2 = msg.find("]");
|
|
1494
|
+
if (p1 != std::string::npos &&
|
|
1495
|
+
p2 != std::string::npos) {
|
|
1496
|
+
std::string params =
|
|
1497
|
+
msg.substr(p1 + 1, p2 - p1 - 1);
|
|
1498
|
+
int x = 0, y = 0;
|
|
1499
|
+
sscanf(params.c_str(), "%d, %d", &x,
|
|
1500
|
+
&y);
|
|
1501
|
+
if (pImpl->window)
|
|
1502
|
+
pImpl->window->setPosition(x, y);
|
|
1503
|
+
}
|
|
1504
|
+
success = true;
|
|
1505
|
+
} else if (winMethod == "setTitle") {
|
|
1506
|
+
size_t p1 = msg.find("[\"");
|
|
1507
|
+
size_t p2 = msg.find("\"]");
|
|
1508
|
+
if (p1 != std::string::npos &&
|
|
1509
|
+
p2 != std::string::npos) {
|
|
1510
|
+
std::string title =
|
|
1511
|
+
msg.substr(p1 + 2, p2 - p1 - 2);
|
|
1512
|
+
if (pImpl->window)
|
|
1513
|
+
pImpl->window->setTitle(title);
|
|
1514
|
+
}
|
|
1515
|
+
success = true;
|
|
1516
|
+
} else if (winMethod == "setFullscreen") {
|
|
1517
|
+
size_t p1 = msg.find("[");
|
|
1518
|
+
size_t p2 = msg.find("]");
|
|
1519
|
+
if (p1 != std::string::npos &&
|
|
1520
|
+
p2 != std::string::npos) {
|
|
1521
|
+
std::string params =
|
|
1522
|
+
msg.substr(p1 + 1, p2 - p1 - 1);
|
|
1523
|
+
if (pImpl->window)
|
|
1524
|
+
pImpl->window->setFullscreen(
|
|
1525
|
+
params.find("true") !=
|
|
1526
|
+
std::string::npos);
|
|
1527
|
+
}
|
|
1528
|
+
success = true;
|
|
1529
|
+
} else if (winMethod == "setAlwaysOnTop") {
|
|
1530
|
+
size_t p1 = msg.find("[");
|
|
1531
|
+
size_t p2 = msg.find("]");
|
|
1532
|
+
if (p1 != std::string::npos &&
|
|
1533
|
+
p2 != std::string::npos) {
|
|
1534
|
+
std::string params =
|
|
1535
|
+
msg.substr(p1 + 1, p2 - p1 - 1);
|
|
1536
|
+
if (pImpl->window)
|
|
1537
|
+
pImpl->window->setAlwaysOnTop(
|
|
1538
|
+
params.find("true") !=
|
|
1539
|
+
std::string::npos);
|
|
1540
|
+
}
|
|
1541
|
+
success = true;
|
|
1542
|
+
} else if (winMethod == "setResizable") {
|
|
1543
|
+
size_t p1 = msg.find("[");
|
|
1544
|
+
size_t p2 = msg.find("]");
|
|
1545
|
+
if (p1 != std::string::npos &&
|
|
1546
|
+
p2 != std::string::npos) {
|
|
1547
|
+
std::string params =
|
|
1548
|
+
msg.substr(p1 + 1, p2 - p1 - 1);
|
|
1549
|
+
if (pImpl->window)
|
|
1550
|
+
pImpl->window->setResizable(
|
|
1551
|
+
params.find("true") !=
|
|
1552
|
+
std::string::npos);
|
|
1553
|
+
}
|
|
1554
|
+
success = true;
|
|
1555
|
+
} else if (winMethod == "isMaximized") {
|
|
1556
|
+
result = (pImpl->window &&
|
|
1557
|
+
pImpl->window->isMaximized())
|
|
1558
|
+
? "true"
|
|
1559
|
+
: "false";
|
|
1560
|
+
success = true;
|
|
1561
|
+
} else if (winMethod == "isMinimized") {
|
|
1562
|
+
result = (pImpl->window &&
|
|
1563
|
+
pImpl->window->isMinimized())
|
|
1564
|
+
? "true"
|
|
1565
|
+
: "false";
|
|
1566
|
+
success = true;
|
|
1567
|
+
} else if (winMethod == "isVisible") {
|
|
1568
|
+
result = (pImpl->window &&
|
|
1569
|
+
pImpl->window->isVisible())
|
|
1570
|
+
? "true"
|
|
1571
|
+
: "false";
|
|
1572
|
+
success = true;
|
|
1573
|
+
} else if (winMethod == "center") {
|
|
1574
|
+
if (pImpl->window)
|
|
1575
|
+
pImpl->window->center();
|
|
1576
|
+
success = true;
|
|
1577
|
+
}
|
|
1578
|
+
} else if (method.find("browser.") == 0) {
|
|
1579
|
+
std::string browserMethod =
|
|
1580
|
+
method.substr(8);
|
|
1581
|
+
if (browserMethod == "navigate") {
|
|
1582
|
+
size_t p1 = msg.find("[\"");
|
|
1583
|
+
size_t p2 = msg.find("\"]");
|
|
1584
|
+
if (p1 != std::string::npos &&
|
|
1585
|
+
p2 != std::string::npos) {
|
|
1586
|
+
std::string url =
|
|
1587
|
+
msg.substr(p1 + 2, p2 - p1 - 2);
|
|
1588
|
+
pImpl->webview->Navigate(
|
|
1589
|
+
std::wstring(url.begin(), url.end())
|
|
1590
|
+
.c_str());
|
|
1591
|
+
}
|
|
1592
|
+
success = true;
|
|
1593
|
+
} else if (browserMethod == "goBack") {
|
|
1594
|
+
pImpl->webview->GoBack();
|
|
1595
|
+
success = true;
|
|
1596
|
+
} else if (browserMethod == "goForward") {
|
|
1597
|
+
pImpl->webview->GoForward();
|
|
1598
|
+
success = true;
|
|
1599
|
+
} else if (browserMethod == "reload") {
|
|
1600
|
+
pImpl->webview->Reload();
|
|
1601
|
+
success = true;
|
|
1602
|
+
} else if (browserMethod == "stop") {
|
|
1603
|
+
pImpl->webview->Stop();
|
|
1604
|
+
success = true;
|
|
1605
|
+
} else if (browserMethod == "getUrl") {
|
|
1606
|
+
result = "\"" + pImpl->currentURL + "\"";
|
|
1607
|
+
success = true;
|
|
1608
|
+
} else if (browserMethod == "getTitle") {
|
|
1609
|
+
result =
|
|
1610
|
+
"\"" + pImpl->currentTitle + "\"";
|
|
1611
|
+
success = true;
|
|
1612
|
+
} else if (browserMethod == "canGoBack") {
|
|
1613
|
+
BOOL canBack;
|
|
1614
|
+
pImpl->webview->get_CanGoBack(&canBack);
|
|
1615
|
+
result = canBack ? "true" : "false";
|
|
1616
|
+
success = true;
|
|
1617
|
+
} else if (browserMethod ==
|
|
1618
|
+
"canGoForward") {
|
|
1619
|
+
BOOL canForward;
|
|
1620
|
+
pImpl->webview->get_CanGoForward(
|
|
1621
|
+
&canForward);
|
|
1622
|
+
result = canForward ? "true" : "false";
|
|
1623
|
+
success = true;
|
|
1624
|
+
}
|
|
1625
|
+
} else if (method.find("fileDrop.") == 0) {
|
|
1626
|
+
std::string fileDropMethod =
|
|
1627
|
+
method.substr(9);
|
|
1628
|
+
if (fileDropMethod == "setEnabled") {
|
|
1629
|
+
size_t p1 = msg.find("[");
|
|
1630
|
+
size_t p2 = msg.find("]");
|
|
1631
|
+
if (p1 != std::string::npos &&
|
|
1632
|
+
p2 != std::string::npos) {
|
|
1633
|
+
std::string params =
|
|
1634
|
+
msg.substr(p1 + 1, p2 - p1 - 1);
|
|
1635
|
+
bool enabled = params.find("true") !=
|
|
1636
|
+
std::string::npos;
|
|
1637
|
+
pImpl->config.fileDrop = enabled;
|
|
1638
|
+
|
|
1639
|
+
HWND targetHwnd = nullptr;
|
|
1640
|
+
if (pImpl->window) {
|
|
1641
|
+
targetHwnd = static_cast<HWND>(
|
|
1642
|
+
pImpl->window->nativeHandle());
|
|
1643
|
+
}
|
|
1644
|
+
if (!targetHwnd && pImpl->controller) {
|
|
1645
|
+
pImpl->controller->get_ParentWindow(
|
|
1646
|
+
&targetHwnd);
|
|
1647
|
+
}
|
|
1648
|
+
if (targetHwnd) {
|
|
1649
|
+
DragAcceptFiles(targetHwnd,
|
|
1650
|
+
enabled ? TRUE
|
|
1651
|
+
: FALSE);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// Keep external drops DISABLED at WebView2 level
|
|
1655
|
+
// so our IDropTarget on the parent HWND can intercept
|
|
1656
|
+
// them and WM_DROPFILES never fires (which would
|
|
1657
|
+
// cause double-handling). We handle everything
|
|
1658
|
+
// in IDropTarget::Drop.
|
|
1665
1659
|
if (pImpl->controller) {
|
|
1666
1660
|
ComPtr<ICoreWebView2Controller4>
|
|
1667
1661
|
controller4;
|
|
@@ -1669,115 +1663,115 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1669
1663
|
&controller4)) &&
|
|
1670
1664
|
controller4) {
|
|
1671
1665
|
controller4->put_AllowExternalDrop(
|
|
1672
|
-
|
|
1666
|
+
FALSE);
|
|
1673
1667
|
}
|
|
1674
1668
|
}
|
|
1675
|
-
}
|
|
1676
|
-
success = true;
|
|
1677
|
-
} else if (fileDropMethod == "isEnabled") {
|
|
1678
|
-
result = pImpl->config.fileDrop
|
|
1679
|
-
? "true"
|
|
1680
|
-
: "false";
|
|
1681
|
-
success = true;
|
|
1682
|
-
} else if (fileDropMethod == "startDrag") {
|
|
1683
|
-
result = "false";
|
|
1684
|
-
success = true;
|
|
1685
|
-
} else if (fileDropMethod ==
|
|
1686
|
-
"clearCallbacks") {
|
|
1687
|
-
success = true;
|
|
1688
|
-
}
|
|
1689
|
-
} else if (method.find("webview.") == 0) {
|
|
1690
|
-
std::string bindingName = method.substr(8);
|
|
1691
|
-
auto bindingIt =
|
|
1692
|
-
pImpl->bindings.find(bindingName);
|
|
1693
|
-
if (bindingIt != pImpl->bindings.end()) {
|
|
1694
|
-
std::string rawParam =
|
|
1695
|
-
extractFirstParam();
|
|
1696
|
-
std::string callbackArg =
|
|
1697
|
-
decodeJsonString(rawParam);
|
|
1698
|
-
try {
|
|
1699
|
-
result =
|
|
1700
|
-
bindingIt->second(callbackArg);
|
|
1701
|
-
if (result.empty()) {
|
|
1702
|
-
result = "null";
|
|
1703
|
-
}
|
|
1704
|
-
success = true;
|
|
1705
|
-
} catch (const std::exception &e) {
|
|
1706
|
-
std::cerr
|
|
1707
|
-
<< "[PlusUI] webview binding error: "
|
|
1708
|
-
<< e.what() << std::endl;
|
|
1709
|
-
result = "null";
|
|
1710
|
-
success = true;
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
// Send response back to JS (matches
|
|
1716
|
-
// plusui-native-core SDK bridge)
|
|
1717
|
-
std::string response =
|
|
1718
|
-
"window.__response__(\"" + id + "\", " +
|
|
1719
|
-
result + ");";
|
|
1720
|
-
pImpl->webview->ExecuteScript(
|
|
1721
|
-
std::wstring(response.begin(),
|
|
1722
|
-
response.end())
|
|
1723
|
-
.c_str(),
|
|
1724
|
-
nullptr);
|
|
1725
|
-
|
|
1726
|
-
return S_OK;
|
|
1727
|
-
})
|
|
1728
|
-
.Get(),
|
|
1729
|
-
nullptr);
|
|
1730
|
-
|
|
1731
|
-
// Process pending scripts
|
|
1732
|
-
for (const auto &script : pImpl->pendingScripts) {
|
|
1733
|
-
pImpl->webview->ExecuteScript(
|
|
1734
|
-
std::wstring(script.begin(), script.end())
|
|
1735
|
-
.c_str(),
|
|
1736
|
-
nullptr);
|
|
1737
|
-
}
|
|
1738
|
-
pImpl->pendingScripts.clear();
|
|
1739
|
-
|
|
1740
|
-
// Process pending navigation
|
|
1741
|
-
if (!pImpl->pendingNavigation.empty()) {
|
|
1742
|
-
pImpl->webview->Navigate(
|
|
1743
|
-
std::wstring(pImpl->pendingNavigation.begin(),
|
|
1744
|
-
pImpl->pendingNavigation.end())
|
|
1745
|
-
.c_str());
|
|
1746
|
-
pImpl->pendingNavigation.clear();
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
// Process pending HTML
|
|
1750
|
-
if (!pImpl->pendingHTML.empty()) {
|
|
1751
|
-
pImpl->webview->NavigateToString(
|
|
1752
|
-
std::wstring(pImpl->pendingHTML.begin(),
|
|
1753
|
-
pImpl->pendingHTML.end())
|
|
1754
|
-
.c_str());
|
|
1755
|
-
pImpl->pendingHTML.clear();
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
// Process pending File
|
|
1759
|
-
if (!pImpl->pendingFile.empty()) {
|
|
1760
|
-
// For now, loadFile is implemented via navigate in
|
|
1761
|
-
// some versions or direct file reading. We'll handle
|
|
1762
|
-
// it via navigate for simplicity if it's already
|
|
1763
|
-
// implemented that way.
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
return S_OK;
|
|
1767
|
-
})
|
|
1768
|
-
.Get());
|
|
1769
|
-
return S_OK;
|
|
1770
|
-
})
|
|
1771
|
-
.Get());
|
|
1772
|
-
|
|
1773
|
-
#elif defined(__APPLE__)
|
|
1774
|
-
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
|
|
1775
|
-
config.preferences.javaScriptEnabled = YES;
|
|
1776
|
-
|
|
1777
|
-
if (win.pImpl->config.devtools) {
|
|
1778
|
-
[config.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1669
|
+
}
|
|
1670
|
+
success = true;
|
|
1671
|
+
} else if (fileDropMethod == "isEnabled") {
|
|
1672
|
+
result = pImpl->config.fileDrop
|
|
1673
|
+
? "true"
|
|
1674
|
+
: "false";
|
|
1675
|
+
success = true;
|
|
1676
|
+
} else if (fileDropMethod == "startDrag") {
|
|
1677
|
+
result = "false";
|
|
1678
|
+
success = true;
|
|
1679
|
+
} else if (fileDropMethod ==
|
|
1680
|
+
"clearCallbacks") {
|
|
1681
|
+
success = true;
|
|
1682
|
+
}
|
|
1683
|
+
} else if (method.find("webview.") == 0) {
|
|
1684
|
+
std::string bindingName = method.substr(8);
|
|
1685
|
+
auto bindingIt =
|
|
1686
|
+
pImpl->bindings.find(bindingName);
|
|
1687
|
+
if (bindingIt != pImpl->bindings.end()) {
|
|
1688
|
+
std::string rawParam =
|
|
1689
|
+
extractFirstParam();
|
|
1690
|
+
std::string callbackArg =
|
|
1691
|
+
decodeJsonString(rawParam);
|
|
1692
|
+
try {
|
|
1693
|
+
result =
|
|
1694
|
+
bindingIt->second(callbackArg);
|
|
1695
|
+
if (result.empty()) {
|
|
1696
|
+
result = "null";
|
|
1697
|
+
}
|
|
1698
|
+
success = true;
|
|
1699
|
+
} catch (const std::exception &e) {
|
|
1700
|
+
std::cerr
|
|
1701
|
+
<< "[PlusUI] webview binding error: "
|
|
1702
|
+
<< e.what() << std::endl;
|
|
1703
|
+
result = "null";
|
|
1704
|
+
success = true;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// Send response back to JS (matches
|
|
1710
|
+
// plusui-native-core SDK bridge)
|
|
1711
|
+
std::string response =
|
|
1712
|
+
"window.__response__(\"" + id + "\", " +
|
|
1713
|
+
result + ");";
|
|
1714
|
+
pImpl->webview->ExecuteScript(
|
|
1715
|
+
std::wstring(response.begin(),
|
|
1716
|
+
response.end())
|
|
1717
|
+
.c_str(),
|
|
1718
|
+
nullptr);
|
|
1719
|
+
|
|
1720
|
+
return S_OK;
|
|
1721
|
+
})
|
|
1722
|
+
.Get(),
|
|
1723
|
+
nullptr);
|
|
1724
|
+
|
|
1725
|
+
// Process pending scripts
|
|
1726
|
+
for (const auto &script : pImpl->pendingScripts) {
|
|
1727
|
+
pImpl->webview->ExecuteScript(
|
|
1728
|
+
std::wstring(script.begin(), script.end())
|
|
1729
|
+
.c_str(),
|
|
1730
|
+
nullptr);
|
|
1731
|
+
}
|
|
1732
|
+
pImpl->pendingScripts.clear();
|
|
1733
|
+
|
|
1734
|
+
// Process pending navigation
|
|
1735
|
+
if (!pImpl->pendingNavigation.empty()) {
|
|
1736
|
+
pImpl->webview->Navigate(
|
|
1737
|
+
std::wstring(pImpl->pendingNavigation.begin(),
|
|
1738
|
+
pImpl->pendingNavigation.end())
|
|
1739
|
+
.c_str());
|
|
1740
|
+
pImpl->pendingNavigation.clear();
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// Process pending HTML
|
|
1744
|
+
if (!pImpl->pendingHTML.empty()) {
|
|
1745
|
+
pImpl->webview->NavigateToString(
|
|
1746
|
+
std::wstring(pImpl->pendingHTML.begin(),
|
|
1747
|
+
pImpl->pendingHTML.end())
|
|
1748
|
+
.c_str());
|
|
1749
|
+
pImpl->pendingHTML.clear();
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
// Process pending File
|
|
1753
|
+
if (!pImpl->pendingFile.empty()) {
|
|
1754
|
+
// For now, loadFile is implemented via navigate in
|
|
1755
|
+
// some versions or direct file reading. We'll handle
|
|
1756
|
+
// it via navigate for simplicity if it's already
|
|
1757
|
+
// implemented that way.
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
return S_OK;
|
|
1761
|
+
})
|
|
1762
|
+
.Get());
|
|
1763
|
+
return S_OK;
|
|
1764
|
+
})
|
|
1765
|
+
.Get());
|
|
1766
|
+
|
|
1767
|
+
#elif defined(__APPLE__)
|
|
1768
|
+
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
|
|
1769
|
+
config.preferences.javaScriptEnabled = YES;
|
|
1770
|
+
|
|
1771
|
+
if (win.pImpl->config.devtools) {
|
|
1772
|
+
[config.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1781
1775
|
// Block browser default drag-drop while allowing drop zone visual feedback.
|
|
1782
1776
|
// File delivery is handled natively by macOS drag APIs, not browser events.
|
|
1783
1777
|
if (win.pImpl->config.disableWebviewDragDrop ||
|
|
@@ -1835,24 +1829,24 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1835
1829
|
forMainFrameOnly:NO];
|
|
1836
1830
|
[config.userContentController addUserScript:userScript];
|
|
1837
1831
|
}
|
|
1838
|
-
|
|
1839
|
-
// Hide scrollbars if disabled
|
|
1840
|
-
if (!win.pImpl->config.scrollbars) {
|
|
1841
|
-
NSString *scrollbarScript =
|
|
1842
|
-
[NSString stringWithUTF8String:kHideScrollbarsScript];
|
|
1843
|
-
|
|
1844
|
-
WKUserScript *scrollScript = [[WKUserScript alloc]
|
|
1845
|
-
initWithSource:scrollbarScript
|
|
1846
|
-
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
|
|
1847
|
-
forMainFrameOnly:NO];
|
|
1848
|
-
[config.userContentController addUserScript:scrollScript];
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
NSView *parentView = (__bridge NSView *)windowHandle;
|
|
1852
|
-
win.pImpl->wkWebView =
|
|
1853
|
-
[[WKWebView alloc] initWithFrame:parentView.bounds configuration:config];
|
|
1854
|
-
[parentView addSubview:win.pImpl->wkWebView];
|
|
1855
|
-
|
|
1832
|
+
|
|
1833
|
+
// Hide scrollbars if disabled
|
|
1834
|
+
if (!win.pImpl->config.scrollbars) {
|
|
1835
|
+
NSString *scrollbarScript =
|
|
1836
|
+
[NSString stringWithUTF8String:kHideScrollbarsScript];
|
|
1837
|
+
|
|
1838
|
+
WKUserScript *scrollScript = [[WKUserScript alloc]
|
|
1839
|
+
initWithSource:scrollbarScript
|
|
1840
|
+
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
|
|
1841
|
+
forMainFrameOnly:NO];
|
|
1842
|
+
[config.userContentController addUserScript:scrollScript];
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
NSView *parentView = (__bridge NSView *)windowHandle;
|
|
1846
|
+
win.pImpl->wkWebView =
|
|
1847
|
+
[[WKWebView alloc] initWithFrame:parentView.bounds configuration:config];
|
|
1848
|
+
[parentView addSubview:win.pImpl->wkWebView];
|
|
1849
|
+
|
|
1856
1850
|
win.pImpl->nativeWebView = (__bridge void *)win.pImpl->wkWebView;
|
|
1857
1851
|
|
|
1858
1852
|
// ── macOS key event forwarding ─────────────────────────────────────────────
|
|
@@ -1935,8 +1929,20 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1935
1929
|
// ── Linux / GTK + WebKit2 WebView ─────────────────────────────────────────
|
|
1936
1930
|
{
|
|
1937
1931
|
GtkWidget *gtkParent = static_cast<GtkWidget*>(windowHandle);
|
|
1932
|
+
|
|
1933
|
+
// Disable GPU hardware acceleration — GBM/DRM buffer creation fails under
|
|
1934
|
+
// XWayland. Software rendering works everywhere and is fine for a UI webview.
|
|
1935
|
+
WebKitSettings *wkSettings = webkit_settings_new();
|
|
1936
|
+
webkit_settings_set_hardware_acceleration_policy(
|
|
1937
|
+
wkSettings, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER);
|
|
1938
|
+
if (win.pImpl->config.devtools) {
|
|
1939
|
+
webkit_settings_set_enable_developer_extras(wkSettings, TRUE);
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1938
1942
|
WebKitWebView *webView =
|
|
1939
|
-
WEBKIT_WEB_VIEW(
|
|
1943
|
+
WEBKIT_WEB_VIEW(webkit_web_view_new_with_settings(wkSettings));
|
|
1944
|
+
g_object_unref(wkSettings);
|
|
1945
|
+
|
|
1940
1946
|
gtk_container_add(GTK_CONTAINER(gtkParent), GTK_WIDGET(webView));
|
|
1941
1947
|
gtk_widget_show(GTK_WIDGET(webView));
|
|
1942
1948
|
win.pImpl->gtkWebView = webView;
|
|
@@ -1955,12 +1961,260 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1955
1961
|
webkit_user_content_manager_add_script(mgr, bridgeScript);
|
|
1956
1962
|
webkit_user_script_unref(bridgeScript);
|
|
1957
1963
|
|
|
1964
|
+
// Register "plusui" as a named message handler so the JS bridge above works.
|
|
1965
|
+
webkit_user_content_manager_register_script_message_handler(mgr, "plusui");
|
|
1966
|
+
|
|
1958
1967
|
// Helper to run JS in this WebView
|
|
1959
1968
|
auto runJS = [webView](const std::string& script) {
|
|
1960
1969
|
webkit_web_view_run_javascript(webView, script.c_str(),
|
|
1961
1970
|
nullptr, nullptr, nullptr);
|
|
1962
1971
|
};
|
|
1963
1972
|
|
|
1973
|
+
// ── JS → C++ message dispatch ─────────────────────────────────────────────
|
|
1974
|
+
// Mirror the full Windows dispatch so every plusui.window.*, app.*, etc.
|
|
1975
|
+
// call from the frontend reaches the correct C++ handler.
|
|
1976
|
+
struct MsgCtx {
|
|
1977
|
+
WebKitWebView *webView;
|
|
1978
|
+
Window::Impl *impl;
|
|
1979
|
+
};
|
|
1980
|
+
auto *msgCtx = new MsgCtx{webView, win.pImpl.get()};
|
|
1981
|
+
|
|
1982
|
+
// Assign to a typed pointer so G_CALLBACK receives a single token (not a
|
|
1983
|
+
// multi-line lambda — the preprocessor would split on commas otherwise).
|
|
1984
|
+
static auto sMsgHandler = +[](WebKitUserContentManager *,
|
|
1985
|
+
WebKitJavascriptResult *jsResult,
|
|
1986
|
+
gpointer data) -> void {
|
|
1987
|
+
auto *ctx = static_cast<MsgCtx *>(data);
|
|
1988
|
+
Window::Impl *pImpl = ctx->impl;
|
|
1989
|
+
|
|
1990
|
+
JSCValue *jsVal = webkit_javascript_result_get_js_value(jsResult);
|
|
1991
|
+
gchar *raw = jsc_value_to_string(jsVal);
|
|
1992
|
+
if (!raw) return;
|
|
1993
|
+
std::string msg(raw);
|
|
1994
|
+
g_free(raw);
|
|
1995
|
+
|
|
1996
|
+
auto runJSResp = [ctx](const std::string &script) {
|
|
1997
|
+
webkit_web_view_run_javascript(ctx->webView, script.c_str(),
|
|
1998
|
+
nullptr, nullptr, nullptr);
|
|
1999
|
+
};
|
|
2000
|
+
|
|
2001
|
+
// ── simple JSON field extractors (no external dep) ───────────────────
|
|
2002
|
+
auto getField = [&](const std::string &key) -> std::string {
|
|
2003
|
+
size_t pos = msg.find("\"" + key + "\"");
|
|
2004
|
+
if (pos == std::string::npos) return {};
|
|
2005
|
+
pos = msg.find(':', pos); if (pos == std::string::npos) return {};
|
|
2006
|
+
++pos;
|
|
2007
|
+
while (pos < msg.size() && std::isspace((unsigned char)msg[pos])) ++pos;
|
|
2008
|
+
if (pos >= msg.size()) return {};
|
|
2009
|
+
if (msg[pos] == '"') {
|
|
2010
|
+
size_t s = pos + 1, e = s;
|
|
2011
|
+
while (e < msg.size() && msg[e] != '"') {
|
|
2012
|
+
if (msg[e] == '\\') ++e;
|
|
2013
|
+
++e;
|
|
2014
|
+
}
|
|
2015
|
+
return msg.substr(s, e - s);
|
|
2016
|
+
}
|
|
2017
|
+
size_t s = pos;
|
|
2018
|
+
while (pos < msg.size() && msg[pos] != ',' && msg[pos] != '}') ++pos;
|
|
2019
|
+
return msg.substr(s, pos - s);
|
|
2020
|
+
};
|
|
2021
|
+
|
|
2022
|
+
auto extractFirstParam = [&]() -> std::string {
|
|
2023
|
+
size_t pa = msg.find("\"params\"");
|
|
2024
|
+
if (pa == std::string::npos) return "null";
|
|
2025
|
+
size_t ar = msg.find('[', pa);
|
|
2026
|
+
if (ar == std::string::npos) return "null";
|
|
2027
|
+
size_t i = ar + 1;
|
|
2028
|
+
while (i < msg.size() && std::isspace((unsigned char)msg[i])) ++i;
|
|
2029
|
+
if (i >= msg.size() || msg[i] == ']') return "null";
|
|
2030
|
+
size_t start = i;
|
|
2031
|
+
if (msg[i] == '"') {
|
|
2032
|
+
++i;
|
|
2033
|
+
while (i < msg.size()) {
|
|
2034
|
+
if (msg[i] == '\\') { ++i; } else if (msg[i] == '"') { ++i; break; }
|
|
2035
|
+
++i;
|
|
2036
|
+
}
|
|
2037
|
+
return msg.substr(start, i - start);
|
|
2038
|
+
}
|
|
2039
|
+
int depth = 0;
|
|
2040
|
+
bool inStr = false, esc = false;
|
|
2041
|
+
while (i < msg.size()) {
|
|
2042
|
+
char c = msg[i];
|
|
2043
|
+
if (inStr) { if (esc) esc=false; else if (c=='\\') esc=true; else if (c=='"') inStr=false; }
|
|
2044
|
+
else {
|
|
2045
|
+
if (c=='"') inStr=true;
|
|
2046
|
+
else if (c=='{' || c=='[') ++depth;
|
|
2047
|
+
else if ((c=='}' || c==']') && depth==0) break;
|
|
2048
|
+
else if (c==',' && depth==0) break;
|
|
2049
|
+
else if (c=='}' || c==']') --depth;
|
|
2050
|
+
}
|
|
2051
|
+
++i;
|
|
2052
|
+
}
|
|
2053
|
+
return msg.substr(start, i - start);
|
|
2054
|
+
};
|
|
2055
|
+
|
|
2056
|
+
auto decodeStr = [](const std::string &s) -> std::string {
|
|
2057
|
+
if (s.size() < 2 || s.front() != '"' || s.back() != '"') return s;
|
|
2058
|
+
std::string out; out.reserve(s.size()-2);
|
|
2059
|
+
for (size_t i = 1; i+1 < s.size(); ++i) {
|
|
2060
|
+
if (s[i]=='\\' && i+2 < s.size()) {
|
|
2061
|
+
char n = s[++i];
|
|
2062
|
+
switch(n){ case '"': out+='"'; break; case '\\': out+='\\'; break;
|
|
2063
|
+
case 'n': out+='\n'; break; case 'r': out+='\r'; break;
|
|
2064
|
+
case 't': out+='\t'; break; default: out+=n; break; }
|
|
2065
|
+
} else { out += s[i]; }
|
|
2066
|
+
}
|
|
2067
|
+
return out;
|
|
2068
|
+
};
|
|
2069
|
+
|
|
2070
|
+
std::string id = getField("id");
|
|
2071
|
+
std::string method = getField("method");
|
|
2072
|
+
std::string result = "null";
|
|
2073
|
+
bool success = false;
|
|
2074
|
+
|
|
2075
|
+
// ── messageCallback (user-level handler) ──────────────────────────────
|
|
2076
|
+
if (pImpl->messageCallback) {
|
|
2077
|
+
pImpl->messageCallback(msg);
|
|
2078
|
+
if (msg.find("\"kind\"") != std::string::npos) {
|
|
2079
|
+
success = true;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// ── window.* ──────────────────────────────────────────────────────────
|
|
2084
|
+
if (!success && method.rfind("window.", 0) == 0) {
|
|
2085
|
+
std::string wm = method.substr(7);
|
|
2086
|
+
success = true;
|
|
2087
|
+
if (wm == "minimize") { if (pImpl->window) pImpl->window->minimize(); }
|
|
2088
|
+
else if (wm == "maximize") { if (pImpl->window) pImpl->window->maximize(); }
|
|
2089
|
+
else if (wm == "restore") { if (pImpl->window) pImpl->window->restore(); }
|
|
2090
|
+
else if (wm == "close") { if (pImpl->window) pImpl->window->close(); }
|
|
2091
|
+
else if (wm == "show") { if (pImpl->window) pImpl->window->show();
|
|
2092
|
+
else { pImpl->state.isHidden=false; pImpl->state.isVisible=true; } }
|
|
2093
|
+
else if (wm == "hide") { if (pImpl->window) pImpl->window->hide();
|
|
2094
|
+
else { pImpl->state.isHidden=true; pImpl->state.isVisible=false; } }
|
|
2095
|
+
else if (wm == "center") { if (pImpl->window) pImpl->window->center(); }
|
|
2096
|
+
else if (wm == "getSize") {
|
|
2097
|
+
int w=0,h=0; if (pImpl->window) pImpl->window->getSize(w,h);
|
|
2098
|
+
result = "{\"width\":" + std::to_string(w) + ",\"height\":" + std::to_string(h) + "}";
|
|
2099
|
+
}
|
|
2100
|
+
else if (wm == "getPosition") {
|
|
2101
|
+
int x=0,y=0; if (pImpl->window) pImpl->window->getPosition(x,y);
|
|
2102
|
+
result = "{\"x\":" + std::to_string(x) + ",\"y\":" + std::to_string(y) + "}";
|
|
2103
|
+
}
|
|
2104
|
+
else if (wm == "setSize") {
|
|
2105
|
+
size_t p1=msg.find('['), p2=msg.find(']');
|
|
2106
|
+
if (p1!=std::string::npos && p2!=std::string::npos) {
|
|
2107
|
+
int w=0,h=0; sscanf(msg.substr(p1+1,p2-p1-1).c_str(),"%d, %d",&w,&h);
|
|
2108
|
+
if (pImpl->window) pImpl->window->setSize(w,h);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
else if (wm == "setPosition") {
|
|
2112
|
+
size_t p1=msg.find('['), p2=msg.find(']');
|
|
2113
|
+
if (p1!=std::string::npos && p2!=std::string::npos) {
|
|
2114
|
+
int x=0,y=0; sscanf(msg.substr(p1+1,p2-p1-1).c_str(),"%d, %d",&x,&y);
|
|
2115
|
+
if (pImpl->window) pImpl->window->setPosition(x,y);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
else if (wm == "setTitle") {
|
|
2119
|
+
size_t p1=msg.find("[\""), p2=msg.find("\"]");
|
|
2120
|
+
if (p1!=std::string::npos && p2!=std::string::npos) {
|
|
2121
|
+
if (pImpl->window) pImpl->window->setTitle(msg.substr(p1+2,p2-p1-2));
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
else if (wm == "setFullscreen") {
|
|
2125
|
+
size_t p1=msg.find('['), p2=msg.find(']');
|
|
2126
|
+
if (p1!=std::string::npos && p2!=std::string::npos)
|
|
2127
|
+
if (pImpl->window) pImpl->window->setFullscreen(msg.find("true",p1)!=std::string::npos && msg.find("true",p1)<p2);
|
|
2128
|
+
}
|
|
2129
|
+
else if (wm == "setAlwaysOnTop") {
|
|
2130
|
+
size_t p1=msg.find('['), p2=msg.find(']');
|
|
2131
|
+
if (p1!=std::string::npos && p2!=std::string::npos)
|
|
2132
|
+
if (pImpl->window) pImpl->window->setAlwaysOnTop(msg.find("true",p1)!=std::string::npos && msg.find("true",p1)<p2);
|
|
2133
|
+
}
|
|
2134
|
+
else if (wm == "setResizable") {
|
|
2135
|
+
size_t p1=msg.find('['), p2=msg.find(']');
|
|
2136
|
+
if (p1!=std::string::npos && p2!=std::string::npos)
|
|
2137
|
+
if (pImpl->window) pImpl->window->setResizable(msg.find("true",p1)!=std::string::npos && msg.find("true",p1)<p2);
|
|
2138
|
+
}
|
|
2139
|
+
else if (wm == "isMaximized") { result = (pImpl->window && pImpl->window->isMaximized()) ? "true":"false"; }
|
|
2140
|
+
else if (wm == "isMinimized") { result = (pImpl->window && pImpl->window->isMinimized()) ? "true":"false"; }
|
|
2141
|
+
else if (wm == "isVisible") { result = (pImpl->window && pImpl->window->isVisible()) ? "true":"false"; }
|
|
2142
|
+
else { success = false; }
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
// ── browser.* ─────────────────────────────────────────────────────────
|
|
2146
|
+
if (!success && method.rfind("browser.", 0) == 0) {
|
|
2147
|
+
std::string bm = method.substr(8); success = true;
|
|
2148
|
+
if (bm == "navigate") {
|
|
2149
|
+
size_t p1=msg.find("[\""), p2=msg.find("\"]");
|
|
2150
|
+
if (p1!=std::string::npos && p2!=std::string::npos)
|
|
2151
|
+
webkit_web_view_load_uri(ctx->webView, msg.substr(p1+2,p2-p1-2).c_str());
|
|
2152
|
+
}
|
|
2153
|
+
else if (bm == "goBack") { webkit_web_view_go_back(ctx->webView); }
|
|
2154
|
+
else if (bm == "goForward") { webkit_web_view_go_forward(ctx->webView); }
|
|
2155
|
+
else if (bm == "reload") { webkit_web_view_reload(ctx->webView); }
|
|
2156
|
+
else if (bm == "stop") { webkit_web_view_stop_loading(ctx->webView); }
|
|
2157
|
+
else if (bm == "getUrl") { result = "\"" + pImpl->currentURL + "\""; }
|
|
2158
|
+
else if (bm == "getTitle") { result = "\"" + pImpl->currentTitle + "\""; }
|
|
2159
|
+
else if (bm == "canGoBack") { result = webkit_web_view_can_go_back(ctx->webView) ? "true":"false"; }
|
|
2160
|
+
else if (bm == "canGoForward") { result = webkit_web_view_can_go_forward(ctx->webView) ? "true":"false"; }
|
|
2161
|
+
else { success = false; }
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
// ── app.* ─────────────────────────────────────────────────────────────
|
|
2165
|
+
if (!success && method.rfind("app.", 0) == 0) {
|
|
2166
|
+
std::string am = method.substr(4); success = true;
|
|
2167
|
+
if (am == "quit") { gtk_main_quit(); }
|
|
2168
|
+
else { success = false; }
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
// ── fileDrop.* ────────────────────────────────────────────────────────
|
|
2172
|
+
if (!success && method.rfind("fileDrop.", 0) == 0) {
|
|
2173
|
+
std::string fm = method.substr(9); success = true;
|
|
2174
|
+
if (fm == "setEnabled") { size_t p1=msg.find('['),p2=msg.find(']');
|
|
2175
|
+
if(p1!=std::string::npos&&p2!=std::string::npos)
|
|
2176
|
+
pImpl->config.fileDrop = msg.find("true",p1)!=std::string::npos&&msg.find("true",p1)<p2; }
|
|
2177
|
+
else if (fm == "isEnabled") { result = pImpl->config.fileDrop ? "true":"false"; }
|
|
2178
|
+
else if (fm == "startDrag" || fm == "clearCallbacks") { /* no-op on Linux */ }
|
|
2179
|
+
else { success = false; }
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
// ── webview.* (custom C++ bindings) ───────────────────────────────────
|
|
2183
|
+
if (!success && method.rfind("webview.", 0) == 0) {
|
|
2184
|
+
std::string bn = method.substr(8);
|
|
2185
|
+
auto it = pImpl->bindings.find(bn);
|
|
2186
|
+
if (it != pImpl->bindings.end()) {
|
|
2187
|
+
try {
|
|
2188
|
+
result = it->second(decodeStr(extractFirstParam()));
|
|
2189
|
+
if (result.empty()) result = "null";
|
|
2190
|
+
success = true;
|
|
2191
|
+
} catch (...) { result = "null"; success = true; }
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
// ── connect.* (custom semantic bindings via bindConnect) ──────────────
|
|
2196
|
+
if (!success && method.rfind("connect.", 0) == 0) {
|
|
2197
|
+
std::string bn = method.substr(8);
|
|
2198
|
+
auto it = pImpl->bindings.find("connect." + bn);
|
|
2199
|
+
if (it == pImpl->bindings.end()) it = pImpl->bindings.find(bn);
|
|
2200
|
+
if (it != pImpl->bindings.end()) {
|
|
2201
|
+
try {
|
|
2202
|
+
result = it->second(decodeStr(extractFirstParam()));
|
|
2203
|
+
if (result.empty()) result = "null";
|
|
2204
|
+
success = true;
|
|
2205
|
+
} catch (...) { result = "null"; success = true; }
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
// ── send response back to JS ──────────────────────────────────────────
|
|
2210
|
+
if (!id.empty()) {
|
|
2211
|
+
std::string resp = "window.__response__(\"" + id + "\", " + result + ");";
|
|
2212
|
+
runJSResp(resp);
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
g_signal_connect(mgr, "script-message-received::plusui",
|
|
2216
|
+
G_CALLBACK(sMsgHandler), msgCtx);
|
|
2217
|
+
|
|
1964
2218
|
// GDK keyval → PlusUI KeyCode
|
|
1965
2219
|
auto gdkKeyToPlusUI = [](guint keyval) -> int {
|
|
1966
2220
|
if (keyval >= GDK_KEY_a && keyval <= GDK_KEY_z)
|
|
@@ -2102,423 +2356,423 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
2102
2356
|
|
|
2103
2357
|
return win;
|
|
2104
2358
|
}
|
|
2105
|
-
|
|
2106
|
-
TrayManager &Window::tray() { return *pImpl->trayManager; }
|
|
2107
|
-
|
|
2108
|
-
void Window::setWindow(std::shared_ptr<Window> win) {
|
|
2109
|
-
pImpl->window = win;
|
|
2110
|
-
if (!win)
|
|
2111
|
-
return;
|
|
2112
|
-
|
|
2113
|
-
std::weak_ptr<Impl> weak_pImpl = pImpl;
|
|
2114
|
-
win->onResize([weak_pImpl](int w, int h) {
|
|
2115
|
-
if (auto pImpl = weak_pImpl.lock()) {
|
|
2116
|
-
#ifdef _WIN32
|
|
2117
|
-
if (pImpl->controller) {
|
|
2118
|
-
RECT bounds = {0, 0, w, h};
|
|
2119
|
-
pImpl->controller->put_Bounds(bounds);
|
|
2120
|
-
}
|
|
2121
|
-
#elif defined(__APPLE__)
|
|
2122
|
-
if (pImpl->wkWebView) {
|
|
2123
|
-
NSView *view = (__bridge NSView *)pImpl->wkWebView;
|
|
2124
|
-
NSView *parent = [view superview];
|
|
2125
|
-
if (parent) {
|
|
2126
|
-
[view setFrame:[parent bounds]];
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
#else
|
|
2130
|
-
if (pImpl->gtkWebView) {
|
|
2131
|
-
// GTK usually handles this if added to a container with expand=TRUE
|
|
2132
|
-
// but we can ensure it here if it's a fixed layout parent
|
|
2133
|
-
gtk_widget_set_size_request(GTK_WIDGET(pImpl->gtkWebView), w, h);
|
|
2134
|
-
}
|
|
2135
|
-
#endif
|
|
2136
|
-
}
|
|
2137
|
-
});
|
|
2138
|
-
|
|
2139
|
-
// Trigger initial resize
|
|
2140
|
-
int w, h;
|
|
2141
|
-
win->getSize(w, h);
|
|
2142
|
-
#ifdef _WIN32
|
|
2143
|
-
if (pImpl->controller) {
|
|
2144
|
-
RECT bounds = {0, 0, w, h};
|
|
2145
|
-
pImpl->controller->put_Bounds(bounds);
|
|
2146
|
-
}
|
|
2147
|
-
#endif
|
|
2148
|
-
}
|
|
2149
|
-
|
|
2150
|
-
void Window::navigate(const std::string &url) {
|
|
2151
|
-
pImpl->currentURL = url;
|
|
2152
|
-
pImpl->loading = true;
|
|
2153
|
-
|
|
2154
|
-
#ifdef _WIN32
|
|
2155
|
-
if (pImpl->ready && pImpl->webview) {
|
|
2156
|
-
pImpl->webview->Navigate(std::wstring(url.begin(), url.end()).c_str());
|
|
2157
|
-
} else {
|
|
2158
|
-
pImpl->pendingNavigation = url;
|
|
2159
|
-
}
|
|
2160
|
-
#elif defined(__APPLE__)
|
|
2161
|
-
if (pImpl->wkWebView) {
|
|
2162
|
-
NSURL *nsurl =
|
|
2163
|
-
[NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]];
|
|
2164
|
-
NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
|
|
2165
|
-
[pImpl->wkWebView loadRequest:request];
|
|
2166
|
-
}
|
|
2167
|
-
#else
|
|
2168
|
-
if (pImpl->gtkWebView) {
|
|
2169
|
-
webkit_web_view_load_uri(pImpl->gtkWebView, url.c_str());
|
|
2170
|
-
}
|
|
2171
|
-
#endif
|
|
2172
|
-
}
|
|
2173
|
-
|
|
2174
|
-
void Window::onMessage(MessageCallback callback) {
|
|
2175
|
-
pImpl->messageCallback = callback;
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
void Window::postMessage(const std::string &message) {
|
|
2179
|
-
#ifdef _WIN32
|
|
2180
|
-
if (pImpl->webview) {
|
|
2181
|
-
std::wstring wmsg(message.begin(), message.end());
|
|
2182
|
-
pImpl->webview->PostWebMessageAsJson(wmsg.c_str());
|
|
2183
|
-
}
|
|
2184
|
-
#elif defined(__APPLE__)
|
|
2185
|
-
// TODO: formatting for Apple
|
|
2186
|
-
#endif
|
|
2187
|
-
}
|
|
2188
|
-
|
|
2189
|
-
void Window::onFileDrop(FileDropCallback callback) {
|
|
2190
|
-
pImpl->fileDropCallback = callback;
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
|
-
void Window::bind(const std::string &name, JSCallback callback) {
|
|
2194
|
-
pImpl->bindings[name] = callback;
|
|
2195
|
-
|
|
2196
|
-
std::string bridgeScript =
|
|
2197
|
-
"window." + name +
|
|
2198
|
-
R"( = function(...args) {
|
|
2199
|
-
if (window.plusui && typeof window.plusui.invoke === 'function') {
|
|
2200
|
-
const payload = args.length === 0 ? null : (args.length === 1 ? args[0] : args);
|
|
2201
|
-
return window.plusui.invoke('webview.)" +
|
|
2202
|
-
name +
|
|
2203
|
-
R"(', [payload]);
|
|
2204
|
-
}
|
|
2205
|
-
if (window.__invoke__) {
|
|
2206
|
-
const payload = args.length === 0 ? null : (args.length === 1 ? args[0] : args);
|
|
2207
|
-
return window.__invoke__('webview.)" +
|
|
2208
|
-
name +
|
|
2209
|
-
R"(', [payload]);
|
|
2210
|
-
}
|
|
2211
|
-
return Promise.resolve(null);
|
|
2212
|
-
};)";
|
|
2213
|
-
|
|
2214
|
-
executeScript(bridgeScript);
|
|
2215
|
-
}
|
|
2216
|
-
|
|
2217
|
-
void Window::unbind(const std::string &name) {
|
|
2218
|
-
pImpl->bindings.erase(name);
|
|
2219
|
-
executeScript("delete window." + name + ";");
|
|
2220
|
-
}
|
|
2221
|
-
|
|
2222
|
-
void Window::loadURL(const std::string &url) { navigate(url); }
|
|
2223
|
-
|
|
2224
|
-
void Window::loadHTML(const std::string &html) {
|
|
2225
|
-
loadHTML(html, "about:blank");
|
|
2226
|
-
}
|
|
2227
|
-
|
|
2228
|
-
void Window::loadHTML(const std::string &html, const std::string &baseURL) {
|
|
2229
|
-
pImpl->loading = true;
|
|
2230
|
-
|
|
2231
|
-
#ifdef _WIN32
|
|
2232
|
-
(void)baseURL; // Not used on Windows
|
|
2233
|
-
if (pImpl->ready && pImpl->webview) {
|
|
2234
|
-
pImpl->webview->NavigateToString(
|
|
2235
|
-
std::wstring(html.begin(), html.end()).c_str());
|
|
2236
|
-
} else {
|
|
2237
|
-
pImpl->pendingHTML = html;
|
|
2238
|
-
}
|
|
2239
|
-
#elif defined(__APPLE__)
|
|
2240
|
-
if (pImpl->wkWebView) {
|
|
2241
|
-
NSString *htmlString = [NSString stringWithUTF8String:html.c_str()];
|
|
2242
|
-
NSURL *base =
|
|
2243
|
-
[NSURL URLWithString:[NSString stringWithUTF8String:baseURL.c_str()]];
|
|
2244
|
-
[pImpl->wkWebView loadHTMLString:htmlString baseURL:base];
|
|
2245
|
-
}
|
|
2246
|
-
#else
|
|
2247
|
-
if (pImpl->gtkWebView) {
|
|
2248
|
-
webkit_web_view_load_html(pImpl->gtkWebView, html.c_str(), baseURL.c_str());
|
|
2249
|
-
}
|
|
2250
|
-
#endif
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
void Window::loadFile(const std::string &filePath) {
|
|
2254
|
-
std::ifstream file(filePath);
|
|
2255
|
-
if (!file) {
|
|
2256
|
-
std::cerr << "PlusUI: Failed to open file: " << filePath << std::endl;
|
|
2257
|
-
return;
|
|
2258
|
-
}
|
|
2259
|
-
|
|
2260
|
-
std::stringstream buffer;
|
|
2261
|
-
buffer << file.rdbuf();
|
|
2262
|
-
|
|
2263
|
-
// Use file:// URL as base for relative paths
|
|
2264
|
-
std::string baseURL =
|
|
2265
|
-
"file://" + filePath.substr(0, filePath.find_last_of("/\\") + 1);
|
|
2266
|
-
loadHTML(buffer.str(), baseURL);
|
|
2267
|
-
}
|
|
2268
|
-
|
|
2269
|
-
void Window::reload() {
|
|
2270
|
-
#ifdef _WIN32
|
|
2271
|
-
if (pImpl->webview) {
|
|
2272
|
-
pImpl->webview->Reload();
|
|
2273
|
-
}
|
|
2274
|
-
#elif defined(__APPLE__)
|
|
2275
|
-
if (pImpl->wkWebView) {
|
|
2276
|
-
[pImpl->wkWebView reload];
|
|
2277
|
-
}
|
|
2278
|
-
#else
|
|
2279
|
-
if (pImpl->gtkWebView) {
|
|
2280
|
-
webkit_web_view_reload(pImpl->gtkWebView);
|
|
2281
|
-
}
|
|
2282
|
-
#endif
|
|
2283
|
-
}
|
|
2284
|
-
|
|
2285
|
-
void Window::stop() {
|
|
2286
|
-
#ifdef _WIN32
|
|
2287
|
-
if (pImpl->webview) {
|
|
2288
|
-
pImpl->webview->Stop();
|
|
2289
|
-
}
|
|
2290
|
-
#elif defined(__APPLE__)
|
|
2291
|
-
if (pImpl->wkWebView) {
|
|
2292
|
-
[pImpl->wkWebView stopLoading];
|
|
2293
|
-
}
|
|
2294
|
-
#else
|
|
2295
|
-
if (pImpl->gtkWebView) {
|
|
2296
|
-
webkit_web_view_stop_loading(pImpl->gtkWebView);
|
|
2297
|
-
}
|
|
2298
|
-
#endif
|
|
2299
|
-
}
|
|
2300
|
-
|
|
2301
|
-
void Window::goBack() {
|
|
2302
|
-
#ifdef _WIN32
|
|
2303
|
-
if (pImpl->webview) {
|
|
2304
|
-
pImpl->webview->GoBack();
|
|
2305
|
-
}
|
|
2306
|
-
#elif defined(__APPLE__)
|
|
2307
|
-
if (pImpl->wkWebView) {
|
|
2308
|
-
[pImpl->wkWebView goBack];
|
|
2309
|
-
}
|
|
2310
|
-
#else
|
|
2311
|
-
if (pImpl->gtkWebView) {
|
|
2312
|
-
webkit_web_view_go_back(pImpl->gtkWebView);
|
|
2313
|
-
}
|
|
2314
|
-
#endif
|
|
2315
|
-
}
|
|
2316
|
-
|
|
2317
|
-
void Window::goForward() {
|
|
2318
|
-
#ifdef _WIN32
|
|
2319
|
-
if (pImpl->webview) {
|
|
2320
|
-
pImpl->webview->GoForward();
|
|
2321
|
-
}
|
|
2322
|
-
#elif defined(__APPLE__)
|
|
2323
|
-
if (pImpl->wkWebView) {
|
|
2324
|
-
[pImpl->wkWebView goForward];
|
|
2325
|
-
}
|
|
2326
|
-
#else
|
|
2327
|
-
if (pImpl->gtkWebView) {
|
|
2328
|
-
webkit_web_view_go_forward(pImpl->gtkWebView);
|
|
2329
|
-
}
|
|
2330
|
-
#endif
|
|
2331
|
-
}
|
|
2332
|
-
|
|
2333
|
-
bool Window::canGoBack() const {
|
|
2334
|
-
#ifdef _WIN32
|
|
2335
|
-
if (pImpl->webview) {
|
|
2336
|
-
BOOL canGoBack;
|
|
2337
|
-
pImpl->webview->get_CanGoBack(&canGoBack);
|
|
2338
|
-
return canGoBack;
|
|
2339
|
-
}
|
|
2340
|
-
#elif defined(__APPLE__)
|
|
2341
|
-
if (pImpl->wkWebView) {
|
|
2342
|
-
return [pImpl->wkWebView canGoBack];
|
|
2343
|
-
}
|
|
2344
|
-
#else
|
|
2345
|
-
if (pImpl->gtkWebView) {
|
|
2346
|
-
return webkit_web_view_can_go_back(pImpl->gtkWebView);
|
|
2347
|
-
}
|
|
2348
|
-
#endif
|
|
2349
|
-
return false;
|
|
2350
|
-
}
|
|
2351
|
-
|
|
2352
|
-
bool Window::canGoForward() const {
|
|
2353
|
-
#ifdef _WIN32
|
|
2354
|
-
if (pImpl->webview) {
|
|
2355
|
-
BOOL canGoForward;
|
|
2356
|
-
pImpl->webview->get_CanGoForward(&canGoForward);
|
|
2357
|
-
return canGoForward;
|
|
2358
|
-
}
|
|
2359
|
-
#elif defined(__APPLE__)
|
|
2360
|
-
if (pImpl->wkWebView) {
|
|
2361
|
-
return [pImpl->wkWebView canGoForward];
|
|
2362
|
-
}
|
|
2363
|
-
#else
|
|
2364
|
-
if (pImpl->gtkWebView) {
|
|
2365
|
-
return webkit_web_view_can_go_forward(pImpl->gtkWebView);
|
|
2366
|
-
}
|
|
2367
|
-
#endif
|
|
2368
|
-
return false;
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
|
-
void Window::executeScript(const std::string &script) {
|
|
2372
|
-
#ifdef _WIN32
|
|
2373
|
-
if (pImpl->ready && pImpl->webview) {
|
|
2374
|
-
pImpl->webview->ExecuteScript(
|
|
2375
|
-
std::wstring(script.begin(), script.end()).c_str(), nullptr);
|
|
2376
|
-
} else {
|
|
2377
|
-
pImpl->pendingScripts.push_back(script);
|
|
2378
|
-
}
|
|
2379
|
-
#elif defined(__APPLE__)
|
|
2380
|
-
if (pImpl->wkWebView) {
|
|
2381
|
-
NSString *js = [NSString stringWithUTF8String:script.c_str()];
|
|
2382
|
-
[pImpl->wkWebView evaluateJavaScript:js completionHandler:nil];
|
|
2383
|
-
}
|
|
2384
|
-
#else
|
|
2385
|
-
if (pImpl->gtkWebView) {
|
|
2386
|
-
webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
|
|
2387
|
-
nullptr, nullptr);
|
|
2388
|
-
}
|
|
2389
|
-
#endif
|
|
2390
|
-
}
|
|
2391
|
-
|
|
2392
|
-
void Window::executeScript(const std::string &script,
|
|
2393
|
-
std::function<void(const std::string &)> callback) {
|
|
2394
|
-
#ifdef _WIN32
|
|
2395
|
-
if (pImpl->webview) {
|
|
2396
|
-
pImpl->webview->ExecuteScript(
|
|
2397
|
-
std::wstring(script.begin(), script.end()).c_str(),
|
|
2398
|
-
Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
|
|
2399
|
-
[callback](HRESULT error, LPCWSTR result) -> HRESULT {
|
|
2400
|
-
(void)error; // Suppress unused warning
|
|
2401
|
-
if (result && callback) {
|
|
2402
|
-
std::wstring wstr(result);
|
|
2403
|
-
callback(std::string(wstr.begin(), wstr.end()));
|
|
2404
|
-
}
|
|
2405
|
-
return S_OK;
|
|
2406
|
-
})
|
|
2407
|
-
.Get());
|
|
2408
|
-
}
|
|
2409
|
-
#elif defined(__APPLE__)
|
|
2410
|
-
if (pImpl->wkWebView) {
|
|
2411
|
-
NSString *js = [NSString stringWithUTF8String:script.c_str()];
|
|
2412
|
-
[pImpl->wkWebView evaluateJavaScript:js
|
|
2413
|
-
completionHandler:^(id result, NSError *error) {
|
|
2414
|
-
if (result && callback) {
|
|
2415
|
-
NSString *resultStr =
|
|
2416
|
-
[NSString stringWithFormat:@"%@", result];
|
|
2417
|
-
callback([resultStr UTF8String]);
|
|
2418
|
-
}
|
|
2419
|
-
}];
|
|
2420
|
-
}
|
|
2421
|
-
#else
|
|
2422
|
-
if (pImpl->gtkWebView) {
|
|
2423
|
-
// GTK WebKit callback handling would go here
|
|
2424
|
-
webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
|
|
2425
|
-
nullptr, nullptr);
|
|
2426
|
-
}
|
|
2427
|
-
#endif
|
|
2428
|
-
}
|
|
2429
|
-
|
|
2430
|
-
void Window::openDevTools() {
|
|
2431
|
-
#ifdef _WIN32
|
|
2432
|
-
if (pImpl->webview) {
|
|
2433
|
-
pImpl->webview->OpenDevToolsWindow();
|
|
2434
|
-
}
|
|
2435
|
-
#elif defined(__APPLE__)
|
|
2436
|
-
// macOS: Dev tools open in Safari's Web Inspector
|
|
2437
|
-
#else
|
|
2438
|
-
if (pImpl->gtkWebView) {
|
|
2439
|
-
WebKitWebInspector *inspector =
|
|
2440
|
-
webkit_web_view_get_inspector(pImpl->gtkWebView);
|
|
2441
|
-
webkit_web_inspector_show(inspector);
|
|
2442
|
-
}
|
|
2443
|
-
#endif
|
|
2444
|
-
}
|
|
2445
|
-
|
|
2446
|
-
void Window::closeDevTools() {
|
|
2447
|
-
#ifdef _WIN32
|
|
2448
|
-
// WebView2 doesn't have explicit close
|
|
2449
|
-
#elif defined(__APPLE__)
|
|
2450
|
-
// macOS: Handled by Web Inspector
|
|
2451
|
-
#else
|
|
2452
|
-
if (pImpl->gtkWebView) {
|
|
2453
|
-
WebKitWebInspector *inspector =
|
|
2454
|
-
webkit_web_view_get_inspector(pImpl->gtkWebView);
|
|
2455
|
-
webkit_web_inspector_close(inspector);
|
|
2456
|
-
}
|
|
2457
|
-
#endif
|
|
2458
|
-
}
|
|
2459
|
-
|
|
2460
|
-
void Window::setUserAgent(const std::string &userAgent) {
|
|
2461
|
-
pImpl->userAgent = userAgent;
|
|
2462
|
-
|
|
2463
|
-
#ifdef _WIN32
|
|
2464
|
-
if (pImpl->webview) {
|
|
2465
|
-
ComPtr<ICoreWebView2Settings> settings;
|
|
2466
|
-
pImpl->webview->get_Settings(&settings);
|
|
2467
|
-
// WebView2 user agent requires settings2 interface
|
|
2468
|
-
}
|
|
2469
|
-
#elif defined(__APPLE__)
|
|
2470
|
-
if (pImpl->wkWebView) {
|
|
2471
|
-
[pImpl->wkWebView
|
|
2472
|
-
setCustomUserAgent:[NSString stringWithUTF8String:userAgent.c_str()]];
|
|
2473
|
-
}
|
|
2474
|
-
#else
|
|
2475
|
-
if (pImpl->gtkWebView) {
|
|
2476
|
-
WebKitSettings *settings = webkit_web_view_get_settings(pImpl->gtkWebView);
|
|
2477
|
-
webkit_settings_set_user_agent(settings, userAgent.c_str());
|
|
2478
|
-
}
|
|
2479
|
-
#endif
|
|
2480
|
-
}
|
|
2481
|
-
|
|
2482
|
-
std::string Window::getUserAgent() const { return pImpl->userAgent; }
|
|
2483
|
-
|
|
2484
|
-
void Window::setZoom(double factor) {
|
|
2485
|
-
pImpl->zoom = factor;
|
|
2486
|
-
|
|
2487
|
-
#ifdef _WIN32
|
|
2488
|
-
if (pImpl->controller) {
|
|
2489
|
-
pImpl->controller->put_ZoomFactor(factor);
|
|
2490
|
-
}
|
|
2491
|
-
#elif defined(__APPLE__)
|
|
2492
|
-
if (pImpl->wkWebView) {
|
|
2493
|
-
[pImpl->wkWebView setPageZoom:factor];
|
|
2494
|
-
}
|
|
2495
|
-
#else
|
|
2496
|
-
if (pImpl->gtkWebView) {
|
|
2497
|
-
webkit_web_view_set_zoom_level(pImpl->gtkWebView, factor);
|
|
2498
|
-
}
|
|
2499
|
-
#endif
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
double Window::getZoom() const { return pImpl->zoom; }
|
|
2503
|
-
|
|
2504
|
-
void Window::injectCSS(const std::string &css) {
|
|
2505
|
-
std::string script = R"(
|
|
2506
|
-
(function() {
|
|
2507
|
-
const style = document.createElement('style');
|
|
2508
|
-
style.id = 'plusui-injected-css-' + Date.now();
|
|
2509
|
-
style.textContent = `)" +
|
|
2510
|
-
css + R"(`;
|
|
2511
|
-
if (document.head) {
|
|
2512
|
-
document.head.appendChild(style);
|
|
2513
|
-
} else {
|
|
2514
|
-
document.documentElement.appendChild(style);
|
|
2515
|
-
}
|
|
2516
|
-
})();
|
|
2517
|
-
)";
|
|
2518
|
-
|
|
2519
|
-
executeScript(script);
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2359
|
+
|
|
2360
|
+
TrayManager &Window::tray() { return *pImpl->trayManager; }
|
|
2361
|
+
|
|
2362
|
+
void Window::setWindow(std::shared_ptr<Window> win) {
|
|
2363
|
+
pImpl->window = win;
|
|
2364
|
+
if (!win)
|
|
2365
|
+
return;
|
|
2366
|
+
|
|
2367
|
+
std::weak_ptr<Impl> weak_pImpl = pImpl;
|
|
2368
|
+
win->onResize([weak_pImpl](int w, int h) {
|
|
2369
|
+
if (auto pImpl = weak_pImpl.lock()) {
|
|
2370
|
+
#ifdef _WIN32
|
|
2371
|
+
if (pImpl->controller) {
|
|
2372
|
+
RECT bounds = {0, 0, w, h};
|
|
2373
|
+
pImpl->controller->put_Bounds(bounds);
|
|
2374
|
+
}
|
|
2375
|
+
#elif defined(__APPLE__)
|
|
2376
|
+
if (pImpl->wkWebView) {
|
|
2377
|
+
NSView *view = (__bridge NSView *)pImpl->wkWebView;
|
|
2378
|
+
NSView *parent = [view superview];
|
|
2379
|
+
if (parent) {
|
|
2380
|
+
[view setFrame:[parent bounds]];
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
#else
|
|
2384
|
+
if (pImpl->gtkWebView) {
|
|
2385
|
+
// GTK usually handles this if added to a container with expand=TRUE
|
|
2386
|
+
// but we can ensure it here if it's a fixed layout parent
|
|
2387
|
+
gtk_widget_set_size_request(GTK_WIDGET(pImpl->gtkWebView), w, h);
|
|
2388
|
+
}
|
|
2389
|
+
#endif
|
|
2390
|
+
}
|
|
2391
|
+
});
|
|
2392
|
+
|
|
2393
|
+
// Trigger initial resize
|
|
2394
|
+
int w, h;
|
|
2395
|
+
win->getSize(w, h);
|
|
2396
|
+
#ifdef _WIN32
|
|
2397
|
+
if (pImpl->controller) {
|
|
2398
|
+
RECT bounds = {0, 0, w, h};
|
|
2399
|
+
pImpl->controller->put_Bounds(bounds);
|
|
2400
|
+
}
|
|
2401
|
+
#endif
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
void Window::navigate(const std::string &url) {
|
|
2405
|
+
pImpl->currentURL = url;
|
|
2406
|
+
pImpl->loading = true;
|
|
2407
|
+
|
|
2408
|
+
#ifdef _WIN32
|
|
2409
|
+
if (pImpl->ready && pImpl->webview) {
|
|
2410
|
+
pImpl->webview->Navigate(std::wstring(url.begin(), url.end()).c_str());
|
|
2411
|
+
} else {
|
|
2412
|
+
pImpl->pendingNavigation = url;
|
|
2413
|
+
}
|
|
2414
|
+
#elif defined(__APPLE__)
|
|
2415
|
+
if (pImpl->wkWebView) {
|
|
2416
|
+
NSURL *nsurl =
|
|
2417
|
+
[NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]];
|
|
2418
|
+
NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
|
|
2419
|
+
[pImpl->wkWebView loadRequest:request];
|
|
2420
|
+
}
|
|
2421
|
+
#else
|
|
2422
|
+
if (pImpl->gtkWebView) {
|
|
2423
|
+
webkit_web_view_load_uri(pImpl->gtkWebView, url.c_str());
|
|
2424
|
+
}
|
|
2425
|
+
#endif
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
void Window::onMessage(MessageCallback callback) {
|
|
2429
|
+
pImpl->messageCallback = callback;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
void Window::postMessage(const std::string &message) {
|
|
2433
|
+
#ifdef _WIN32
|
|
2434
|
+
if (pImpl->webview) {
|
|
2435
|
+
std::wstring wmsg(message.begin(), message.end());
|
|
2436
|
+
pImpl->webview->PostWebMessageAsJson(wmsg.c_str());
|
|
2437
|
+
}
|
|
2438
|
+
#elif defined(__APPLE__)
|
|
2439
|
+
// TODO: formatting for Apple
|
|
2440
|
+
#endif
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
void Window::onFileDrop(FileDropCallback callback) {
|
|
2444
|
+
pImpl->fileDropCallback = callback;
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
void Window::bind(const std::string &name, JSCallback callback) {
|
|
2448
|
+
pImpl->bindings[name] = callback;
|
|
2449
|
+
|
|
2450
|
+
std::string bridgeScript =
|
|
2451
|
+
"window." + name +
|
|
2452
|
+
R"( = function(...args) {
|
|
2453
|
+
if (window.plusui && typeof window.plusui.invoke === 'function') {
|
|
2454
|
+
const payload = args.length === 0 ? null : (args.length === 1 ? args[0] : args);
|
|
2455
|
+
return window.plusui.invoke('webview.)" +
|
|
2456
|
+
name +
|
|
2457
|
+
R"(', [payload]);
|
|
2458
|
+
}
|
|
2459
|
+
if (window.__invoke__) {
|
|
2460
|
+
const payload = args.length === 0 ? null : (args.length === 1 ? args[0] : args);
|
|
2461
|
+
return window.__invoke__('webview.)" +
|
|
2462
|
+
name +
|
|
2463
|
+
R"(', [payload]);
|
|
2464
|
+
}
|
|
2465
|
+
return Promise.resolve(null);
|
|
2466
|
+
};)";
|
|
2467
|
+
|
|
2468
|
+
executeScript(bridgeScript);
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
void Window::unbind(const std::string &name) {
|
|
2472
|
+
pImpl->bindings.erase(name);
|
|
2473
|
+
executeScript("delete window." + name + ";");
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
void Window::loadURL(const std::string &url) { navigate(url); }
|
|
2477
|
+
|
|
2478
|
+
void Window::loadHTML(const std::string &html) {
|
|
2479
|
+
loadHTML(html, "about:blank");
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
void Window::loadHTML(const std::string &html, const std::string &baseURL) {
|
|
2483
|
+
pImpl->loading = true;
|
|
2484
|
+
|
|
2485
|
+
#ifdef _WIN32
|
|
2486
|
+
(void)baseURL; // Not used on Windows
|
|
2487
|
+
if (pImpl->ready && pImpl->webview) {
|
|
2488
|
+
pImpl->webview->NavigateToString(
|
|
2489
|
+
std::wstring(html.begin(), html.end()).c_str());
|
|
2490
|
+
} else {
|
|
2491
|
+
pImpl->pendingHTML = html;
|
|
2492
|
+
}
|
|
2493
|
+
#elif defined(__APPLE__)
|
|
2494
|
+
if (pImpl->wkWebView) {
|
|
2495
|
+
NSString *htmlString = [NSString stringWithUTF8String:html.c_str()];
|
|
2496
|
+
NSURL *base =
|
|
2497
|
+
[NSURL URLWithString:[NSString stringWithUTF8String:baseURL.c_str()]];
|
|
2498
|
+
[pImpl->wkWebView loadHTMLString:htmlString baseURL:base];
|
|
2499
|
+
}
|
|
2500
|
+
#else
|
|
2501
|
+
if (pImpl->gtkWebView) {
|
|
2502
|
+
webkit_web_view_load_html(pImpl->gtkWebView, html.c_str(), baseURL.c_str());
|
|
2503
|
+
}
|
|
2504
|
+
#endif
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
void Window::loadFile(const std::string &filePath) {
|
|
2508
|
+
std::ifstream file(filePath);
|
|
2509
|
+
if (!file) {
|
|
2510
|
+
std::cerr << "PlusUI: Failed to open file: " << filePath << std::endl;
|
|
2511
|
+
return;
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
std::stringstream buffer;
|
|
2515
|
+
buffer << file.rdbuf();
|
|
2516
|
+
|
|
2517
|
+
// Use file:// URL as base for relative paths
|
|
2518
|
+
std::string baseURL =
|
|
2519
|
+
"file://" + filePath.substr(0, filePath.find_last_of("/\\") + 1);
|
|
2520
|
+
loadHTML(buffer.str(), baseURL);
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
void Window::reload() {
|
|
2524
|
+
#ifdef _WIN32
|
|
2525
|
+
if (pImpl->webview) {
|
|
2526
|
+
pImpl->webview->Reload();
|
|
2527
|
+
}
|
|
2528
|
+
#elif defined(__APPLE__)
|
|
2529
|
+
if (pImpl->wkWebView) {
|
|
2530
|
+
[pImpl->wkWebView reload];
|
|
2531
|
+
}
|
|
2532
|
+
#else
|
|
2533
|
+
if (pImpl->gtkWebView) {
|
|
2534
|
+
webkit_web_view_reload(pImpl->gtkWebView);
|
|
2535
|
+
}
|
|
2536
|
+
#endif
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
void Window::stop() {
|
|
2540
|
+
#ifdef _WIN32
|
|
2541
|
+
if (pImpl->webview) {
|
|
2542
|
+
pImpl->webview->Stop();
|
|
2543
|
+
}
|
|
2544
|
+
#elif defined(__APPLE__)
|
|
2545
|
+
if (pImpl->wkWebView) {
|
|
2546
|
+
[pImpl->wkWebView stopLoading];
|
|
2547
|
+
}
|
|
2548
|
+
#else
|
|
2549
|
+
if (pImpl->gtkWebView) {
|
|
2550
|
+
webkit_web_view_stop_loading(pImpl->gtkWebView);
|
|
2551
|
+
}
|
|
2552
|
+
#endif
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
void Window::goBack() {
|
|
2556
|
+
#ifdef _WIN32
|
|
2557
|
+
if (pImpl->webview) {
|
|
2558
|
+
pImpl->webview->GoBack();
|
|
2559
|
+
}
|
|
2560
|
+
#elif defined(__APPLE__)
|
|
2561
|
+
if (pImpl->wkWebView) {
|
|
2562
|
+
[pImpl->wkWebView goBack];
|
|
2563
|
+
}
|
|
2564
|
+
#else
|
|
2565
|
+
if (pImpl->gtkWebView) {
|
|
2566
|
+
webkit_web_view_go_back(pImpl->gtkWebView);
|
|
2567
|
+
}
|
|
2568
|
+
#endif
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
void Window::goForward() {
|
|
2572
|
+
#ifdef _WIN32
|
|
2573
|
+
if (pImpl->webview) {
|
|
2574
|
+
pImpl->webview->GoForward();
|
|
2575
|
+
}
|
|
2576
|
+
#elif defined(__APPLE__)
|
|
2577
|
+
if (pImpl->wkWebView) {
|
|
2578
|
+
[pImpl->wkWebView goForward];
|
|
2579
|
+
}
|
|
2580
|
+
#else
|
|
2581
|
+
if (pImpl->gtkWebView) {
|
|
2582
|
+
webkit_web_view_go_forward(pImpl->gtkWebView);
|
|
2583
|
+
}
|
|
2584
|
+
#endif
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
bool Window::canGoBack() const {
|
|
2588
|
+
#ifdef _WIN32
|
|
2589
|
+
if (pImpl->webview) {
|
|
2590
|
+
BOOL canGoBack;
|
|
2591
|
+
pImpl->webview->get_CanGoBack(&canGoBack);
|
|
2592
|
+
return canGoBack;
|
|
2593
|
+
}
|
|
2594
|
+
#elif defined(__APPLE__)
|
|
2595
|
+
if (pImpl->wkWebView) {
|
|
2596
|
+
return [pImpl->wkWebView canGoBack];
|
|
2597
|
+
}
|
|
2598
|
+
#else
|
|
2599
|
+
if (pImpl->gtkWebView) {
|
|
2600
|
+
return webkit_web_view_can_go_back(pImpl->gtkWebView);
|
|
2601
|
+
}
|
|
2602
|
+
#endif
|
|
2603
|
+
return false;
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
bool Window::canGoForward() const {
|
|
2607
|
+
#ifdef _WIN32
|
|
2608
|
+
if (pImpl->webview) {
|
|
2609
|
+
BOOL canGoForward;
|
|
2610
|
+
pImpl->webview->get_CanGoForward(&canGoForward);
|
|
2611
|
+
return canGoForward;
|
|
2612
|
+
}
|
|
2613
|
+
#elif defined(__APPLE__)
|
|
2614
|
+
if (pImpl->wkWebView) {
|
|
2615
|
+
return [pImpl->wkWebView canGoForward];
|
|
2616
|
+
}
|
|
2617
|
+
#else
|
|
2618
|
+
if (pImpl->gtkWebView) {
|
|
2619
|
+
return webkit_web_view_can_go_forward(pImpl->gtkWebView);
|
|
2620
|
+
}
|
|
2621
|
+
#endif
|
|
2622
|
+
return false;
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
void Window::executeScript(const std::string &script) {
|
|
2626
|
+
#ifdef _WIN32
|
|
2627
|
+
if (pImpl->ready && pImpl->webview) {
|
|
2628
|
+
pImpl->webview->ExecuteScript(
|
|
2629
|
+
std::wstring(script.begin(), script.end()).c_str(), nullptr);
|
|
2630
|
+
} else {
|
|
2631
|
+
pImpl->pendingScripts.push_back(script);
|
|
2632
|
+
}
|
|
2633
|
+
#elif defined(__APPLE__)
|
|
2634
|
+
if (pImpl->wkWebView) {
|
|
2635
|
+
NSString *js = [NSString stringWithUTF8String:script.c_str()];
|
|
2636
|
+
[pImpl->wkWebView evaluateJavaScript:js completionHandler:nil];
|
|
2637
|
+
}
|
|
2638
|
+
#else
|
|
2639
|
+
if (pImpl->gtkWebView) {
|
|
2640
|
+
webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
|
|
2641
|
+
nullptr, nullptr);
|
|
2642
|
+
}
|
|
2643
|
+
#endif
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
void Window::executeScript(const std::string &script,
|
|
2647
|
+
std::function<void(const std::string &)> callback) {
|
|
2648
|
+
#ifdef _WIN32
|
|
2649
|
+
if (pImpl->webview) {
|
|
2650
|
+
pImpl->webview->ExecuteScript(
|
|
2651
|
+
std::wstring(script.begin(), script.end()).c_str(),
|
|
2652
|
+
Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
|
|
2653
|
+
[callback](HRESULT error, LPCWSTR result) -> HRESULT {
|
|
2654
|
+
(void)error; // Suppress unused warning
|
|
2655
|
+
if (result && callback) {
|
|
2656
|
+
std::wstring wstr(result);
|
|
2657
|
+
callback(std::string(wstr.begin(), wstr.end()));
|
|
2658
|
+
}
|
|
2659
|
+
return S_OK;
|
|
2660
|
+
})
|
|
2661
|
+
.Get());
|
|
2662
|
+
}
|
|
2663
|
+
#elif defined(__APPLE__)
|
|
2664
|
+
if (pImpl->wkWebView) {
|
|
2665
|
+
NSString *js = [NSString stringWithUTF8String:script.c_str()];
|
|
2666
|
+
[pImpl->wkWebView evaluateJavaScript:js
|
|
2667
|
+
completionHandler:^(id result, NSError *error) {
|
|
2668
|
+
if (result && callback) {
|
|
2669
|
+
NSString *resultStr =
|
|
2670
|
+
[NSString stringWithFormat:@"%@", result];
|
|
2671
|
+
callback([resultStr UTF8String]);
|
|
2672
|
+
}
|
|
2673
|
+
}];
|
|
2674
|
+
}
|
|
2675
|
+
#else
|
|
2676
|
+
if (pImpl->gtkWebView) {
|
|
2677
|
+
// GTK WebKit callback handling would go here
|
|
2678
|
+
webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
|
|
2679
|
+
nullptr, nullptr);
|
|
2680
|
+
}
|
|
2681
|
+
#endif
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
void Window::openDevTools() {
|
|
2685
|
+
#ifdef _WIN32
|
|
2686
|
+
if (pImpl->webview) {
|
|
2687
|
+
pImpl->webview->OpenDevToolsWindow();
|
|
2688
|
+
}
|
|
2689
|
+
#elif defined(__APPLE__)
|
|
2690
|
+
// macOS: Dev tools open in Safari's Web Inspector
|
|
2691
|
+
#else
|
|
2692
|
+
if (pImpl->gtkWebView) {
|
|
2693
|
+
WebKitWebInspector *inspector =
|
|
2694
|
+
webkit_web_view_get_inspector(pImpl->gtkWebView);
|
|
2695
|
+
webkit_web_inspector_show(inspector);
|
|
2696
|
+
}
|
|
2697
|
+
#endif
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
void Window::closeDevTools() {
|
|
2701
|
+
#ifdef _WIN32
|
|
2702
|
+
// WebView2 doesn't have explicit close
|
|
2703
|
+
#elif defined(__APPLE__)
|
|
2704
|
+
// macOS: Handled by Web Inspector
|
|
2705
|
+
#else
|
|
2706
|
+
if (pImpl->gtkWebView) {
|
|
2707
|
+
WebKitWebInspector *inspector =
|
|
2708
|
+
webkit_web_view_get_inspector(pImpl->gtkWebView);
|
|
2709
|
+
webkit_web_inspector_close(inspector);
|
|
2710
|
+
}
|
|
2711
|
+
#endif
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
void Window::setUserAgent(const std::string &userAgent) {
|
|
2715
|
+
pImpl->userAgent = userAgent;
|
|
2716
|
+
|
|
2717
|
+
#ifdef _WIN32
|
|
2718
|
+
if (pImpl->webview) {
|
|
2719
|
+
ComPtr<ICoreWebView2Settings> settings;
|
|
2720
|
+
pImpl->webview->get_Settings(&settings);
|
|
2721
|
+
// WebView2 user agent requires settings2 interface
|
|
2722
|
+
}
|
|
2723
|
+
#elif defined(__APPLE__)
|
|
2724
|
+
if (pImpl->wkWebView) {
|
|
2725
|
+
[pImpl->wkWebView
|
|
2726
|
+
setCustomUserAgent:[NSString stringWithUTF8String:userAgent.c_str()]];
|
|
2727
|
+
}
|
|
2728
|
+
#else
|
|
2729
|
+
if (pImpl->gtkWebView) {
|
|
2730
|
+
WebKitSettings *settings = webkit_web_view_get_settings(pImpl->gtkWebView);
|
|
2731
|
+
webkit_settings_set_user_agent(settings, userAgent.c_str());
|
|
2732
|
+
}
|
|
2733
|
+
#endif
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
std::string Window::getUserAgent() const { return pImpl->userAgent; }
|
|
2737
|
+
|
|
2738
|
+
void Window::setZoom(double factor) {
|
|
2739
|
+
pImpl->zoom = factor;
|
|
2740
|
+
|
|
2741
|
+
#ifdef _WIN32
|
|
2742
|
+
if (pImpl->controller) {
|
|
2743
|
+
pImpl->controller->put_ZoomFactor(factor);
|
|
2744
|
+
}
|
|
2745
|
+
#elif defined(__APPLE__)
|
|
2746
|
+
if (pImpl->wkWebView) {
|
|
2747
|
+
[pImpl->wkWebView setPageZoom:factor];
|
|
2748
|
+
}
|
|
2749
|
+
#else
|
|
2750
|
+
if (pImpl->gtkWebView) {
|
|
2751
|
+
webkit_web_view_set_zoom_level(pImpl->gtkWebView, factor);
|
|
2752
|
+
}
|
|
2753
|
+
#endif
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
double Window::getZoom() const { return pImpl->zoom; }
|
|
2757
|
+
|
|
2758
|
+
void Window::injectCSS(const std::string &css) {
|
|
2759
|
+
std::string script = R"(
|
|
2760
|
+
(function() {
|
|
2761
|
+
const style = document.createElement('style');
|
|
2762
|
+
style.id = 'plusui-injected-css-' + Date.now();
|
|
2763
|
+
style.textContent = `)" +
|
|
2764
|
+
css + R"(`;
|
|
2765
|
+
if (document.head) {
|
|
2766
|
+
document.head.appendChild(style);
|
|
2767
|
+
} else {
|
|
2768
|
+
document.documentElement.appendChild(style);
|
|
2769
|
+
}
|
|
2770
|
+
})();
|
|
2771
|
+
)";
|
|
2772
|
+
|
|
2773
|
+
executeScript(script);
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2522
2776
|
void Window::setWebviewDragDropEnabled(bool enabled) {
|
|
2523
2777
|
if (enabled) {
|
|
2524
2778
|
// Remove the dropzone init so it can be re-applied if needed later
|
|
@@ -2588,33 +2842,33 @@ void Window::setWebviewDragDropEnabled(bool enabled) {
|
|
|
2588
2842
|
)");
|
|
2589
2843
|
}
|
|
2590
2844
|
}
|
|
2591
|
-
|
|
2592
|
-
void Window::onNavigationStart(NavigationCallback callback) {
|
|
2593
|
-
pImpl->navigationCallback = callback;
|
|
2594
|
-
}
|
|
2595
|
-
|
|
2596
|
-
void Window::onNavigationComplete(LoadCallback callback) {
|
|
2597
|
-
pImpl->navigationCompleteCallback = callback;
|
|
2598
|
-
}
|
|
2599
|
-
|
|
2600
|
-
void Window::onLoadStart(LoadCallback callback) {
|
|
2601
|
-
pImpl->loadStartCallback = callback;
|
|
2602
|
-
}
|
|
2603
|
-
|
|
2604
|
-
void Window::onLoadEnd(LoadCallback callback) {
|
|
2605
|
-
pImpl->loadEndCallback = callback;
|
|
2606
|
-
}
|
|
2607
|
-
|
|
2608
|
-
void Window::onLoadError(ErrorCallback callback) {
|
|
2609
|
-
pImpl->errorCallback = callback;
|
|
2610
|
-
}
|
|
2611
|
-
|
|
2612
|
-
void Window::onConsoleMessage(ConsoleCallback callback) {
|
|
2613
|
-
pImpl->consoleCallback = callback;
|
|
2614
|
-
}
|
|
2615
|
-
|
|
2616
|
-
bool Window::isLoading() const { return pImpl->loading; }
|
|
2617
|
-
|
|
2618
|
-
std::string Window::getURL() const { return pImpl->currentURL; }
|
|
2619
|
-
|
|
2620
|
-
} // namespace plusui
|
|
2845
|
+
|
|
2846
|
+
void Window::onNavigationStart(NavigationCallback callback) {
|
|
2847
|
+
pImpl->navigationCallback = callback;
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
void Window::onNavigationComplete(LoadCallback callback) {
|
|
2851
|
+
pImpl->navigationCompleteCallback = callback;
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
void Window::onLoadStart(LoadCallback callback) {
|
|
2855
|
+
pImpl->loadStartCallback = callback;
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
void Window::onLoadEnd(LoadCallback callback) {
|
|
2859
|
+
pImpl->loadEndCallback = callback;
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
void Window::onLoadError(ErrorCallback callback) {
|
|
2863
|
+
pImpl->errorCallback = callback;
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
void Window::onConsoleMessage(ConsoleCallback callback) {
|
|
2867
|
+
pImpl->consoleCallback = callback;
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
bool Window::isLoading() const { return pImpl->loading; }
|
|
2871
|
+
|
|
2872
|
+
std::string Window::getURL() const { return pImpl->currentURL; }
|
|
2873
|
+
|
|
2874
|
+
} // namespace plusui
|