bunite-core 0.0.1 → 0.0.4
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/package.json +3 -2
- package/src/bun/core/App.ts +155 -15
- package/src/bun/core/BrowserView.ts +124 -44
- package/src/bun/core/BrowserWindow.ts +94 -47
- package/src/bun/core/Socket.ts +2 -1
- package/src/bun/core/SurfaceBrowserIPC.ts +65 -0
- package/src/bun/core/SurfaceManager.ts +201 -0
- package/src/bun/core/SurfaceRegistry.ts +60 -0
- package/src/bun/core/Utils.ts +275 -46
- package/src/bun/events/appEvents.ts +2 -1
- package/src/bun/events/webviewEvents.ts +1 -3
- package/src/bun/events/windowEvents.ts +2 -0
- package/src/bun/index.ts +4 -3
- package/src/bun/preload/inline.ts +19 -25
- package/src/bun/proc/native.ts +158 -122
- package/src/native/shared/callbacks.h +6 -6
- package/src/native/shared/ffi_exports.h +123 -119
- package/src/native/shared/log.h +24 -0
- package/src/native/shared/webview_storage.h +5 -5
- package/src/native/win/native_host_appres.cpp +258 -0
- package/src/native/win/native_host_cef.cpp +834 -0
- package/src/native/win/native_host_ffi.cpp +935 -0
- package/src/native/win/native_host_internal.h +285 -0
- package/src/native/win/native_host_runtime.cpp +286 -0
- package/src/native/win/native_host_utils.cpp +314 -0
- package/src/native/win/process_helper_win.cpp +126 -26
- package/src/preload/runtime.built.js +1 -1
- package/src/preload/runtime.ts +65 -42
- package/src/preload/tsconfig.json +2 -1
- package/src/preload/tsconfig.tsbuildinfo +1 -0
- package/src/preload/webviewElement.ts +307 -0
- package/src/shared/cefVersion.ts +2 -0
- package/src/shared/log.ts +40 -0
- package/src/shared/paths.ts +122 -52
- package/src/shared/rpc.ts +7 -1
- package/src/shared/webviewPolyfill.ts +80 -0
- package/src/view/index.ts +8 -5
- package/src/native/shared/cef_response_filter.h +0 -116
- package/src/native/win/native_host.cpp +0 -2453
- package/src/types/config.ts +0 -29
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
#include "native_host_internal.h"
|
|
2
|
+
|
|
3
|
+
using bunite_win::runOnUiThreadSync;
|
|
4
|
+
using bunite_win::runOnCefUiThreadSync;
|
|
5
|
+
|
|
6
|
+
static constexpr int32_t BUNITE_ABI_VERSION = 2;
|
|
7
|
+
|
|
8
|
+
extern "C" BUNITE_EXPORT int32_t bunite_abi_version(void) {
|
|
9
|
+
return BUNITE_ABI_VERSION;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
extern "C" BUNITE_EXPORT void bunite_set_log_level(int32_t level) {
|
|
13
|
+
buniteSetLogLevel(static_cast<BuniteLogLevel>(level));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
extern "C" BUNITE_EXPORT bool bunite_init(
|
|
17
|
+
const char* process_helper_path,
|
|
18
|
+
const char* cef_dir,
|
|
19
|
+
bool hide_console,
|
|
20
|
+
bool popup_blocking,
|
|
21
|
+
const char* chromium_flags_json
|
|
22
|
+
) {
|
|
23
|
+
{
|
|
24
|
+
std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
25
|
+
if (g_runtime.initialized) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
g_runtime.init_finished = false;
|
|
29
|
+
g_runtime.init_success = false;
|
|
30
|
+
g_runtime.shutdown_complete = false;
|
|
31
|
+
g_runtime.shutdown_finalize_posted.store(false);
|
|
32
|
+
g_runtime.shutting_down.store(false);
|
|
33
|
+
g_runtime.process_helper_path = process_helper_path ? process_helper_path : "";
|
|
34
|
+
g_runtime.cef_dir = cef_dir ? cef_dir : "";
|
|
35
|
+
g_runtime.popup_blocking = popup_blocking;
|
|
36
|
+
g_runtime.chromium_flags = bunite_win::parseChromiumFlagsJson(
|
|
37
|
+
chromium_flags_json ? chromium_flags_json : "");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (hide_console) {
|
|
41
|
+
if (HWND console = GetConsoleWindow()) {
|
|
42
|
+
ShowWindow(console, SW_HIDE);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
g_runtime.ui_thread = std::thread(bunite_win::uiThreadMain);
|
|
47
|
+
|
|
48
|
+
std::unique_lock<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
49
|
+
g_runtime.lifecycle_cv.wait(lock, []() { return g_runtime.init_finished; });
|
|
50
|
+
const bool init_success = g_runtime.init_success;
|
|
51
|
+
lock.unlock();
|
|
52
|
+
|
|
53
|
+
if (!init_success && g_runtime.ui_thread.joinable()) {
|
|
54
|
+
g_runtime.ui_thread.join();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return init_success;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
extern "C" BUNITE_EXPORT void bunite_run_loop(void) {
|
|
61
|
+
// The native UI thread owns the Win32 + CEF loop after bunite_init succeeds.
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
extern "C" BUNITE_EXPORT void bunite_free_cstring(const char* value) {
|
|
65
|
+
std::free(const_cast<char*>(value));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
extern "C" BUNITE_EXPORT void bunite_quit(void) {
|
|
69
|
+
{
|
|
70
|
+
std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
71
|
+
if (!g_runtime.initialized) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (g_runtime.shutting_down.load()) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
g_runtime.shutting_down.store(true);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
bunite_win::postCefUiTask([]() {
|
|
81
|
+
bunite_win::cancelPendingPermissionRequestsOnUiThread();
|
|
82
|
+
bunite_win::cancelPendingRouteRequestsOnUiThread();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
runOnUiThreadSync<void>([]() {
|
|
86
|
+
bunite_win::closeAllWindowsForShutdown();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
bunite_win::postCefUiTask([]() {
|
|
90
|
+
bunite_win::maybeCompleteShutdownOnUiThread();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
bool shutdown_completed = false;
|
|
94
|
+
{
|
|
95
|
+
std::unique_lock<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
96
|
+
shutdown_completed = g_runtime.lifecycle_cv.wait_for(
|
|
97
|
+
lock,
|
|
98
|
+
std::chrono::seconds(5),
|
|
99
|
+
[]() { return g_runtime.shutdown_complete; }
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (!shutdown_completed) {
|
|
103
|
+
BUNITE_WARN("Shutdown timed out, posting finalize.");
|
|
104
|
+
if (g_runtime.message_window) {
|
|
105
|
+
PostMessageW(g_runtime.message_window, kFinalizeShutdownMessage, 0, 0);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
shutdown_completed = g_runtime.lifecycle_cv.wait_for(
|
|
109
|
+
lock,
|
|
110
|
+
std::chrono::milliseconds(500),
|
|
111
|
+
[]() { return g_runtime.shutdown_complete; }
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!shutdown_completed) {
|
|
116
|
+
BUNITE_WARN("Finalize timed out, forcing message loop exit.");
|
|
117
|
+
if (g_runtime.ui_thread_id != 0) {
|
|
118
|
+
PostThreadMessageW(g_runtime.ui_thread_id, WM_QUIT, 0, 0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
shutdown_completed = g_runtime.lifecycle_cv.wait_for(
|
|
122
|
+
lock,
|
|
123
|
+
std::chrono::milliseconds(1000),
|
|
124
|
+
[]() { return g_runtime.shutdown_complete; }
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (g_runtime.ui_thread.joinable()) {
|
|
130
|
+
if (shutdown_completed) {
|
|
131
|
+
g_runtime.ui_thread.join();
|
|
132
|
+
} else {
|
|
133
|
+
BUNITE_WARN("UI thread did not exit, detaching.");
|
|
134
|
+
g_runtime.ui_thread.detach();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
extern "C" BUNITE_EXPORT void bunite_set_webview_event_handler(BuniteWebviewEventHandler handler) {
|
|
140
|
+
std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
141
|
+
g_runtime.webview_event_handler = handler;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
extern "C" BUNITE_EXPORT void bunite_set_window_event_handler(BuniteWindowEventHandler handler) {
|
|
145
|
+
std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
146
|
+
g_runtime.window_event_handler = handler;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
extern "C" BUNITE_EXPORT bool bunite_window_create(
|
|
150
|
+
uint32_t window_id,
|
|
151
|
+
double x,
|
|
152
|
+
double y,
|
|
153
|
+
double width,
|
|
154
|
+
double height,
|
|
155
|
+
const char* title,
|
|
156
|
+
const char* title_bar_style,
|
|
157
|
+
bool transparent,
|
|
158
|
+
bool hidden,
|
|
159
|
+
bool minimized,
|
|
160
|
+
bool maximized
|
|
161
|
+
) {
|
|
162
|
+
return runOnUiThreadSync<bool>([=]() -> bool {
|
|
163
|
+
auto* window = new WindowHost{
|
|
164
|
+
window_id,
|
|
165
|
+
nullptr,
|
|
166
|
+
bunite_win::utf8ToWide(title ? title : ""),
|
|
167
|
+
bunite_win::utf8ToWide(title_bar_style ? title_bar_style : ""),
|
|
168
|
+
RECT{
|
|
169
|
+
static_cast<LONG>(x),
|
|
170
|
+
static_cast<LONG>(y),
|
|
171
|
+
static_cast<LONG>(x + width),
|
|
172
|
+
static_cast<LONG>(y + height)
|
|
173
|
+
},
|
|
174
|
+
transparent,
|
|
175
|
+
hidden,
|
|
176
|
+
minimized,
|
|
177
|
+
maximized,
|
|
178
|
+
false
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
window->hwnd = CreateWindowExW(
|
|
182
|
+
0,
|
|
183
|
+
kWindowClass,
|
|
184
|
+
window->title.c_str(),
|
|
185
|
+
bunite_win::makeWindowStyle(window->title_bar_style),
|
|
186
|
+
static_cast<int>(x),
|
|
187
|
+
static_cast<int>(y),
|
|
188
|
+
static_cast<int>(std::max(width, 100.0)),
|
|
189
|
+
static_cast<int>(std::max(height, 100.0)),
|
|
190
|
+
nullptr,
|
|
191
|
+
nullptr,
|
|
192
|
+
bunite_win::getCurrentModuleHandle(),
|
|
193
|
+
window
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
if (!window->hwnd) {
|
|
197
|
+
delete window;
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
{
|
|
202
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
203
|
+
g_runtime.windows_by_id[window_id] = window;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!hidden) {
|
|
207
|
+
ShowWindow(window->hwnd, minimized ? SW_SHOWMINIMIZED : (maximized ? SW_SHOWMAXIMIZED : SW_SHOW));
|
|
208
|
+
UpdateWindow(window->hwnd);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return true;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
extern "C" BUNITE_EXPORT void bunite_window_destroy(uint32_t window_id) {
|
|
216
|
+
runOnUiThreadSync<void>([window_id]() {
|
|
217
|
+
auto* window = bunite_win::getWindowHostById(window_id);
|
|
218
|
+
bunite_win::destroyWindowHost(window);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
extern "C" BUNITE_EXPORT void bunite_window_reset_close_pending(uint32_t window_id) {
|
|
223
|
+
runOnUiThreadSync<void>([window_id]() {
|
|
224
|
+
auto* window = bunite_win::getWindowHostById(window_id);
|
|
225
|
+
if (window) {
|
|
226
|
+
window->close_pending.store(false);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
extern "C" BUNITE_EXPORT void bunite_window_show(uint32_t window_id) {
|
|
232
|
+
runOnUiThreadSync<void>([window_id]() {
|
|
233
|
+
auto* window = bunite_win::getWindowHostById(window_id);
|
|
234
|
+
if (!window || !window->hwnd) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
window->hidden = false;
|
|
238
|
+
ShowWindow(
|
|
239
|
+
window->hwnd,
|
|
240
|
+
window->minimized ? SW_SHOWMINIMIZED : (window->maximized ? SW_SHOWMAXIMIZED : SW_SHOW)
|
|
241
|
+
);
|
|
242
|
+
SetForegroundWindow(window->hwnd);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
extern "C" BUNITE_EXPORT void bunite_window_close(uint32_t window_id) {
|
|
247
|
+
runOnUiThreadSync<void>([window_id]() {
|
|
248
|
+
auto* window = bunite_win::getWindowHostById(window_id);
|
|
249
|
+
if (!window || !window->hwnd) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
SendMessageW(window->hwnd, WM_CLOSE, 0, 0);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
extern "C" BUNITE_EXPORT void bunite_window_set_title(uint32_t window_id, const char* title) {
|
|
257
|
+
runOnUiThreadSync<void>([window_id, value = std::string(title ? title : "")]() {
|
|
258
|
+
auto* window = bunite_win::getWindowHostById(window_id);
|
|
259
|
+
if (!window || !window->hwnd) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
window->title = bunite_win::utf8ToWide(value);
|
|
263
|
+
SetWindowTextW(window->hwnd, window->title.c_str());
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
extern "C" BUNITE_EXPORT void bunite_window_minimize(uint32_t window_id) {
|
|
268
|
+
runOnUiThreadSync<void>([window_id]() {
|
|
269
|
+
auto* window = bunite_win::getWindowHostById(window_id);
|
|
270
|
+
if (!window || !window->hwnd) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
window->restore_maximized_after_minimize = window->maximized;
|
|
275
|
+
window->minimized = true;
|
|
276
|
+
window->maximized = false;
|
|
277
|
+
if (window->hidden) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
ShowWindow(window->hwnd, SW_MINIMIZE);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
extern "C" BUNITE_EXPORT void bunite_window_unminimize(uint32_t window_id) {
|
|
286
|
+
runOnUiThreadSync<void>([window_id]() {
|
|
287
|
+
auto* window = bunite_win::getWindowHostById(window_id);
|
|
288
|
+
if (!window || !window->hwnd) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
window->minimized = false;
|
|
293
|
+
if (window->hidden) {
|
|
294
|
+
window->maximized = window->restore_maximized_after_minimize;
|
|
295
|
+
window->restore_maximized_after_minimize = false;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
ShowWindow(window->hwnd, SW_RESTORE);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
extern "C" BUNITE_EXPORT bool bunite_window_is_minimized(uint32_t window_id) {
|
|
304
|
+
return runOnUiThreadSync<bool>([window_id]() -> bool {
|
|
305
|
+
auto* window = bunite_win::getWindowHostById(window_id);
|
|
306
|
+
if (!window || !window->hwnd) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
if (window->hidden) {
|
|
310
|
+
return window->minimized;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
window->minimized = IsIconic(window->hwnd) != 0;
|
|
314
|
+
return window->minimized;
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
extern "C" BUNITE_EXPORT void bunite_window_maximize(uint32_t window_id) {
|
|
319
|
+
runOnUiThreadSync<void>([window_id]() {
|
|
320
|
+
auto* window = bunite_win::getWindowHostById(window_id);
|
|
321
|
+
if (!window || !window->hwnd) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
window->minimized = false;
|
|
326
|
+
window->restore_maximized_after_minimize = false;
|
|
327
|
+
window->maximized = true;
|
|
328
|
+
if (window->hidden) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
ShowWindow(window->hwnd, SW_MAXIMIZE);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
extern "C" BUNITE_EXPORT void bunite_window_unmaximize(uint32_t window_id) {
|
|
337
|
+
runOnUiThreadSync<void>([window_id]() {
|
|
338
|
+
auto* window = bunite_win::getWindowHostById(window_id);
|
|
339
|
+
if (!window || !window->hwnd) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
window->minimized = false;
|
|
344
|
+
window->restore_maximized_after_minimize = false;
|
|
345
|
+
window->maximized = false;
|
|
346
|
+
if (window->hidden) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
ShowWindow(window->hwnd, SW_RESTORE);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
extern "C" BUNITE_EXPORT bool bunite_window_is_maximized(uint32_t window_id) {
|
|
355
|
+
return runOnUiThreadSync<bool>([window_id]() -> bool {
|
|
356
|
+
auto* window = bunite_win::getWindowHostById(window_id);
|
|
357
|
+
if (!window || !window->hwnd) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
if (window->hidden) {
|
|
361
|
+
return window->maximized;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
window->maximized = IsZoomed(window->hwnd) != 0;
|
|
365
|
+
return window->maximized;
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
extern "C" BUNITE_EXPORT void bunite_window_set_frame(
|
|
370
|
+
uint32_t window_id,
|
|
371
|
+
double x,
|
|
372
|
+
double y,
|
|
373
|
+
double width,
|
|
374
|
+
double height
|
|
375
|
+
) {
|
|
376
|
+
runOnUiThreadSync<void>([window_id, x, y, width, height]() {
|
|
377
|
+
auto* window = bunite_win::getWindowHostById(window_id);
|
|
378
|
+
if (!window || !window->hwnd) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
SetWindowPos(
|
|
383
|
+
window->hwnd,
|
|
384
|
+
nullptr,
|
|
385
|
+
static_cast<int>(x),
|
|
386
|
+
static_cast<int>(y),
|
|
387
|
+
static_cast<int>(std::max(width, 100.0)),
|
|
388
|
+
static_cast<int>(std::max(height, 100.0)),
|
|
389
|
+
SWP_NOZORDER | SWP_NOACTIVATE
|
|
390
|
+
);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
extern "C" BUNITE_EXPORT bool bunite_view_create(
|
|
395
|
+
uint32_t view_id,
|
|
396
|
+
uint32_t window_id,
|
|
397
|
+
const char* url,
|
|
398
|
+
const char* html,
|
|
399
|
+
const char* preload,
|
|
400
|
+
const char* appres_root,
|
|
401
|
+
const char* navigation_rules_json,
|
|
402
|
+
double x,
|
|
403
|
+
double y,
|
|
404
|
+
double width,
|
|
405
|
+
double height,
|
|
406
|
+
bool auto_resize,
|
|
407
|
+
bool sandbox,
|
|
408
|
+
const char* preload_origins_json
|
|
409
|
+
) {
|
|
410
|
+
auto origins = bunite_win::parseNavigationRulesJson(preload_origins_json ? preload_origins_json : "");
|
|
411
|
+
|
|
412
|
+
return runOnUiThreadSync<bool>([=, origins = std::move(origins)]() -> bool {
|
|
413
|
+
auto* window = bunite_win::getWindowHostById(window_id);
|
|
414
|
+
if (!window || !window->hwnd) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
auto* view = new ViewHost{
|
|
419
|
+
view_id,
|
|
420
|
+
window,
|
|
421
|
+
RECT{
|
|
422
|
+
static_cast<LONG>(x),
|
|
423
|
+
static_cast<LONG>(y),
|
|
424
|
+
static_cast<LONG>(x + width),
|
|
425
|
+
static_cast<LONG>(y + height)
|
|
426
|
+
},
|
|
427
|
+
url ? url : "",
|
|
428
|
+
html ? html : "",
|
|
429
|
+
preload ? preload : "",
|
|
430
|
+
appres_root ? appres_root : "",
|
|
431
|
+
bunite_win::parseNavigationRulesJson(navigation_rules_json ? navigation_rules_json : ""),
|
|
432
|
+
auto_resize ? static_cast<int>(ViewAnchorMode::Fill) : static_cast<int>(ViewAnchorMode::None),
|
|
433
|
+
0.0,
|
|
434
|
+
sandbox,
|
|
435
|
+
origins
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
{
|
|
439
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
440
|
+
g_runtime.views_by_id[view_id] = view;
|
|
441
|
+
window->views.push_back(view);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (!bunite_win::createBrowserForView(view)) {
|
|
445
|
+
{
|
|
446
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
447
|
+
g_runtime.views_by_id.erase(view_id);
|
|
448
|
+
window->views.erase(std::remove(window->views.begin(), window->views.end(), view), window->views.end());
|
|
449
|
+
}
|
|
450
|
+
delete view;
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return true;
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
extern "C" BUNITE_EXPORT void bunite_view_execute_javascript(uint32_t view_id, const char* script) {
|
|
459
|
+
bunite_win::postCefUiTask([view_id, code = std::string(script ? script : "")]() {
|
|
460
|
+
auto* view = bunite_win::getViewHostById(view_id);
|
|
461
|
+
if (!view || !view->browser || !view->browser->GetMainFrame()) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
view->browser->GetMainFrame()->ExecuteJavaScript(
|
|
465
|
+
code, view->browser->GetMainFrame()->GetURL(), 0
|
|
466
|
+
);
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
extern "C" BUNITE_EXPORT void bunite_view_load_url(uint32_t view_id, const char* url) {
|
|
471
|
+
bunite_win::postCefUiTask([view_id, next_url = std::string(url ? url : "")]() {
|
|
472
|
+
auto* view = bunite_win::getViewHostById(view_id);
|
|
473
|
+
if (!view) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
view->url = next_url;
|
|
478
|
+
view->html.clear();
|
|
479
|
+
bunite::WebviewContentStorage::instance().remove(view->id);
|
|
480
|
+
if (view->browser && view->browser->GetMainFrame()) {
|
|
481
|
+
view->browser->GetMainFrame()->LoadURL(next_url);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
extern "C" BUNITE_EXPORT void bunite_view_load_html(uint32_t view_id, const char* html) {
|
|
487
|
+
bunite_win::postCefUiTask([view_id, content = std::string(html ? html : "")]() {
|
|
488
|
+
auto* view = bunite_win::getViewHostById(view_id);
|
|
489
|
+
if (!view) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
view->html = content;
|
|
494
|
+
bunite::WebviewContentStorage::instance().set(view->id, content);
|
|
495
|
+
if (view->browser && view->browser->GetMainFrame()) {
|
|
496
|
+
view->browser->GetMainFrame()->LoadURL("appres://app.internal/internal/index.html");
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_visible(uint32_t view_id, bool visible) {
|
|
502
|
+
runOnUiThreadSync<void>([view_id, visible]() {
|
|
503
|
+
auto* view = bunite_win::getViewHostById(view_id);
|
|
504
|
+
if (!view) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
auto browser = view->browser;
|
|
508
|
+
if (!browser) {
|
|
509
|
+
view->pending_visible = visible;
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
HWND browser_hwnd = browser->GetHost()->GetWindowHandle();
|
|
513
|
+
if (browser_hwnd) {
|
|
514
|
+
ShowWindow(browser_hwnd, visible ? SW_SHOW : SW_HIDE);
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
extern "C" BUNITE_EXPORT void bunite_view_bring_to_front(uint32_t view_id) {
|
|
520
|
+
runOnUiThreadSync<void>([view_id]() {
|
|
521
|
+
auto* view = bunite_win::getViewHostById(view_id);
|
|
522
|
+
if (!view) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
auto browser = view->browser;
|
|
526
|
+
if (!browser) {
|
|
527
|
+
view->pending_bring_to_front = true;
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
HWND browser_hwnd = browser->GetHost()->GetWindowHandle();
|
|
531
|
+
if (browser_hwnd) {
|
|
532
|
+
SetWindowPos(browser_hwnd, HWND_TOP, 0, 0, 0, 0,
|
|
533
|
+
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_mask_region(
|
|
539
|
+
uint32_t view_id,
|
|
540
|
+
const double* rects,
|
|
541
|
+
uint32_t count
|
|
542
|
+
) {
|
|
543
|
+
std::vector<RECT> mask_rects;
|
|
544
|
+
mask_rects.reserve(count);
|
|
545
|
+
for (uint32_t i = 0; i < count; i++) {
|
|
546
|
+
const double* r = rects + i * 4;
|
|
547
|
+
mask_rects.push_back(RECT{
|
|
548
|
+
static_cast<LONG>(r[0]),
|
|
549
|
+
static_cast<LONG>(r[1]),
|
|
550
|
+
static_cast<LONG>(r[0] + r[2]),
|
|
551
|
+
static_cast<LONG>(r[1] + r[3])
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
runOnUiThreadSync<void>([view_id, mask_rects = std::move(mask_rects)]() {
|
|
556
|
+
auto* view = bunite_win::getViewHostById(view_id);
|
|
557
|
+
if (!view) return;
|
|
558
|
+
auto browser = view->browser;
|
|
559
|
+
if (!browser) return;
|
|
560
|
+
HWND hwnd = browser->GetHost()->GetWindowHandle();
|
|
561
|
+
if (!hwnd) return;
|
|
562
|
+
|
|
563
|
+
// Helper: apply region to a window and all its descendants
|
|
564
|
+
auto applyRegionToTree = [](HWND root, HRGN rgn) {
|
|
565
|
+
EnumChildWindows(root, [](HWND child, LPARAM lParam) -> BOOL {
|
|
566
|
+
HRGN src = reinterpret_cast<HRGN>(lParam);
|
|
567
|
+
RECT childRect;
|
|
568
|
+
GetWindowRect(child, &childRect);
|
|
569
|
+
HWND parent = GetParent(child);
|
|
570
|
+
POINT offset = { childRect.left, childRect.top };
|
|
571
|
+
if (parent) ScreenToClient(parent, &offset);
|
|
572
|
+
HRGN copy = CreateRectRgn(0, 0, 0, 0);
|
|
573
|
+
CombineRgn(copy, src, nullptr, RGN_COPY);
|
|
574
|
+
OffsetRgn(copy, -offset.x, -offset.y);
|
|
575
|
+
if (!SetWindowRgn(child, copy, TRUE)) {
|
|
576
|
+
DeleteObject(copy);
|
|
577
|
+
}
|
|
578
|
+
return TRUE;
|
|
579
|
+
}, reinterpret_cast<LPARAM>(rgn));
|
|
580
|
+
if (!SetWindowRgn(root, rgn, TRUE)) {
|
|
581
|
+
DeleteObject(rgn);
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
if (mask_rects.empty()) {
|
|
586
|
+
// Clear region — restore full window
|
|
587
|
+
SetWindowRgn(hwnd, nullptr, TRUE);
|
|
588
|
+
EnumChildWindows(hwnd, [](HWND child, LPARAM) -> BOOL {
|
|
589
|
+
SetWindowRgn(child, nullptr, TRUE);
|
|
590
|
+
return TRUE;
|
|
591
|
+
}, 0);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Start with the full window rect
|
|
596
|
+
RECT wr;
|
|
597
|
+
GetClientRect(hwnd, &wr);
|
|
598
|
+
HRGN full = CreateRectRgnIndirect(&wr);
|
|
599
|
+
|
|
600
|
+
// Subtract each mask rect (punch holes)
|
|
601
|
+
for (const auto& mr : mask_rects) {
|
|
602
|
+
RECT surface_relative = {
|
|
603
|
+
mr.left - view->bounds.left,
|
|
604
|
+
mr.top - view->bounds.top,
|
|
605
|
+
mr.right - view->bounds.left,
|
|
606
|
+
mr.bottom - view->bounds.top
|
|
607
|
+
};
|
|
608
|
+
HRGN hole = CreateRectRgnIndirect(&surface_relative);
|
|
609
|
+
CombineRgn(full, full, hole, RGN_DIFF);
|
|
610
|
+
DeleteObject(hole);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
applyRegionToTree(hwnd, full);
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_input_passthrough(uint32_t view_id, bool passthrough) {
|
|
618
|
+
runOnUiThreadSync<void>([view_id, passthrough]() {
|
|
619
|
+
auto* view = bunite_win::getViewHostById(view_id);
|
|
620
|
+
if (!view) return;
|
|
621
|
+
view->pending_passthrough = passthrough;
|
|
622
|
+
auto browser = view->browser;
|
|
623
|
+
if (!browser) return;
|
|
624
|
+
HWND hwnd = browser->GetHost()->GetWindowHandle();
|
|
625
|
+
if (!hwnd) return;
|
|
626
|
+
EnableWindow(hwnd, passthrough ? FALSE : TRUE);
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_bounds(
|
|
631
|
+
uint32_t view_id,
|
|
632
|
+
double x,
|
|
633
|
+
double y,
|
|
634
|
+
double width,
|
|
635
|
+
double height
|
|
636
|
+
) {
|
|
637
|
+
runOnUiThreadSync<void>([view_id, x, y, width, height]() {
|
|
638
|
+
auto* view = bunite_win::getViewHostById(view_id);
|
|
639
|
+
if (!view) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const RECT new_bounds = RECT{
|
|
643
|
+
static_cast<LONG>(x),
|
|
644
|
+
static_cast<LONG>(y),
|
|
645
|
+
static_cast<LONG>(x + width),
|
|
646
|
+
static_cast<LONG>(y + height)
|
|
647
|
+
};
|
|
648
|
+
auto browser = view->browser;
|
|
649
|
+
if (!browser) {
|
|
650
|
+
view->has_pending_bounds = true;
|
|
651
|
+
view->pending_bounds = new_bounds;
|
|
652
|
+
view->anchor_mode = static_cast<int>(ViewAnchorMode::None);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
view->anchor_mode = static_cast<int>(ViewAnchorMode::None);
|
|
656
|
+
view->bounds = new_bounds;
|
|
657
|
+
HWND browser_hwnd = browser->GetHost()->GetWindowHandle();
|
|
658
|
+
if (browser_hwnd) {
|
|
659
|
+
SetWindowPos(
|
|
660
|
+
browser_hwnd,
|
|
661
|
+
nullptr,
|
|
662
|
+
view->bounds.left,
|
|
663
|
+
view->bounds.top,
|
|
664
|
+
view->bounds.right - view->bounds.left,
|
|
665
|
+
view->bounds.bottom - view->bounds.top,
|
|
666
|
+
SWP_NOZORDER | SWP_NOACTIVATE
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_bounds_async(
|
|
673
|
+
uint32_t view_id,
|
|
674
|
+
double x,
|
|
675
|
+
double y,
|
|
676
|
+
double width,
|
|
677
|
+
double height
|
|
678
|
+
) {
|
|
679
|
+
bunite_win::postUiTask([view_id, x, y, width, height]() {
|
|
680
|
+
auto* view = bunite_win::getViewHostById(view_id);
|
|
681
|
+
if (!view) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
const RECT new_bounds = RECT{
|
|
685
|
+
static_cast<LONG>(x),
|
|
686
|
+
static_cast<LONG>(y),
|
|
687
|
+
static_cast<LONG>(x + width),
|
|
688
|
+
static_cast<LONG>(y + height)
|
|
689
|
+
};
|
|
690
|
+
auto browser = view->browser;
|
|
691
|
+
if (!browser) {
|
|
692
|
+
view->has_pending_bounds = true;
|
|
693
|
+
view->pending_bounds = new_bounds;
|
|
694
|
+
view->anchor_mode = static_cast<int>(ViewAnchorMode::None);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
view->anchor_mode = static_cast<int>(ViewAnchorMode::None);
|
|
698
|
+
view->bounds = new_bounds;
|
|
699
|
+
HWND browser_hwnd = browser->GetHost()->GetWindowHandle();
|
|
700
|
+
if (browser_hwnd) {
|
|
701
|
+
SetWindowPos(
|
|
702
|
+
browser_hwnd,
|
|
703
|
+
nullptr,
|
|
704
|
+
view->bounds.left,
|
|
705
|
+
view->bounds.top,
|
|
706
|
+
view->bounds.right - view->bounds.left,
|
|
707
|
+
view->bounds.bottom - view->bounds.top,
|
|
708
|
+
SWP_NOZORDER | SWP_NOACTIVATE
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
extern "C" BUNITE_EXPORT void bunite_register_appres_route(const char* path) {
|
|
715
|
+
bunite::AppResRouteStorage::instance().registerRoute(path ? path : "");
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
extern "C" BUNITE_EXPORT void bunite_unregister_appres_route(const char* path) {
|
|
719
|
+
bunite::AppResRouteStorage::instance().unregisterRoute(path ? path : "");
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
extern "C" BUNITE_EXPORT void bunite_complete_route_request(uint32_t request_id, const char* html) {
|
|
723
|
+
std::lock_guard<std::mutex> lock(g_runtime.route_mutex);
|
|
724
|
+
const auto it = g_runtime.pending_routes.find(request_id);
|
|
725
|
+
if (it == g_runtime.pending_routes.end()) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
auto pending = std::move(it->second);
|
|
730
|
+
g_runtime.pending_routes.erase(it);
|
|
731
|
+
|
|
732
|
+
bunite::AppResRouteStorage::instance().setResponse(request_id, html ? html : "");
|
|
733
|
+
|
|
734
|
+
if (pending.callback) {
|
|
735
|
+
pending.callback->Continue();
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_anchor(uint32_t view_id, int mode, double inset) {
|
|
740
|
+
runOnUiThreadSync<void>([view_id, mode, inset]() {
|
|
741
|
+
auto* view = bunite_win::getViewHostById(view_id);
|
|
742
|
+
if (!view) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
view->anchor_mode = mode;
|
|
746
|
+
view->anchor_inset = inset;
|
|
747
|
+
bunite_win::resizeViewToFit(view);
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
extern "C" BUNITE_EXPORT void bunite_view_go_back(uint32_t view_id) {
|
|
752
|
+
bunite_win::postCefUiTask([view_id]() {
|
|
753
|
+
auto* view = bunite_win::getViewHostById(view_id);
|
|
754
|
+
if (view && view->browser) {
|
|
755
|
+
view->browser->GoBack();
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
extern "C" BUNITE_EXPORT void bunite_view_reload(uint32_t view_id) {
|
|
761
|
+
bunite_win::postCefUiTask([view_id]() {
|
|
762
|
+
auto* view = bunite_win::getViewHostById(view_id);
|
|
763
|
+
if (view && view->browser) {
|
|
764
|
+
view->browser->Reload();
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
extern "C" BUNITE_EXPORT void bunite_view_remove(uint32_t view_id) {
|
|
770
|
+
bunite_win::postCefUiTask([view_id]() { bunite_win::closeViewHost(bunite_win::getViewHostById(view_id)); });
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
extern "C" BUNITE_EXPORT void bunite_view_open_devtools(uint32_t view_id) {
|
|
774
|
+
bunite_win::postCefUiTask([view_id]() { bunite_win::openDevToolsForView(bunite_win::getViewHostById(view_id)); });
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
extern "C" BUNITE_EXPORT void bunite_view_close_devtools(uint32_t view_id) {
|
|
778
|
+
bunite_win::postCefUiTask([view_id]() { bunite_win::closeDevToolsForView(bunite_win::getViewHostById(view_id)); });
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
extern "C" BUNITE_EXPORT void bunite_view_toggle_devtools(uint32_t view_id) {
|
|
782
|
+
bunite_win::postCefUiTask([view_id]() { bunite_win::toggleDevToolsForView(bunite_win::getViewHostById(view_id)); });
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
extern "C" BUNITE_EXPORT void bunite_complete_permission_request(uint32_t request_id, uint32_t state) {
|
|
786
|
+
bunite_win::postCefUiTask([=]() {
|
|
787
|
+
std::optional<PendingPermissionRequest> request;
|
|
788
|
+
{
|
|
789
|
+
std::lock_guard<std::mutex> lock(g_runtime.permission_mutex);
|
|
790
|
+
const auto it = g_runtime.pending_permissions.find(request_id);
|
|
791
|
+
if (it == g_runtime.pending_permissions.end()) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
request = it->second;
|
|
795
|
+
g_runtime.pending_permissions.erase(it);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (!request) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const bool allow = state != 0;
|
|
803
|
+
if (request->kind == PermissionRequestKind::Prompt && request->prompt_callback) {
|
|
804
|
+
request->prompt_callback->Continue(
|
|
805
|
+
allow ? CEF_PERMISSION_RESULT_ACCEPT : CEF_PERMISSION_RESULT_DENY
|
|
806
|
+
);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (request->kind == PermissionRequestKind::MediaAccess && request->media_callback) {
|
|
811
|
+
if (allow) {
|
|
812
|
+
request->media_callback->Continue(request->permissions);
|
|
813
|
+
} else {
|
|
814
|
+
request->media_callback->Cancel();
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
extern "C" BUNITE_EXPORT int32_t bunite_show_message_box(
|
|
821
|
+
uint32_t window_id,
|
|
822
|
+
const char* type,
|
|
823
|
+
const char* title,
|
|
824
|
+
const char* message,
|
|
825
|
+
const char* detail,
|
|
826
|
+
const char* buttons,
|
|
827
|
+
int32_t default_id,
|
|
828
|
+
int32_t cancel_id
|
|
829
|
+
) {
|
|
830
|
+
return runOnUiThreadSync<int32_t>([=]() -> int32_t {
|
|
831
|
+
std::string composed_message = message ? message : "";
|
|
832
|
+
if (detail && std::strlen(detail) > 0) {
|
|
833
|
+
if (!composed_message.empty()) {
|
|
834
|
+
composed_message += "\n\n";
|
|
835
|
+
}
|
|
836
|
+
composed_message += detail;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
UINT flags = MB_OK;
|
|
840
|
+
const std::string type_name = type ? type : "info";
|
|
841
|
+
if (type_name == "none") {
|
|
842
|
+
// Intentionally no icon flag.
|
|
843
|
+
} else if (type_name == "warning") {
|
|
844
|
+
flags |= MB_ICONWARNING;
|
|
845
|
+
} else if (type_name == "error") {
|
|
846
|
+
flags |= MB_ICONERROR;
|
|
847
|
+
} else if (type_name == "question") {
|
|
848
|
+
flags |= MB_ICONQUESTION;
|
|
849
|
+
} else {
|
|
850
|
+
flags |= MB_ICONINFORMATION;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const std::vector<std::string> labels = bunite_win::splitButtonLabels(buttons ? buttons : "");
|
|
854
|
+
std::vector<std::string> normalized_labels;
|
|
855
|
+
normalized_labels.reserve(labels.size());
|
|
856
|
+
for (const std::string& label : labels) {
|
|
857
|
+
normalized_labels.push_back(bunite_win::toLowerAscii(bunite_win::trimAsciiWhitespace(label)));
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
if (normalized_labels.size() == 2) {
|
|
861
|
+
if (normalized_labels[0] == "yes" && normalized_labels[1] == "no") {
|
|
862
|
+
flags = (flags & ~MB_OK) | MB_YESNO;
|
|
863
|
+
} else {
|
|
864
|
+
flags = (flags & ~MB_OK) | MB_OKCANCEL;
|
|
865
|
+
}
|
|
866
|
+
} else if (
|
|
867
|
+
normalized_labels.size() >= 3 &&
|
|
868
|
+
normalized_labels[0] == "yes" &&
|
|
869
|
+
normalized_labels[1] == "no" &&
|
|
870
|
+
normalized_labels[2] == "cancel"
|
|
871
|
+
) {
|
|
872
|
+
flags = (flags & ~MB_OK) | MB_YESNOCANCEL;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (default_id == 1) {
|
|
876
|
+
flags |= MB_DEFBUTTON2;
|
|
877
|
+
} else if (default_id >= 2) {
|
|
878
|
+
flags |= MB_DEFBUTTON3;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const std::wstring window_title = bunite_win::utf8ToWide(title ? title : "");
|
|
882
|
+
const std::wstring window_message = bunite_win::utf8ToWide(composed_message);
|
|
883
|
+
HWND owner = nullptr;
|
|
884
|
+
if (window_id != 0) {
|
|
885
|
+
auto* window = bunite_win::getWindowHostById(window_id);
|
|
886
|
+
if (window && window->hwnd && IsWindow(window->hwnd)) {
|
|
887
|
+
owner = window->hwnd;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Center the message box on the owner window via CBT hook.
|
|
892
|
+
static thread_local HWND s_center_on = nullptr;
|
|
893
|
+
static thread_local HHOOK s_cbt_hook = nullptr;
|
|
894
|
+
s_center_on = owner;
|
|
895
|
+
if (owner) {
|
|
896
|
+
s_cbt_hook = SetWindowsHookExW(WH_CBT, [](int code, WPARAM wp, LPARAM) -> LRESULT {
|
|
897
|
+
if (code == HCBT_ACTIVATE && s_center_on) {
|
|
898
|
+
HWND dlg = reinterpret_cast<HWND>(wp);
|
|
899
|
+
RECT owner_rect{}, dlg_rect{};
|
|
900
|
+
if (GetWindowRect(s_center_on, &owner_rect) && GetWindowRect(dlg, &dlg_rect)) {
|
|
901
|
+
int dw = dlg_rect.right - dlg_rect.left;
|
|
902
|
+
int dh = dlg_rect.bottom - dlg_rect.top;
|
|
903
|
+
int ow = owner_rect.right - owner_rect.left;
|
|
904
|
+
int oh = owner_rect.bottom - owner_rect.top;
|
|
905
|
+
SetWindowPos(dlg, nullptr,
|
|
906
|
+
owner_rect.left + (ow - dw) / 2,
|
|
907
|
+
owner_rect.top + (oh - dh) / 2,
|
|
908
|
+
0, 0, SWP_NOSIZE | SWP_NOZORDER);
|
|
909
|
+
}
|
|
910
|
+
s_center_on = nullptr;
|
|
911
|
+
}
|
|
912
|
+
return CallNextHookEx(s_cbt_hook, code, wp, 0);
|
|
913
|
+
}, nullptr, GetCurrentThreadId());
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const int result = MessageBoxW(owner, window_message.c_str(), window_title.c_str(), flags);
|
|
917
|
+
|
|
918
|
+
if (s_cbt_hook) {
|
|
919
|
+
UnhookWindowsHookEx(s_cbt_hook);
|
|
920
|
+
s_cbt_hook = nullptr;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
switch (result) {
|
|
924
|
+
case IDOK:
|
|
925
|
+
case IDYES:
|
|
926
|
+
return 0;
|
|
927
|
+
case IDNO:
|
|
928
|
+
return 1;
|
|
929
|
+
case IDCANCEL:
|
|
930
|
+
return cancel_id >= 0 ? cancel_id : (labels.size() > 2 ? 2 : 1);
|
|
931
|
+
default:
|
|
932
|
+
return cancel_id >= 0 ? cancel_id : -1;
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
}
|