plusui-native-core 0.1.104 → 0.1.106
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Core/.claude/settings.local.json +7 -0
- package/Core/CMakeLists.txt +1 -1
- package/Core/Features/API/Connect_API.ts +20 -52
- 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 +128 -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 +5 -9
- 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 +328 -324
- package/Core/Features/WebGPU/webgpu.cpp +948 -948
- package/Core/Features/Window/webview.cpp +1026 -1014
- package/Core/Features/Window/webview.ts +516 -516
- package/Core/Features/Window/window.cpp +2265 -1988
- package/Core/include/plusui/api.hpp +237 -237
- package/Core/include/plusui/app.hpp +33 -33
- 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,196 +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
|
-
// Zone-specific delivery via DPI-corrected hit test
|
|
354
|
-
"var el=document.elementFromPoint(" +
|
|
355
|
-
std::to_string(dpx) + "," + std::to_string(dpy) +
|
|
356
|
-
");"
|
|
357
|
-
"var zone=el&&el.closest?el.closest('[data-dropzone]'):null;"
|
|
358
|
-
"var zoneName=zone?zone.getAttribute('data-dropzone'):null;"
|
|
359
|
-
"if(zoneName&&window.__plusui_fileDrop__){"
|
|
360
|
-
"window.__plusui_fileDrop__(zoneName,files);"
|
|
361
|
-
"}"
|
|
362
|
-
// If no zone matched deliver to all registered zones so a
|
|
363
|
-
// single-zone app always works regardless of hit-test accuracy.
|
|
364
|
-
"if(!zoneName&&window.__plusui_fileDrop_default__){"
|
|
365
|
-
"window.__plusui_fileDrop_default__(files);"
|
|
366
|
-
"}"
|
|
367
|
-
"})();";
|
|
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
|
+
}
|
|
368
252
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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;
|
|
374
277
|
case WM_HOTKEY: {
|
|
375
278
|
int hotKeyId = (int)wp;
|
|
376
279
|
auto it = impl->hotkeys.find(hotKeyId);
|
|
@@ -435,717 +338,755 @@ struct Window::Impl {
|
|
|
435
338
|
}
|
|
436
339
|
return DefWindowProc(hwnd, msg, wp, lp);
|
|
437
340
|
}
|
|
438
|
-
#elif defined(__APPLE__)
|
|
439
|
-
WKWebView *wkWebView = nullptr;
|
|
440
|
-
#else
|
|
441
|
-
WebKitWebView *gtkWebView = nullptr;
|
|
442
|
-
#endif
|
|
443
|
-
};
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
#
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
wc
|
|
466
|
-
wc.
|
|
467
|
-
wc.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if (!config.
|
|
480
|
-
style &= ~
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
if (config.
|
|
493
|
-
exStyle
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
if (
|
|
560
|
-
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
w.pImpl->trayManager
|
|
578
|
-
#
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
-
pImpl->
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
pImpl->
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
pImpl->config.
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
-
if (
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
-
pImpl->
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
#
|
|
752
|
-
if (pImpl->nativeWindow) {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
pImpl->state.
|
|
771
|
-
pImpl->state.
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
bool wasMaximized = pImpl->state.isMaximized;
|
|
796
|
-
pImpl->state.
|
|
797
|
-
pImpl->state.
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
bool
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
pImpl->
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
void Window::
|
|
852
|
-
bool
|
|
853
|
-
pImpl->state.isVisible =
|
|
854
|
-
pImpl->state.isHidden =
|
|
855
|
-
|
|
856
|
-
#ifdef _WIN32
|
|
857
|
-
if (pImpl->hwnd)
|
|
858
|
-
ShowWindow(pImpl->hwnd,
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if (pImpl->
|
|
866
|
-
|
|
867
|
-
}
|
|
868
|
-
#
|
|
869
|
-
|
|
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
|
-
if (pImpl->
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
#else
|
|
930
|
-
if (pImpl->nativeWindow) {
|
|
931
|
-
|
|
932
|
-
}
|
|
933
|
-
#endif
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
}
|
|
950
|
-
#else
|
|
951
|
-
if (pImpl->nativeWindow) {
|
|
952
|
-
|
|
953
|
-
}
|
|
954
|
-
#endif
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
void Window::
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
pImpl->
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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
|
-
|
|
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__.
|
|
1113
1054
|
ComPtr<ICoreWebView2Controller4> controller4;
|
|
1114
1055
|
if (controller &&
|
|
1115
1056
|
SUCCEEDED(controller->QueryInterface(
|
|
1116
1057
|
IID_PPV_ARGS(&controller4))) &&
|
|
1117
1058
|
controller4) {
|
|
1118
|
-
controller4->put_AllowExternalDrop(
|
|
1059
|
+
controller4->put_AllowExternalDrop(TRUE);
|
|
1119
1060
|
}
|
|
1120
|
-
|
|
1121
|
-
pImpl->nativeWebView = pImpl->webview.Get();
|
|
1122
|
-
pImpl->ready = true;
|
|
1123
|
-
|
|
1124
|
-
// Inject bridge script that runs on EVERY document
|
|
1125
|
-
// (survives navigation)
|
|
1126
|
-
std::string bridgeScript = R"(
|
|
1127
|
-
window.__native_invoke__ = function(request) {
|
|
1128
|
-
if (window.chrome && window.chrome.webview) {
|
|
1129
|
-
window.chrome.webview.postMessage(request);
|
|
1130
|
-
}
|
|
1131
|
-
};
|
|
1132
|
-
)";
|
|
1133
|
-
pImpl->webview->AddScriptToExecuteOnDocumentCreated(
|
|
1134
|
-
std::wstring(bridgeScript.begin(),
|
|
1135
|
-
bridgeScript.end())
|
|
1136
|
-
.c_str(),
|
|
1137
|
-
nullptr);
|
|
1138
|
-
|
|
1139
|
-
// Inject scrollbar hiding CSS if disabled
|
|
1140
|
-
if (!pImpl->config.scrollbars) {
|
|
1141
|
-
std::string scrollbarScript = kHideScrollbarsScript;
|
|
1142
|
-
pImpl->webview->AddScriptToExecuteOnDocumentCreated(
|
|
1143
|
-
std::wstring(scrollbarScript.begin(),
|
|
1144
|
-
scrollbarScript.end())
|
|
1145
|
-
.c_str(),
|
|
1146
|
-
nullptr);
|
|
1147
|
-
}
|
|
1148
|
-
|
|
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
|
+
|
|
1149
1090
|
// Block browser default drag-drop behavior (prevents
|
|
1150
1091
|
// the browser from navigating to the dropped file) while
|
|
1151
1092
|
// still allowing visual feedback on drop zones.
|
|
@@ -1157,6 +1098,7 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1157
1098
|
window.__plusui_dropzone_init = true;
|
|
1158
1099
|
|
|
1159
1100
|
var activeZone = null;
|
|
1101
|
+
var dragDepth = 0;
|
|
1160
1102
|
|
|
1161
1103
|
var findDropZone = function(e) {
|
|
1162
1104
|
var target = null;
|
|
@@ -1170,19 +1112,28 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1170
1112
|
|
|
1171
1113
|
var updateActiveZone = function(zone) {
|
|
1172
1114
|
if (activeZone === zone) return;
|
|
1173
|
-
if (activeZone)
|
|
1115
|
+
if (activeZone) {
|
|
1116
|
+
activeZone.classList.remove('dropzone-active');
|
|
1117
|
+
activeZone.classList.remove('filedrop-active');
|
|
1118
|
+
}
|
|
1174
1119
|
activeZone = zone;
|
|
1175
|
-
if (activeZone)
|
|
1120
|
+
if (activeZone) {
|
|
1121
|
+
activeZone.classList.add('dropzone-active');
|
|
1122
|
+
activeZone.classList.add('filedrop-active');
|
|
1123
|
+
}
|
|
1176
1124
|
};
|
|
1177
1125
|
|
|
1178
1126
|
// Always preventDefault to stop browser from navigating to file,
|
|
1179
|
-
// but show visual feedback when over a drop zone
|
|
1127
|
+
// but show visual feedback when over a drop zone.
|
|
1128
|
+
// dragDepth tracks nested dragenter/dragleave pairs so we know
|
|
1129
|
+
// when the drag truly leaves the window (depth returns to 0).
|
|
1180
1130
|
document.addEventListener('dragenter', function(e) {
|
|
1181
1131
|
e.preventDefault();
|
|
1132
|
+
dragDepth++;
|
|
1182
1133
|
var zone = findDropZone(e);
|
|
1183
1134
|
updateActiveZone(zone);
|
|
1184
1135
|
if (e.dataTransfer) {
|
|
1185
|
-
try { e.dataTransfer.dropEffect = zone ? '
|
|
1136
|
+
try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {}
|
|
1186
1137
|
}
|
|
1187
1138
|
}, true);
|
|
1188
1139
|
|
|
@@ -1191,19 +1142,63 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1191
1142
|
var zone = findDropZone(e);
|
|
1192
1143
|
updateActiveZone(zone);
|
|
1193
1144
|
if (e.dataTransfer) {
|
|
1194
|
-
try { e.dataTransfer.dropEffect = zone ? '
|
|
1145
|
+
try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {}
|
|
1195
1146
|
}
|
|
1196
1147
|
}, true);
|
|
1197
1148
|
|
|
1198
1149
|
document.addEventListener('dragleave', function(e) {
|
|
1199
1150
|
e.preventDefault();
|
|
1200
|
-
|
|
1201
|
-
|
|
1151
|
+
dragDepth--;
|
|
1152
|
+
if (dragDepth <= 0) {
|
|
1153
|
+
dragDepth = 0;
|
|
1154
|
+
updateActiveZone(null);
|
|
1155
|
+
} else {
|
|
1156
|
+
var zone = findDropZone(e);
|
|
1157
|
+
updateActiveZone(zone);
|
|
1158
|
+
}
|
|
1202
1159
|
}, true);
|
|
1203
1160
|
|
|
1204
1161
|
document.addEventListener('drop', function(e) {
|
|
1205
1162
|
e.preventDefault();
|
|
1163
|
+
dragDepth = 0;
|
|
1206
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
|
+
}
|
|
1207
1202
|
}, true);
|
|
1208
1203
|
|
|
1209
1204
|
// Also block at window level as a safety net
|
|
@@ -1217,428 +1212,450 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1217
1212
|
.c_str(),
|
|
1218
1213
|
nullptr);
|
|
1219
1214
|
|
|
1220
|
-
// Set up WebMessageReceived handler for JS->C++ bridge
|
|
1221
|
-
pImpl->webview->add_WebMessageReceived(
|
|
1222
|
-
Callback<
|
|
1223
|
-
ICoreWebView2WebMessageReceivedEventHandler>(
|
|
1224
|
-
[pImpl](ICoreWebView2 *sender,
|
|
1225
|
-
ICoreWebView2WebMessageReceivedEventArgs
|
|
1226
|
-
*args) -> HRESULT {
|
|
1227
|
-
(void)sender; // Suppress unused warning
|
|
1228
|
-
LPWSTR message = nullptr;
|
|
1229
|
-
args->TryGetWebMessageAsString(&message);
|
|
1230
|
-
if (!message)
|
|
1231
|
-
return S_OK;
|
|
1232
|
-
std::wstring wmsg(message);
|
|
1233
|
-
#pragma warning(push)
|
|
1234
|
-
#pragma warning(disable : 4244) // Suppress wchar_t to char conversion warning
|
|
1235
|
-
std::string msg(wmsg.begin(), wmsg.end());
|
|
1236
|
-
#pragma warning(pop)
|
|
1237
|
-
CoTaskMemFree(message);
|
|
1238
|
-
|
|
1239
|
-
// Debug: log received message
|
|
1240
|
-
std::cout << "[PlusUI] Received: " << msg
|
|
1241
|
-
<< std::endl;
|
|
1242
|
-
|
|
1243
|
-
bool handledByMessageCallback = false;
|
|
1244
|
-
|
|
1245
|
-
// New Generic Message Handler
|
|
1246
|
-
if (pImpl->messageCallback) {
|
|
1247
|
-
pImpl->messageCallback(msg);
|
|
1248
|
-
if (msg.find("\"kind\"") !=
|
|
1249
|
-
std::string::npos) {
|
|
1250
|
-
handledByMessageCallback = true;
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
// Parse JSON-RPC: {"id":"...",
|
|
1255
|
-
// "method":"window.minimize", "params":[...]}
|
|
1256
|
-
std::string id, method;
|
|
1257
|
-
std::string result = "null";
|
|
1258
|
-
bool success = false;
|
|
1259
|
-
|
|
1260
|
-
//
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
auto
|
|
1298
|
-
size_t
|
|
1299
|
-
if (
|
|
1300
|
-
return std::string(
|
|
1301
|
-
|
|
1302
|
-
if (
|
|
1303
|
-
return std::string(
|
|
1304
|
-
size_t
|
|
1305
|
-
if (
|
|
1306
|
-
return std::string(
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
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
|
-
if (
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
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
|
-
if (pImpl->window)
|
|
1440
|
-
pImpl->window->
|
|
1441
|
-
success = true;
|
|
1442
|
-
} else if (winMethod == "
|
|
1443
|
-
if (pImpl->window)
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
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
|
-
if (
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
success = true;
|
|
1578
|
-
} else if (
|
|
1579
|
-
pImpl->
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
pImpl->webview->
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
pImpl->webview->
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
if (
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
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.
|
|
1642
1659
|
if (pImpl->controller) {
|
|
1643
1660
|
ComPtr<ICoreWebView2Controller4>
|
|
1644
1661
|
controller4;
|
|
@@ -1646,115 +1663,115 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1646
1663
|
&controller4)) &&
|
|
1647
1664
|
controller4) {
|
|
1648
1665
|
controller4->put_AllowExternalDrop(
|
|
1649
|
-
|
|
1666
|
+
FALSE);
|
|
1650
1667
|
}
|
|
1651
1668
|
}
|
|
1652
|
-
}
|
|
1653
|
-
success = true;
|
|
1654
|
-
} else if (fileDropMethod == "isEnabled") {
|
|
1655
|
-
result = pImpl->config.fileDrop
|
|
1656
|
-
? "true"
|
|
1657
|
-
: "false";
|
|
1658
|
-
success = true;
|
|
1659
|
-
} else if (fileDropMethod == "startDrag") {
|
|
1660
|
-
result = "false";
|
|
1661
|
-
success = true;
|
|
1662
|
-
} else if (fileDropMethod ==
|
|
1663
|
-
"clearCallbacks") {
|
|
1664
|
-
success = true;
|
|
1665
|
-
}
|
|
1666
|
-
} else if (method.find("webview.") == 0) {
|
|
1667
|
-
std::string bindingName = method.substr(8);
|
|
1668
|
-
auto bindingIt =
|
|
1669
|
-
pImpl->bindings.find(bindingName);
|
|
1670
|
-
if (bindingIt != pImpl->bindings.end()) {
|
|
1671
|
-
std::string rawParam =
|
|
1672
|
-
extractFirstParam();
|
|
1673
|
-
std::string callbackArg =
|
|
1674
|
-
decodeJsonString(rawParam);
|
|
1675
|
-
try {
|
|
1676
|
-
result =
|
|
1677
|
-
bindingIt->second(callbackArg);
|
|
1678
|
-
if (result.empty()) {
|
|
1679
|
-
result = "null";
|
|
1680
|
-
}
|
|
1681
|
-
success = true;
|
|
1682
|
-
} catch (const std::exception &e) {
|
|
1683
|
-
std::cerr
|
|
1684
|
-
<< "[PlusUI] webview binding error: "
|
|
1685
|
-
<< e.what() << std::endl;
|
|
1686
|
-
result = "null";
|
|
1687
|
-
success = true;
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
// Send response back to JS (matches
|
|
1693
|
-
// plusui-native-core SDK bridge)
|
|
1694
|
-
std::string response =
|
|
1695
|
-
"window.__response__(\"" + id + "\", " +
|
|
1696
|
-
result + ");";
|
|
1697
|
-
pImpl->webview->ExecuteScript(
|
|
1698
|
-
std::wstring(response.begin(),
|
|
1699
|
-
response.end())
|
|
1700
|
-
.c_str(),
|
|
1701
|
-
nullptr);
|
|
1702
|
-
|
|
1703
|
-
return S_OK;
|
|
1704
|
-
})
|
|
1705
|
-
.Get(),
|
|
1706
|
-
nullptr);
|
|
1707
|
-
|
|
1708
|
-
// Process pending scripts
|
|
1709
|
-
for (const auto &script : pImpl->pendingScripts) {
|
|
1710
|
-
pImpl->webview->ExecuteScript(
|
|
1711
|
-
std::wstring(script.begin(), script.end())
|
|
1712
|
-
.c_str(),
|
|
1713
|
-
nullptr);
|
|
1714
|
-
}
|
|
1715
|
-
pImpl->pendingScripts.clear();
|
|
1716
|
-
|
|
1717
|
-
// Process pending navigation
|
|
1718
|
-
if (!pImpl->pendingNavigation.empty()) {
|
|
1719
|
-
pImpl->webview->Navigate(
|
|
1720
|
-
std::wstring(pImpl->pendingNavigation.begin(),
|
|
1721
|
-
pImpl->pendingNavigation.end())
|
|
1722
|
-
.c_str());
|
|
1723
|
-
pImpl->pendingNavigation.clear();
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
// Process pending HTML
|
|
1727
|
-
if (!pImpl->pendingHTML.empty()) {
|
|
1728
|
-
pImpl->webview->NavigateToString(
|
|
1729
|
-
std::wstring(pImpl->pendingHTML.begin(),
|
|
1730
|
-
pImpl->pendingHTML.end())
|
|
1731
|
-
.c_str());
|
|
1732
|
-
pImpl->pendingHTML.clear();
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
// Process pending File
|
|
1736
|
-
if (!pImpl->pendingFile.empty()) {
|
|
1737
|
-
// For now, loadFile is implemented via navigate in
|
|
1738
|
-
// some versions or direct file reading. We'll handle
|
|
1739
|
-
// it via navigate for simplicity if it's already
|
|
1740
|
-
// implemented that way.
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
return S_OK;
|
|
1744
|
-
})
|
|
1745
|
-
.Get());
|
|
1746
|
-
return S_OK;
|
|
1747
|
-
})
|
|
1748
|
-
.Get());
|
|
1749
|
-
|
|
1750
|
-
#elif defined(__APPLE__)
|
|
1751
|
-
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
|
|
1752
|
-
config.preferences.javaScriptEnabled = YES;
|
|
1753
|
-
|
|
1754
|
-
if (win.pImpl->config.devtools) {
|
|
1755
|
-
[config.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
|
|
1756
|
-
}
|
|
1757
|
-
|
|
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
|
+
|
|
1758
1775
|
// Block browser default drag-drop while allowing drop zone visual feedback.
|
|
1759
1776
|
// File delivery is handled natively by macOS drag APIs, not browser events.
|
|
1760
1777
|
if (win.pImpl->config.disableWebviewDragDrop ||
|
|
@@ -1812,24 +1829,24 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1812
1829
|
forMainFrameOnly:NO];
|
|
1813
1830
|
[config.userContentController addUserScript:userScript];
|
|
1814
1831
|
}
|
|
1815
|
-
|
|
1816
|
-
// Hide scrollbars if disabled
|
|
1817
|
-
if (!win.pImpl->config.scrollbars) {
|
|
1818
|
-
NSString *scrollbarScript =
|
|
1819
|
-
[NSString stringWithUTF8String:kHideScrollbarsScript];
|
|
1820
|
-
|
|
1821
|
-
WKUserScript *scrollScript = [[WKUserScript alloc]
|
|
1822
|
-
initWithSource:scrollbarScript
|
|
1823
|
-
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
|
|
1824
|
-
forMainFrameOnly:NO];
|
|
1825
|
-
[config.userContentController addUserScript:scrollScript];
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
NSView *parentView = (__bridge NSView *)windowHandle;
|
|
1829
|
-
win.pImpl->wkWebView =
|
|
1830
|
-
[[WKWebView alloc] initWithFrame:parentView.bounds configuration:config];
|
|
1831
|
-
[parentView addSubview:win.pImpl->wkWebView];
|
|
1832
|
-
|
|
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
|
+
|
|
1833
1850
|
win.pImpl->nativeWebView = (__bridge void *)win.pImpl->wkWebView;
|
|
1834
1851
|
|
|
1835
1852
|
// ── macOS key event forwarding ─────────────────────────────────────────────
|
|
@@ -1912,8 +1929,20 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1912
1929
|
// ── Linux / GTK + WebKit2 WebView ─────────────────────────────────────────
|
|
1913
1930
|
{
|
|
1914
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
|
+
|
|
1915
1942
|
WebKitWebView *webView =
|
|
1916
|
-
WEBKIT_WEB_VIEW(
|
|
1943
|
+
WEBKIT_WEB_VIEW(webkit_web_view_new_with_settings(wkSettings));
|
|
1944
|
+
g_object_unref(wkSettings);
|
|
1945
|
+
|
|
1917
1946
|
gtk_container_add(GTK_CONTAINER(gtkParent), GTK_WIDGET(webView));
|
|
1918
1947
|
gtk_widget_show(GTK_WIDGET(webView));
|
|
1919
1948
|
win.pImpl->gtkWebView = webView;
|
|
@@ -1932,12 +1961,260 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1932
1961
|
webkit_user_content_manager_add_script(mgr, bridgeScript);
|
|
1933
1962
|
webkit_user_script_unref(bridgeScript);
|
|
1934
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
|
+
|
|
1935
1967
|
// Helper to run JS in this WebView
|
|
1936
1968
|
auto runJS = [webView](const std::string& script) {
|
|
1937
1969
|
webkit_web_view_run_javascript(webView, script.c_str(),
|
|
1938
1970
|
nullptr, nullptr, nullptr);
|
|
1939
1971
|
};
|
|
1940
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
|
+
|
|
1941
2218
|
// GDK keyval → PlusUI KeyCode
|
|
1942
2219
|
auto gdkKeyToPlusUI = [](guint keyval) -> int {
|
|
1943
2220
|
if (keyval >= GDK_KEY_a && keyval <= GDK_KEY_z)
|
|
@@ -2079,423 +2356,423 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
2079
2356
|
|
|
2080
2357
|
return win;
|
|
2081
2358
|
}
|
|
2082
|
-
|
|
2083
|
-
TrayManager &Window::tray() { return *pImpl->trayManager; }
|
|
2084
|
-
|
|
2085
|
-
void Window::setWindow(std::shared_ptr<Window> win) {
|
|
2086
|
-
pImpl->window = win;
|
|
2087
|
-
if (!win)
|
|
2088
|
-
return;
|
|
2089
|
-
|
|
2090
|
-
std::weak_ptr<Impl> weak_pImpl = pImpl;
|
|
2091
|
-
win->onResize([weak_pImpl](int w, int h) {
|
|
2092
|
-
if (auto pImpl = weak_pImpl.lock()) {
|
|
2093
|
-
#ifdef _WIN32
|
|
2094
|
-
if (pImpl->controller) {
|
|
2095
|
-
RECT bounds = {0, 0, w, h};
|
|
2096
|
-
pImpl->controller->put_Bounds(bounds);
|
|
2097
|
-
}
|
|
2098
|
-
#elif defined(__APPLE__)
|
|
2099
|
-
if (pImpl->wkWebView) {
|
|
2100
|
-
NSView *view = (__bridge NSView *)pImpl->wkWebView;
|
|
2101
|
-
NSView *parent = [view superview];
|
|
2102
|
-
if (parent) {
|
|
2103
|
-
[view setFrame:[parent bounds]];
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
#else
|
|
2107
|
-
if (pImpl->gtkWebView) {
|
|
2108
|
-
// GTK usually handles this if added to a container with expand=TRUE
|
|
2109
|
-
// but we can ensure it here if it's a fixed layout parent
|
|
2110
|
-
gtk_widget_set_size_request(GTK_WIDGET(pImpl->gtkWebView), w, h);
|
|
2111
|
-
}
|
|
2112
|
-
#endif
|
|
2113
|
-
}
|
|
2114
|
-
});
|
|
2115
|
-
|
|
2116
|
-
// Trigger initial resize
|
|
2117
|
-
int w, h;
|
|
2118
|
-
win->getSize(w, h);
|
|
2119
|
-
#ifdef _WIN32
|
|
2120
|
-
if (pImpl->controller) {
|
|
2121
|
-
RECT bounds = {0, 0, w, h};
|
|
2122
|
-
pImpl->controller->put_Bounds(bounds);
|
|
2123
|
-
}
|
|
2124
|
-
#endif
|
|
2125
|
-
}
|
|
2126
|
-
|
|
2127
|
-
void Window::navigate(const std::string &url) {
|
|
2128
|
-
pImpl->currentURL = url;
|
|
2129
|
-
pImpl->loading = true;
|
|
2130
|
-
|
|
2131
|
-
#ifdef _WIN32
|
|
2132
|
-
if (pImpl->ready && pImpl->webview) {
|
|
2133
|
-
pImpl->webview->Navigate(std::wstring(url.begin(), url.end()).c_str());
|
|
2134
|
-
} else {
|
|
2135
|
-
pImpl->pendingNavigation = url;
|
|
2136
|
-
}
|
|
2137
|
-
#elif defined(__APPLE__)
|
|
2138
|
-
if (pImpl->wkWebView) {
|
|
2139
|
-
NSURL *nsurl =
|
|
2140
|
-
[NSURL URLWithString:[NSString stringWithUTF8String:url.c_str()]];
|
|
2141
|
-
NSURLRequest *request = [NSURLRequest requestWithURL:nsurl];
|
|
2142
|
-
[pImpl->wkWebView loadRequest:request];
|
|
2143
|
-
}
|
|
2144
|
-
#else
|
|
2145
|
-
if (pImpl->gtkWebView) {
|
|
2146
|
-
webkit_web_view_load_uri(pImpl->gtkWebView, url.c_str());
|
|
2147
|
-
}
|
|
2148
|
-
#endif
|
|
2149
|
-
}
|
|
2150
|
-
|
|
2151
|
-
void Window::onMessage(MessageCallback callback) {
|
|
2152
|
-
pImpl->messageCallback = callback;
|
|
2153
|
-
}
|
|
2154
|
-
|
|
2155
|
-
void Window::postMessage(const std::string &message) {
|
|
2156
|
-
#ifdef _WIN32
|
|
2157
|
-
if (pImpl->webview) {
|
|
2158
|
-
std::wstring wmsg(message.begin(), message.end());
|
|
2159
|
-
pImpl->webview->PostWebMessageAsJson(wmsg.c_str());
|
|
2160
|
-
}
|
|
2161
|
-
#elif defined(__APPLE__)
|
|
2162
|
-
// TODO: formatting for Apple
|
|
2163
|
-
#endif
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
void Window::onFileDrop(FileDropCallback callback) {
|
|
2167
|
-
pImpl->fileDropCallback = callback;
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
|
-
void Window::bind(const std::string &name, JSCallback callback) {
|
|
2171
|
-
pImpl->bindings[name] = callback;
|
|
2172
|
-
|
|
2173
|
-
std::string bridgeScript =
|
|
2174
|
-
"window." + name +
|
|
2175
|
-
R"( = function(...args) {
|
|
2176
|
-
if (window.plusui && typeof window.plusui.invoke === 'function') {
|
|
2177
|
-
const payload = args.length === 0 ? null : (args.length === 1 ? args[0] : args);
|
|
2178
|
-
return window.plusui.invoke('webview.)" +
|
|
2179
|
-
name +
|
|
2180
|
-
R"(', [payload]);
|
|
2181
|
-
}
|
|
2182
|
-
if (window.__invoke__) {
|
|
2183
|
-
const payload = args.length === 0 ? null : (args.length === 1 ? args[0] : args);
|
|
2184
|
-
return window.__invoke__('webview.)" +
|
|
2185
|
-
name +
|
|
2186
|
-
R"(', [payload]);
|
|
2187
|
-
}
|
|
2188
|
-
return Promise.resolve(null);
|
|
2189
|
-
};)";
|
|
2190
|
-
|
|
2191
|
-
executeScript(bridgeScript);
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
void Window::unbind(const std::string &name) {
|
|
2195
|
-
pImpl->bindings.erase(name);
|
|
2196
|
-
executeScript("delete window." + name + ";");
|
|
2197
|
-
}
|
|
2198
|
-
|
|
2199
|
-
void Window::loadURL(const std::string &url) { navigate(url); }
|
|
2200
|
-
|
|
2201
|
-
void Window::loadHTML(const std::string &html) {
|
|
2202
|
-
loadHTML(html, "about:blank");
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
|
-
void Window::loadHTML(const std::string &html, const std::string &baseURL) {
|
|
2206
|
-
pImpl->loading = true;
|
|
2207
|
-
|
|
2208
|
-
#ifdef _WIN32
|
|
2209
|
-
(void)baseURL; // Not used on Windows
|
|
2210
|
-
if (pImpl->ready && pImpl->webview) {
|
|
2211
|
-
pImpl->webview->NavigateToString(
|
|
2212
|
-
std::wstring(html.begin(), html.end()).c_str());
|
|
2213
|
-
} else {
|
|
2214
|
-
pImpl->pendingHTML = html;
|
|
2215
|
-
}
|
|
2216
|
-
#elif defined(__APPLE__)
|
|
2217
|
-
if (pImpl->wkWebView) {
|
|
2218
|
-
NSString *htmlString = [NSString stringWithUTF8String:html.c_str()];
|
|
2219
|
-
NSURL *base =
|
|
2220
|
-
[NSURL URLWithString:[NSString stringWithUTF8String:baseURL.c_str()]];
|
|
2221
|
-
[pImpl->wkWebView loadHTMLString:htmlString baseURL:base];
|
|
2222
|
-
}
|
|
2223
|
-
#else
|
|
2224
|
-
if (pImpl->gtkWebView) {
|
|
2225
|
-
webkit_web_view_load_html(pImpl->gtkWebView, html.c_str(), baseURL.c_str());
|
|
2226
|
-
}
|
|
2227
|
-
#endif
|
|
2228
|
-
}
|
|
2229
|
-
|
|
2230
|
-
void Window::loadFile(const std::string &filePath) {
|
|
2231
|
-
std::ifstream file(filePath);
|
|
2232
|
-
if (!file) {
|
|
2233
|
-
std::cerr << "PlusUI: Failed to open file: " << filePath << std::endl;
|
|
2234
|
-
return;
|
|
2235
|
-
}
|
|
2236
|
-
|
|
2237
|
-
std::stringstream buffer;
|
|
2238
|
-
buffer << file.rdbuf();
|
|
2239
|
-
|
|
2240
|
-
// Use file:// URL as base for relative paths
|
|
2241
|
-
std::string baseURL =
|
|
2242
|
-
"file://" + filePath.substr(0, filePath.find_last_of("/\\") + 1);
|
|
2243
|
-
loadHTML(buffer.str(), baseURL);
|
|
2244
|
-
}
|
|
2245
|
-
|
|
2246
|
-
void Window::reload() {
|
|
2247
|
-
#ifdef _WIN32
|
|
2248
|
-
if (pImpl->webview) {
|
|
2249
|
-
pImpl->webview->Reload();
|
|
2250
|
-
}
|
|
2251
|
-
#elif defined(__APPLE__)
|
|
2252
|
-
if (pImpl->wkWebView) {
|
|
2253
|
-
[pImpl->wkWebView reload];
|
|
2254
|
-
}
|
|
2255
|
-
#else
|
|
2256
|
-
if (pImpl->gtkWebView) {
|
|
2257
|
-
webkit_web_view_reload(pImpl->gtkWebView);
|
|
2258
|
-
}
|
|
2259
|
-
#endif
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
void Window::stop() {
|
|
2263
|
-
#ifdef _WIN32
|
|
2264
|
-
if (pImpl->webview) {
|
|
2265
|
-
pImpl->webview->Stop();
|
|
2266
|
-
}
|
|
2267
|
-
#elif defined(__APPLE__)
|
|
2268
|
-
if (pImpl->wkWebView) {
|
|
2269
|
-
[pImpl->wkWebView stopLoading];
|
|
2270
|
-
}
|
|
2271
|
-
#else
|
|
2272
|
-
if (pImpl->gtkWebView) {
|
|
2273
|
-
webkit_web_view_stop_loading(pImpl->gtkWebView);
|
|
2274
|
-
}
|
|
2275
|
-
#endif
|
|
2276
|
-
}
|
|
2277
|
-
|
|
2278
|
-
void Window::goBack() {
|
|
2279
|
-
#ifdef _WIN32
|
|
2280
|
-
if (pImpl->webview) {
|
|
2281
|
-
pImpl->webview->GoBack();
|
|
2282
|
-
}
|
|
2283
|
-
#elif defined(__APPLE__)
|
|
2284
|
-
if (pImpl->wkWebView) {
|
|
2285
|
-
[pImpl->wkWebView goBack];
|
|
2286
|
-
}
|
|
2287
|
-
#else
|
|
2288
|
-
if (pImpl->gtkWebView) {
|
|
2289
|
-
webkit_web_view_go_back(pImpl->gtkWebView);
|
|
2290
|
-
}
|
|
2291
|
-
#endif
|
|
2292
|
-
}
|
|
2293
|
-
|
|
2294
|
-
void Window::goForward() {
|
|
2295
|
-
#ifdef _WIN32
|
|
2296
|
-
if (pImpl->webview) {
|
|
2297
|
-
pImpl->webview->GoForward();
|
|
2298
|
-
}
|
|
2299
|
-
#elif defined(__APPLE__)
|
|
2300
|
-
if (pImpl->wkWebView) {
|
|
2301
|
-
[pImpl->wkWebView goForward];
|
|
2302
|
-
}
|
|
2303
|
-
#else
|
|
2304
|
-
if (pImpl->gtkWebView) {
|
|
2305
|
-
webkit_web_view_go_forward(pImpl->gtkWebView);
|
|
2306
|
-
}
|
|
2307
|
-
#endif
|
|
2308
|
-
}
|
|
2309
|
-
|
|
2310
|
-
bool Window::canGoBack() const {
|
|
2311
|
-
#ifdef _WIN32
|
|
2312
|
-
if (pImpl->webview) {
|
|
2313
|
-
BOOL canGoBack;
|
|
2314
|
-
pImpl->webview->get_CanGoBack(&canGoBack);
|
|
2315
|
-
return canGoBack;
|
|
2316
|
-
}
|
|
2317
|
-
#elif defined(__APPLE__)
|
|
2318
|
-
if (pImpl->wkWebView) {
|
|
2319
|
-
return [pImpl->wkWebView canGoBack];
|
|
2320
|
-
}
|
|
2321
|
-
#else
|
|
2322
|
-
if (pImpl->gtkWebView) {
|
|
2323
|
-
return webkit_web_view_can_go_back(pImpl->gtkWebView);
|
|
2324
|
-
}
|
|
2325
|
-
#endif
|
|
2326
|
-
return false;
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
|
-
bool Window::canGoForward() const {
|
|
2330
|
-
#ifdef _WIN32
|
|
2331
|
-
if (pImpl->webview) {
|
|
2332
|
-
BOOL canGoForward;
|
|
2333
|
-
pImpl->webview->get_CanGoForward(&canGoForward);
|
|
2334
|
-
return canGoForward;
|
|
2335
|
-
}
|
|
2336
|
-
#elif defined(__APPLE__)
|
|
2337
|
-
if (pImpl->wkWebView) {
|
|
2338
|
-
return [pImpl->wkWebView canGoForward];
|
|
2339
|
-
}
|
|
2340
|
-
#else
|
|
2341
|
-
if (pImpl->gtkWebView) {
|
|
2342
|
-
return webkit_web_view_can_go_forward(pImpl->gtkWebView);
|
|
2343
|
-
}
|
|
2344
|
-
#endif
|
|
2345
|
-
return false;
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2348
|
-
void Window::executeScript(const std::string &script) {
|
|
2349
|
-
#ifdef _WIN32
|
|
2350
|
-
if (pImpl->ready && pImpl->webview) {
|
|
2351
|
-
pImpl->webview->ExecuteScript(
|
|
2352
|
-
std::wstring(script.begin(), script.end()).c_str(), nullptr);
|
|
2353
|
-
} else {
|
|
2354
|
-
pImpl->pendingScripts.push_back(script);
|
|
2355
|
-
}
|
|
2356
|
-
#elif defined(__APPLE__)
|
|
2357
|
-
if (pImpl->wkWebView) {
|
|
2358
|
-
NSString *js = [NSString stringWithUTF8String:script.c_str()];
|
|
2359
|
-
[pImpl->wkWebView evaluateJavaScript:js completionHandler:nil];
|
|
2360
|
-
}
|
|
2361
|
-
#else
|
|
2362
|
-
if (pImpl->gtkWebView) {
|
|
2363
|
-
webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
|
|
2364
|
-
nullptr, nullptr);
|
|
2365
|
-
}
|
|
2366
|
-
#endif
|
|
2367
|
-
}
|
|
2368
|
-
|
|
2369
|
-
void Window::executeScript(const std::string &script,
|
|
2370
|
-
std::function<void(const std::string &)> callback) {
|
|
2371
|
-
#ifdef _WIN32
|
|
2372
|
-
if (pImpl->webview) {
|
|
2373
|
-
pImpl->webview->ExecuteScript(
|
|
2374
|
-
std::wstring(script.begin(), script.end()).c_str(),
|
|
2375
|
-
Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
|
|
2376
|
-
[callback](HRESULT error, LPCWSTR result) -> HRESULT {
|
|
2377
|
-
(void)error; // Suppress unused warning
|
|
2378
|
-
if (result && callback) {
|
|
2379
|
-
std::wstring wstr(result);
|
|
2380
|
-
callback(std::string(wstr.begin(), wstr.end()));
|
|
2381
|
-
}
|
|
2382
|
-
return S_OK;
|
|
2383
|
-
})
|
|
2384
|
-
.Get());
|
|
2385
|
-
}
|
|
2386
|
-
#elif defined(__APPLE__)
|
|
2387
|
-
if (pImpl->wkWebView) {
|
|
2388
|
-
NSString *js = [NSString stringWithUTF8String:script.c_str()];
|
|
2389
|
-
[pImpl->wkWebView evaluateJavaScript:js
|
|
2390
|
-
completionHandler:^(id result, NSError *error) {
|
|
2391
|
-
if (result && callback) {
|
|
2392
|
-
NSString *resultStr =
|
|
2393
|
-
[NSString stringWithFormat:@"%@", result];
|
|
2394
|
-
callback([resultStr UTF8String]);
|
|
2395
|
-
}
|
|
2396
|
-
}];
|
|
2397
|
-
}
|
|
2398
|
-
#else
|
|
2399
|
-
if (pImpl->gtkWebView) {
|
|
2400
|
-
// GTK WebKit callback handling would go here
|
|
2401
|
-
webkit_web_view_run_javascript(pImpl->gtkWebView, script.c_str(), nullptr,
|
|
2402
|
-
nullptr, nullptr);
|
|
2403
|
-
}
|
|
2404
|
-
#endif
|
|
2405
|
-
}
|
|
2406
|
-
|
|
2407
|
-
void Window::openDevTools() {
|
|
2408
|
-
#ifdef _WIN32
|
|
2409
|
-
if (pImpl->webview) {
|
|
2410
|
-
pImpl->webview->OpenDevToolsWindow();
|
|
2411
|
-
}
|
|
2412
|
-
#elif defined(__APPLE__)
|
|
2413
|
-
// macOS: Dev tools open in Safari's Web Inspector
|
|
2414
|
-
#else
|
|
2415
|
-
if (pImpl->gtkWebView) {
|
|
2416
|
-
WebKitWebInspector *inspector =
|
|
2417
|
-
webkit_web_view_get_inspector(pImpl->gtkWebView);
|
|
2418
|
-
webkit_web_inspector_show(inspector);
|
|
2419
|
-
}
|
|
2420
|
-
#endif
|
|
2421
|
-
}
|
|
2422
|
-
|
|
2423
|
-
void Window::closeDevTools() {
|
|
2424
|
-
#ifdef _WIN32
|
|
2425
|
-
// WebView2 doesn't have explicit close
|
|
2426
|
-
#elif defined(__APPLE__)
|
|
2427
|
-
// macOS: Handled by Web Inspector
|
|
2428
|
-
#else
|
|
2429
|
-
if (pImpl->gtkWebView) {
|
|
2430
|
-
WebKitWebInspector *inspector =
|
|
2431
|
-
webkit_web_view_get_inspector(pImpl->gtkWebView);
|
|
2432
|
-
webkit_web_inspector_close(inspector);
|
|
2433
|
-
}
|
|
2434
|
-
#endif
|
|
2435
|
-
}
|
|
2436
|
-
|
|
2437
|
-
void Window::setUserAgent(const std::string &userAgent) {
|
|
2438
|
-
pImpl->userAgent = userAgent;
|
|
2439
|
-
|
|
2440
|
-
#ifdef _WIN32
|
|
2441
|
-
if (pImpl->webview) {
|
|
2442
|
-
ComPtr<ICoreWebView2Settings> settings;
|
|
2443
|
-
pImpl->webview->get_Settings(&settings);
|
|
2444
|
-
// WebView2 user agent requires settings2 interface
|
|
2445
|
-
}
|
|
2446
|
-
#elif defined(__APPLE__)
|
|
2447
|
-
if (pImpl->wkWebView) {
|
|
2448
|
-
[pImpl->wkWebView
|
|
2449
|
-
setCustomUserAgent:[NSString stringWithUTF8String:userAgent.c_str()]];
|
|
2450
|
-
}
|
|
2451
|
-
#else
|
|
2452
|
-
if (pImpl->gtkWebView) {
|
|
2453
|
-
WebKitSettings *settings = webkit_web_view_get_settings(pImpl->gtkWebView);
|
|
2454
|
-
webkit_settings_set_user_agent(settings, userAgent.c_str());
|
|
2455
|
-
}
|
|
2456
|
-
#endif
|
|
2457
|
-
}
|
|
2458
|
-
|
|
2459
|
-
std::string Window::getUserAgent() const { return pImpl->userAgent; }
|
|
2460
|
-
|
|
2461
|
-
void Window::setZoom(double factor) {
|
|
2462
|
-
pImpl->zoom = factor;
|
|
2463
|
-
|
|
2464
|
-
#ifdef _WIN32
|
|
2465
|
-
if (pImpl->controller) {
|
|
2466
|
-
pImpl->controller->put_ZoomFactor(factor);
|
|
2467
|
-
}
|
|
2468
|
-
#elif defined(__APPLE__)
|
|
2469
|
-
if (pImpl->wkWebView) {
|
|
2470
|
-
[pImpl->wkWebView setPageZoom:factor];
|
|
2471
|
-
}
|
|
2472
|
-
#else
|
|
2473
|
-
if (pImpl->gtkWebView) {
|
|
2474
|
-
webkit_web_view_set_zoom_level(pImpl->gtkWebView, factor);
|
|
2475
|
-
}
|
|
2476
|
-
#endif
|
|
2477
|
-
}
|
|
2478
|
-
|
|
2479
|
-
double Window::getZoom() const { return pImpl->zoom; }
|
|
2480
|
-
|
|
2481
|
-
void Window::injectCSS(const std::string &css) {
|
|
2482
|
-
std::string script = R"(
|
|
2483
|
-
(function() {
|
|
2484
|
-
const style = document.createElement('style');
|
|
2485
|
-
style.id = 'plusui-injected-css-' + Date.now();
|
|
2486
|
-
style.textContent = `)" +
|
|
2487
|
-
css + R"(`;
|
|
2488
|
-
if (document.head) {
|
|
2489
|
-
document.head.appendChild(style);
|
|
2490
|
-
} else {
|
|
2491
|
-
document.documentElement.appendChild(style);
|
|
2492
|
-
}
|
|
2493
|
-
})();
|
|
2494
|
-
)";
|
|
2495
|
-
|
|
2496
|
-
executeScript(script);
|
|
2497
|
-
}
|
|
2498
|
-
|
|
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
|
+
|
|
2499
2776
|
void Window::setWebviewDragDropEnabled(bool enabled) {
|
|
2500
2777
|
if (enabled) {
|
|
2501
2778
|
// Remove the dropzone init so it can be re-applied if needed later
|
|
@@ -2565,33 +2842,33 @@ void Window::setWebviewDragDropEnabled(bool enabled) {
|
|
|
2565
2842
|
)");
|
|
2566
2843
|
}
|
|
2567
2844
|
}
|
|
2568
|
-
|
|
2569
|
-
void Window::onNavigationStart(NavigationCallback callback) {
|
|
2570
|
-
pImpl->navigationCallback = callback;
|
|
2571
|
-
}
|
|
2572
|
-
|
|
2573
|
-
void Window::onNavigationComplete(LoadCallback callback) {
|
|
2574
|
-
pImpl->navigationCompleteCallback = callback;
|
|
2575
|
-
}
|
|
2576
|
-
|
|
2577
|
-
void Window::onLoadStart(LoadCallback callback) {
|
|
2578
|
-
pImpl->loadStartCallback = callback;
|
|
2579
|
-
}
|
|
2580
|
-
|
|
2581
|
-
void Window::onLoadEnd(LoadCallback callback) {
|
|
2582
|
-
pImpl->loadEndCallback = callback;
|
|
2583
|
-
}
|
|
2584
|
-
|
|
2585
|
-
void Window::onLoadError(ErrorCallback callback) {
|
|
2586
|
-
pImpl->errorCallback = callback;
|
|
2587
|
-
}
|
|
2588
|
-
|
|
2589
|
-
void Window::onConsoleMessage(ConsoleCallback callback) {
|
|
2590
|
-
pImpl->consoleCallback = callback;
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
|
-
bool Window::isLoading() const { return pImpl->loading; }
|
|
2594
|
-
|
|
2595
|
-
std::string Window::getURL() const { return pImpl->currentURL; }
|
|
2596
|
-
|
|
2597
|
-
} // 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
|