plusui-native-core 0.1.105 → 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/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 +0 -7
- package/Core/Features/Keyboard/keyboard_linux.cpp +4 -0
- package/Core/Features/Router/router.cpp +62 -62
- package/Core/Features/Router/router.ts +113 -113
- package/Core/Features/Tray/tray.cpp +328 -324
- package/Core/Features/WebGPU/webgpu.cpp +948 -948
- package/Core/Features/Window/webview.cpp +1009 -1009
- package/Core/Features/Window/webview.ts +516 -516
- package/Core/Features/Window/window.cpp +2240 -1986
- package/Core/include/plusui/api.hpp +237 -237
- package/Core/include/plusui/app.hpp +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,324 +1,448 @@
|
|
|
1
|
-
#include <plusui/filedrop.hpp>
|
|
2
|
-
#include <
|
|
3
|
-
#include <
|
|
4
|
-
#include <
|
|
5
|
-
#include <
|
|
6
|
-
#include <
|
|
7
|
-
#include <
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#include <
|
|
14
|
-
#include <
|
|
15
|
-
#include <
|
|
16
|
-
#
|
|
17
|
-
#include <
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
std::
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
{
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
{
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
{
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
1
|
+
#include <plusui/filedrop.hpp>
|
|
2
|
+
#include <plusui/window.hpp>
|
|
3
|
+
#include <memory>
|
|
4
|
+
#include <mutex>
|
|
5
|
+
#include <filesystem>
|
|
6
|
+
#include <fstream>
|
|
7
|
+
#include <unordered_map>
|
|
8
|
+
#include <cctype>
|
|
9
|
+
#include <iostream>
|
|
10
|
+
|
|
11
|
+
#ifdef _WIN32
|
|
12
|
+
#define WIN32_LEAN_AND_MEAN
|
|
13
|
+
#include <windows.h>
|
|
14
|
+
#include <shlobj.h>
|
|
15
|
+
#include <objidl.h>
|
|
16
|
+
#include <oleidl.h>
|
|
17
|
+
#include <shellapi.h>
|
|
18
|
+
#elif defined(__APPLE__)
|
|
19
|
+
#include <AppKit/AppKit.h>
|
|
20
|
+
#include <CoreFoundation/CoreFoundation.h>
|
|
21
|
+
#import <WebKit/WebKit.h>
|
|
22
|
+
#else
|
|
23
|
+
// Linux/GTK
|
|
24
|
+
#include <gtk/gtk.h>
|
|
25
|
+
#include <webkit2/webkit2.h>
|
|
26
|
+
#endif
|
|
27
|
+
|
|
28
|
+
namespace plusui {
|
|
29
|
+
|
|
30
|
+
static std::string jsonEscape(const std::string &input) {
|
|
31
|
+
std::string out;
|
|
32
|
+
out.reserve(input.size() + 8);
|
|
33
|
+
for (char c : input) {
|
|
34
|
+
switch (c) {
|
|
35
|
+
case '\\': out += "\\\\"; break;
|
|
36
|
+
case '"': out += "\\\""; break;
|
|
37
|
+
case '\n': out += "\\n"; break;
|
|
38
|
+
case '\r': out += "\\r"; break;
|
|
39
|
+
case '\t': out += "\\t"; break;
|
|
40
|
+
default: out += c; break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static std::string mimeTypeFromPath(const std::string &path) {
|
|
47
|
+
size_t dot = path.find_last_of(".");
|
|
48
|
+
if (dot == std::string::npos) return "application/octet-stream";
|
|
49
|
+
std::string ext = path.substr(dot + 1);
|
|
50
|
+
for (char &c : ext) c = (char)tolower(c);
|
|
51
|
+
if (ext == "png") return "image/png";
|
|
52
|
+
if (ext == "jpg" || ext == "jpeg") return "image/jpeg";
|
|
53
|
+
if (ext == "gif") return "image/gif";
|
|
54
|
+
if (ext == "html" || ext == "htm") return "text/html";
|
|
55
|
+
if (ext == "js") return "application/javascript";
|
|
56
|
+
if (ext == "json") return "application/json";
|
|
57
|
+
if (ext == "css") return "text/css";
|
|
58
|
+
if (ext == "txt" || ext == "md") return "text/plain";
|
|
59
|
+
if (ext == "svg") return "image/svg+xml";
|
|
60
|
+
if (ext == "pdf") return "application/pdf";
|
|
61
|
+
if (ext == "zip") return "application/zip";
|
|
62
|
+
return "application/octet-stream";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
struct FileDrop::Impl {
|
|
66
|
+
std::mutex mtx;
|
|
67
|
+
bool enabled = true;
|
|
68
|
+
std::function<void(const std::vector<FileInfo>&)> filesDroppedCallback;
|
|
69
|
+
std::function<void()> dragEnterCallback;
|
|
70
|
+
std::function<void()> dragLeaveCallback;
|
|
71
|
+
void* nativeWindowHandle = nullptr;
|
|
72
|
+
void* nativeWebViewHandle = nullptr;
|
|
73
|
+
|
|
74
|
+
#ifdef _WIN32
|
|
75
|
+
void* dropTarget = nullptr;
|
|
76
|
+
#elif defined(__APPLE__)
|
|
77
|
+
// macOS specific drag context
|
|
78
|
+
#elif defined(__linux__)
|
|
79
|
+
struct DropCtx {
|
|
80
|
+
WebKitWebView *wv;
|
|
81
|
+
bool entered = false;
|
|
82
|
+
gint x = 0, y = 0;
|
|
83
|
+
};
|
|
84
|
+
DropCtx* dc = nullptr;
|
|
85
|
+
#endif
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
FileDrop::FileDrop() : pImpl(new Impl()) {}
|
|
89
|
+
|
|
90
|
+
FileDrop::~FileDrop() {
|
|
91
|
+
#ifdef __linux__
|
|
92
|
+
if (pImpl->dc) delete pImpl->dc;
|
|
93
|
+
#endif
|
|
94
|
+
delete pImpl;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#ifdef _WIN32
|
|
98
|
+
class FileDropTarget : public IDropTarget {
|
|
99
|
+
HWND m_hwnd;
|
|
100
|
+
void* m_webview;
|
|
101
|
+
ULONG m_refCount;
|
|
102
|
+
bool m_hasFiles;
|
|
103
|
+
|
|
104
|
+
public:
|
|
105
|
+
FileDropTarget(HWND hwnd, void* webview) : m_hwnd(hwnd), m_webview(webview), m_refCount(1), m_hasFiles(false) {}
|
|
106
|
+
|
|
107
|
+
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override {
|
|
108
|
+
if (riid == IID_IUnknown || riid == IID_IDropTarget) {
|
|
109
|
+
*ppvObject = this;
|
|
110
|
+
AddRef();
|
|
111
|
+
return S_OK;
|
|
112
|
+
}
|
|
113
|
+
*ppvObject = nullptr;
|
|
114
|
+
return E_NOINTERFACE;
|
|
115
|
+
}
|
|
116
|
+
ULONG STDMETHODCALLTYPE AddRef() override { return InterlockedIncrement(&m_refCount); }
|
|
117
|
+
ULONG STDMETHODCALLTYPE Release() override {
|
|
118
|
+
ULONG ref = InterlockedDecrement(&m_refCount);
|
|
119
|
+
if (ref == 0) delete this;
|
|
120
|
+
return ref;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
void screenToCSS(POINTL pt, int &cssX, int &cssY) {
|
|
124
|
+
POINT ptClient = {pt.x, pt.y};
|
|
125
|
+
ScreenToClient(m_hwnd, &ptClient);
|
|
126
|
+
|
|
127
|
+
double dpiScale = 1.0;
|
|
128
|
+
UINT dpi = GetDpiForWindow(m_hwnd);
|
|
129
|
+
if (dpi > 0) dpiScale = static_cast<double>(dpi) / 96.0;
|
|
130
|
+
|
|
131
|
+
cssX = static_cast<int>(ptClient.x / dpiScale);
|
|
132
|
+
cssY = static_cast<int>(ptClient.y / dpiScale);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
bool hasFiles(IDataObject *pDataObj) {
|
|
136
|
+
if (!pDataObj) return false;
|
|
137
|
+
FORMATETC fmt = {CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
138
|
+
return pDataObj->QueryGetData(&fmt) == S_OK;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
void execScript(const std::string &js) {
|
|
142
|
+
if (m_webview) {
|
|
143
|
+
std::wstring wjs(js.begin(), js.end());
|
|
144
|
+
((ICoreWebView2*)m_webview)->ExecuteScript(wjs.c_str(), nullptr);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
HRESULT STDMETHODCALLTYPE DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override {
|
|
149
|
+
m_hasFiles = hasFiles(pDataObj);
|
|
150
|
+
if (m_hasFiles) {
|
|
151
|
+
*pdwEffect = DROPEFFECT_COPY;
|
|
152
|
+
int cssX, cssY;
|
|
153
|
+
screenToCSS(pt, cssX, cssY);
|
|
154
|
+
execScript(
|
|
155
|
+
"(function(){"
|
|
156
|
+
"var el=document.elementFromPoint(" + std::to_string(cssX) + "," + std::to_string(cssY) + ");"
|
|
157
|
+
"var zone=el&&el.closest?el.closest('[data-dropzone]'):null;"
|
|
158
|
+
"document.querySelectorAll('.dropzone-active,.filedrop-active')"
|
|
159
|
+
".forEach(function(z){if(z!==zone){z.classList.remove('dropzone-active');"
|
|
160
|
+
"z.classList.remove('filedrop-active');}});"
|
|
161
|
+
"if(zone){zone.classList.add('dropzone-active');zone.classList.add('filedrop-active');}"
|
|
162
|
+
"})();"
|
|
163
|
+
);
|
|
164
|
+
} else {
|
|
165
|
+
*pdwEffect = DROPEFFECT_NONE;
|
|
166
|
+
}
|
|
167
|
+
return S_OK;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override {
|
|
171
|
+
if (m_hasFiles) {
|
|
172
|
+
*pdwEffect = DROPEFFECT_COPY;
|
|
173
|
+
int cssX, cssY;
|
|
174
|
+
screenToCSS(pt, cssX, cssY);
|
|
175
|
+
execScript(
|
|
176
|
+
"(function(){"
|
|
177
|
+
"var el=document.elementFromPoint(" + std::to_string(cssX) + "," + std::to_string(cssY) + ");"
|
|
178
|
+
"var zone=el&&el.closest?el.closest('[data-dropzone]'):null;"
|
|
179
|
+
"document.querySelectorAll('.dropzone-active,.filedrop-active')"
|
|
180
|
+
".forEach(function(z){if(z!==zone){z.classList.remove('dropzone-active');"
|
|
181
|
+
"z.classList.remove('filedrop-active');}});"
|
|
182
|
+
"if(zone){zone.classList.add('dropzone-active');zone.classList.add('filedrop-active');}"
|
|
183
|
+
"})();"
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
*pdwEffect = DROPEFFECT_NONE;
|
|
187
|
+
}
|
|
188
|
+
return S_OK;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
HRESULT STDMETHODCALLTYPE DragLeave() override {
|
|
192
|
+
execScript(
|
|
193
|
+
"(function(){"
|
|
194
|
+
"document.querySelectorAll('.dropzone-active,.filedrop-active')"
|
|
195
|
+
".forEach(function(z){z.classList.remove('dropzone-active');"
|
|
196
|
+
"z.classList.remove('filedrop-active');});"
|
|
197
|
+
"})();"
|
|
198
|
+
);
|
|
199
|
+
return S_OK;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
HRESULT STDMETHODCALLTYPE Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) override {
|
|
203
|
+
if (!pDataObj || !m_executeScript) return E_FAIL;
|
|
204
|
+
|
|
205
|
+
FORMATETC fmt = {CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
|
206
|
+
STGMEDIUM stg = {};
|
|
207
|
+
|
|
208
|
+
if (pDataObj->GetData(&fmt, &stg) != S_OK) return E_FAIL;
|
|
209
|
+
|
|
210
|
+
HDROP hDrop = static_cast<HDROP>(stg.hGlobal);
|
|
211
|
+
std::string filesJson = "[";
|
|
212
|
+
UINT fileCount = DragQueryFileW(hDrop, 0xFFFFFFFF, nullptr, 0);
|
|
213
|
+
|
|
214
|
+
for (UINT i = 0; i < fileCount; ++i) {
|
|
215
|
+
UINT pathLen = DragQueryFileW(hDrop, i, nullptr, 0);
|
|
216
|
+
std::wstring wpath(pathLen + 1, L'\0');
|
|
217
|
+
DragQueryFileW(hDrop, i, &wpath[0], pathLen + 1);
|
|
218
|
+
wpath.resize(pathLen);
|
|
219
|
+
|
|
220
|
+
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wpath.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
|
221
|
+
std::string path;
|
|
222
|
+
if (utf8Len > 0) {
|
|
223
|
+
path.resize(static_cast<size_t>(utf8Len - 1));
|
|
224
|
+
WideCharToMultiByte(CP_UTF8, 0, wpath.c_str(), -1, &path[0], utf8Len, nullptr, nullptr);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
std::string name = path;
|
|
228
|
+
size_t lastSlash = path.find_last_of("\\/");
|
|
229
|
+
if (lastSlash != std::string::npos) name = path.substr(lastSlash + 1);
|
|
230
|
+
|
|
231
|
+
unsigned long long sizeBytes = 0;
|
|
232
|
+
WIN32_FILE_ATTRIBUTE_DATA fileAttr;
|
|
233
|
+
if (GetFileAttributesExW(wpath.c_str(), GetFileExInfoStandard, &fileAttr)) {
|
|
234
|
+
ULARGE_INTEGER size;
|
|
235
|
+
size.HighPart = fileAttr.nFileSizeHigh;
|
|
236
|
+
size.LowPart = fileAttr.nFileSizeLow;
|
|
237
|
+
sizeBytes = size.QuadPart;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (i > 0) filesJson += ",";
|
|
241
|
+
filesJson += "{\"path\":\"" + jsonEscape(path) + "\",\"name\":\"" +
|
|
242
|
+
jsonEscape(name) + "\",\"type\":\"" +
|
|
243
|
+
jsonEscape(mimeTypeFromPath(path)) +
|
|
244
|
+
"\",\"size\":" + std::to_string(sizeBytes) + "}";
|
|
245
|
+
}
|
|
246
|
+
filesJson += "]";
|
|
247
|
+
ReleaseStgMedium(&stg);
|
|
248
|
+
|
|
249
|
+
int cssX, cssY;
|
|
250
|
+
screenToCSS(pt, cssX, cssY);
|
|
251
|
+
|
|
252
|
+
std::string eventScript =
|
|
253
|
+
"(function(){"
|
|
254
|
+
"var files=" + filesJson + ";"
|
|
255
|
+
"document.querySelectorAll('.dropzone-active,.filedrop-active')"
|
|
256
|
+
".forEach(function(z){z.classList.remove('dropzone-active');"
|
|
257
|
+
"z.classList.remove('filedrop-active');});"
|
|
258
|
+
"window.dispatchEvent(new CustomEvent('plusui:fileDrop.filesDropped',"
|
|
259
|
+
" { detail: { files: files } }));"
|
|
260
|
+
"var el=document.elementFromPoint(" + std::to_string(cssX) + "," + std::to_string(cssY) + ");"
|
|
261
|
+
"var zone=el&&el.closest?el.closest('[data-dropzone]'):null;"
|
|
262
|
+
"var zoneName=zone?zone.getAttribute('data-dropzone'):null;"
|
|
263
|
+
"if(zoneName&&window.__plusui_fileDrop__){"
|
|
264
|
+
"window.__plusui_fileDrop__(zoneName,files);"
|
|
265
|
+
"}"
|
|
266
|
+
"})();";
|
|
267
|
+
|
|
268
|
+
execScript(eventScript);
|
|
269
|
+
*pdwEffect = DROPEFFECT_COPY;
|
|
270
|
+
return S_OK;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
#endif
|
|
274
|
+
|
|
275
|
+
void FileDrop::attach(void* nativeWindowHandle, void* nativeWebViewHandle) {
|
|
276
|
+
pImpl->nativeWindowHandle = nativeWindowHandle;
|
|
277
|
+
pImpl->nativeWebViewHandle = nativeWebViewHandle;
|
|
278
|
+
if (!pImpl->enabled) return;
|
|
279
|
+
|
|
280
|
+
#ifdef _WIN32
|
|
281
|
+
HWND hwnd = static_cast<HWND>(nativeWindowHandle);
|
|
282
|
+
DragAcceptFiles(hwnd, TRUE);
|
|
283
|
+
FileDropTarget* target = new FileDropTarget(hwnd, nativeWebViewHandle);
|
|
284
|
+
pImpl->dropTarget = target;
|
|
285
|
+
RegisterDragDrop(hwnd, target);
|
|
286
|
+
|
|
287
|
+
#elif defined(__APPLE__)
|
|
288
|
+
// We let the objective-c wrapper handle this via PlusUIView
|
|
289
|
+
// No need to redeclare standard drag drops here if it requires subclassing
|
|
290
|
+
#elif defined(__linux__)
|
|
291
|
+
WebKitWebView* webView = static_cast<WebKitWebView*>(nativeWebViewHandle);
|
|
292
|
+
if (!webView) return;
|
|
293
|
+
|
|
294
|
+
gtk_drag_dest_set(GTK_WIDGET(webView), GTK_DEST_DEFAULT_ALL, nullptr, 0, GDK_ACTION_COPY);
|
|
295
|
+
gtk_drag_dest_add_uri_targets(GTK_WIDGET(webView));
|
|
296
|
+
|
|
297
|
+
pImpl->dc = new Impl::DropCtx{webView, false, 0, 0};
|
|
298
|
+
|
|
299
|
+
// drag-motion
|
|
300
|
+
static auto sDragMotion = +[](GtkWidget *, GdkDragContext *ctx, gint x, gint y, guint time, gpointer data) -> gboolean {
|
|
301
|
+
auto *dc = static_cast<Impl::DropCtx *>(data);
|
|
302
|
+
dc->x = x; dc->y = y;
|
|
303
|
+
if (!dc->entered) {
|
|
304
|
+
dc->entered = true;
|
|
305
|
+
}
|
|
306
|
+
gdk_drag_status(ctx, GDK_ACTION_COPY, time);
|
|
307
|
+
|
|
308
|
+
std::string script = "(function(){"
|
|
309
|
+
"var el=document.elementFromPoint(" + std::to_string(x) + "," + std::to_string(y) + ");"
|
|
310
|
+
"var zone=el&&el.closest?el.closest('[data-dropzone]'):null;"
|
|
311
|
+
"document.querySelectorAll('.dropzone-active,.filedrop-active')"
|
|
312
|
+
".forEach(function(z){if(z!==zone){z.classList.remove('dropzone-active');"
|
|
313
|
+
"z.classList.remove('filedrop-active');}});"
|
|
314
|
+
"if(zone){zone.classList.add('dropzone-active');zone.classList.add('filedrop-active');}"
|
|
315
|
+
"})();";
|
|
316
|
+
webkit_web_view_run_javascript(dc->wv, script.c_str(), nullptr, nullptr, nullptr);
|
|
317
|
+
|
|
318
|
+
return TRUE;
|
|
319
|
+
};
|
|
320
|
+
g_signal_connect(GTK_WIDGET(webView), "drag-motion", G_CALLBACK(sDragMotion), pImpl->dc);
|
|
321
|
+
|
|
322
|
+
// drag-leave
|
|
323
|
+
static auto sDragLeave = +[](GtkWidget *, GdkDragContext *, guint, gpointer data) -> void {
|
|
324
|
+
auto *dc = static_cast<Impl::DropCtx *>(data);
|
|
325
|
+
dc->entered = false;
|
|
326
|
+
std::string script = "(function(){"
|
|
327
|
+
"document.querySelectorAll('.dropzone-active,.filedrop-active')"
|
|
328
|
+
".forEach(function(z){z.classList.remove('dropzone-active');"
|
|
329
|
+
"z.classList.remove('filedrop-active');});})();";
|
|
330
|
+
webkit_web_view_run_javascript(dc->wv, script.c_str(), nullptr, nullptr, nullptr);
|
|
331
|
+
};
|
|
332
|
+
g_signal_connect(GTK_WIDGET(webView), "drag-leave", G_CALLBACK(sDragLeave), pImpl->dc);
|
|
333
|
+
|
|
334
|
+
// drag-drop
|
|
335
|
+
static auto sDragDrop = +[](GtkWidget *widget, GdkDragContext *ctx, gint x, gint y, guint time, gpointer data) -> gboolean {
|
|
336
|
+
auto *dc = static_cast<Impl::DropCtx *>(data);
|
|
337
|
+
dc->x = x; dc->y = y;
|
|
338
|
+
GdkAtom target = gtk_drag_dest_find_target(widget, ctx, nullptr);
|
|
339
|
+
if (target == GDK_NONE) { gtk_drag_finish(ctx, FALSE, FALSE, time); return TRUE; }
|
|
340
|
+
gtk_drag_get_data(widget, ctx, target, time);
|
|
341
|
+
return TRUE;
|
|
342
|
+
};
|
|
343
|
+
g_signal_connect(GTK_WIDGET(webView), "drag-drop", G_CALLBACK(sDragDrop), pImpl->dc);
|
|
344
|
+
|
|
345
|
+
// drag-data-received
|
|
346
|
+
static auto sDragDataRcv = +[](GtkWidget *, GdkDragContext *ctx, gint, gint, GtkSelectionData *sel, guint, guint time, gpointer data) -> void {
|
|
347
|
+
auto *dc = static_cast<Impl::DropCtx *>(data);
|
|
348
|
+
dc->entered = false;
|
|
349
|
+
gchar **uris = gtk_selection_data_get_uris(sel);
|
|
350
|
+
if (!uris) { gtk_drag_finish(ctx, FALSE, FALSE, time); return; }
|
|
351
|
+
|
|
352
|
+
std::string filesJson = "[";
|
|
353
|
+
bool first = true;
|
|
354
|
+
for (int i = 0; uris[i]; ++i) {
|
|
355
|
+
GFile *gf = g_file_new_for_uri(uris[i]);
|
|
356
|
+
gchar *path = g_file_get_path(gf);
|
|
357
|
+
if (!path) { g_object_unref(gf); continue; }
|
|
358
|
+
|
|
359
|
+
GFileInfo *fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NONE, nullptr, nullptr);
|
|
360
|
+
guint64 fsize = fi ? (guint64)g_file_info_get_size(fi) : 0;
|
|
361
|
+
if (fi) g_object_unref(fi);
|
|
362
|
+
|
|
363
|
+
std::string fp(path);
|
|
364
|
+
std::string nm = fp.substr(fp.rfind('/') + 1);
|
|
365
|
+
|
|
366
|
+
gboolean uncertain = FALSE;
|
|
367
|
+
gchar *ct = g_content_type_guess(path, nullptr, 0, &uncertain);
|
|
368
|
+
std::string mime = ct ? std::string(ct) : "application/octet-stream";
|
|
369
|
+
g_free(ct); g_free(path); g_object_unref(gf);
|
|
370
|
+
|
|
371
|
+
if (!first) filesJson += ",";
|
|
372
|
+
filesJson += "{\"path\":\"" + jsonEscape(fp) + "\","
|
|
373
|
+
"\"name\":\"" + jsonEscape(nm) + "\","
|
|
374
|
+
"\"type\":\"" + jsonEscape(mime) + "\","
|
|
375
|
+
"\"size\":" + std::to_string(fsize) + "}";
|
|
376
|
+
first = false;
|
|
377
|
+
}
|
|
378
|
+
filesJson += "]";
|
|
379
|
+
g_strfreev(uris);
|
|
380
|
+
if (first) { gtk_drag_finish(ctx, FALSE, FALSE, time); return; }
|
|
381
|
+
|
|
382
|
+
gint x = dc->x, y = dc->y;
|
|
383
|
+
std::string script =
|
|
384
|
+
"(function(){"
|
|
385
|
+
"var files=" + filesJson + ";"
|
|
386
|
+
"document.querySelectorAll('.dropzone-active,.filedrop-active')"
|
|
387
|
+
".forEach(function(z){z.classList.remove('dropzone-active');"
|
|
388
|
+
"z.classList.remove('filedrop-active');});"
|
|
389
|
+
"window.dispatchEvent(new CustomEvent('plusui:fileDrop.filesDropped',"
|
|
390
|
+
" { detail: { files: files } }));"
|
|
391
|
+
"var el=document.elementFromPoint(" + std::to_string(x) + "," + std::to_string(y) + ");"
|
|
392
|
+
"var zone=el&&el.closest?el.closest('[data-dropzone]'):null;"
|
|
393
|
+
"var zoneName=zone?zone.getAttribute('data-dropzone'):null;"
|
|
394
|
+
"if(zoneName&&window.__plusui_fileDrop__){"
|
|
395
|
+
"window.__plusui_fileDrop__(zoneName,files);"
|
|
396
|
+
"}"
|
|
397
|
+
"})();";
|
|
398
|
+
|
|
399
|
+
webkit_web_view_run_javascript(dc->wv, script.c_str(), nullptr, nullptr, nullptr);
|
|
400
|
+
gtk_drag_finish(ctx, TRUE, FALSE, time);
|
|
401
|
+
};
|
|
402
|
+
g_signal_connect(GTK_WIDGET(webView), "drag-data-received", G_CALLBACK(sDragDataRcv), pImpl->dc);
|
|
403
|
+
#endif
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
void FileDrop::setEnabled(bool enabled) {
|
|
407
|
+
std::lock_guard<std::mutex> lock(pImpl->mtx);
|
|
408
|
+
pImpl->enabled = enabled;
|
|
409
|
+
#ifdef _WIN32
|
|
410
|
+
if (pImpl->nativeWindowHandle) {
|
|
411
|
+
DragAcceptFiles(static_cast<HWND>(pImpl->nativeWindowHandle), enabled ? TRUE : FALSE);
|
|
412
|
+
}
|
|
413
|
+
#endif
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
bool FileDrop::isEnabled() const {
|
|
417
|
+
std::lock_guard<std::mutex> lock(pImpl->mtx);
|
|
418
|
+
return pImpl->enabled;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
void FileDrop::onFilesDropped(std::function<void(const std::vector<FileInfo>&)> callback) {
|
|
422
|
+
std::lock_guard<std::mutex> lock(pImpl->mtx);
|
|
423
|
+
pImpl->filesDroppedCallback = callback;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
void FileDrop::onDragEnter(std::function<void()> callback) {
|
|
427
|
+
std::lock_guard<std::mutex> lock(pImpl->mtx);
|
|
428
|
+
pImpl->dragEnterCallback = callback;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
void FileDrop::onDragLeave(std::function<void()> callback) {
|
|
432
|
+
std::lock_guard<std::mutex> lock(pImpl->mtx);
|
|
433
|
+
pImpl->dragLeaveCallback = callback;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
bool FileDrop::startDrag(const std::vector<std::string>& filePaths) {
|
|
437
|
+
if (filePaths.empty()) return false;
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
void FileDrop::clearCallbacks() {
|
|
442
|
+
std::lock_guard<std::mutex> lock(pImpl->mtx);
|
|
443
|
+
pImpl->filesDroppedCallback = nullptr;
|
|
444
|
+
pImpl->dragEnterCallback = nullptr;
|
|
445
|
+
pImpl->dragLeaveCallback = nullptr;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
} // namespace plusui
|