bunite-core 0.6.0 → 0.8.1
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 +5 -2
- package/src/bun/core/App.ts +35 -34
- package/src/bun/core/BrowserView.ts +3 -9
- package/src/bun/core/singleInstanceLock.ts +91 -0
- package/src/bun/index.ts +5 -6
- package/src/bun/preload/inline.ts +1 -2
- package/src/bun/proc/native.ts +54 -78
- package/src/native/linux/bunite_linux_appres.cpp +173 -0
- package/src/native/linux/bunite_linux_ffi.cpp +263 -0
- package/src/native/linux/bunite_linux_internal.h +148 -0
- package/src/native/linux/bunite_linux_preload.cpp +1 -0
- package/src/native/linux/bunite_linux_runtime.cpp +120 -0
- package/src/native/linux/bunite_linux_utils.cpp +114 -0
- package/src/native/linux/bunite_linux_view.cpp +244 -0
- package/src/native/linux/bunite_linux_window.cpp +101 -0
- package/src/native/mac/bunite_mac_appres.mm +163 -0
- package/src/native/mac/bunite_mac_ffi.mm +470 -0
- package/src/native/mac/bunite_mac_internal.h +151 -0
- package/src/native/mac/bunite_mac_runtime.mm +15 -0
- package/src/native/mac/bunite_mac_utils.mm +121 -0
- package/src/native/mac/bunite_mac_view.mm +279 -0
- package/src/native/mac/bunite_mac_window.mm +187 -0
- package/src/native/shared/ffi_exports.h +13 -13
- package/src/native/shared/permissions.h +14 -0
- package/src/native/shared/webview_storage.h +6 -3
- package/src/native/win/native_host_cef.cpp +4 -8
- package/src/native/win/native_host_ffi.cpp +76 -123
- package/src/native/win/native_host_internal.h +5 -3
- package/src/native/win/native_host_runtime.cpp +2 -6
- package/src/native/win/native_host_utils.cpp +23 -52
- package/src/native/win/process_helper_win.cpp +1 -3
- package/src/preload/runtime.ts +1 -3
- package/src/preload/tsconfig.tsbuildinfo +1 -1
- package/src/preload/webviewElement.ts +3 -8
- package/src/shared/paths.ts +35 -44
- package/src/shared/platform.ts +1 -2
- package/src/shared/rpcDemux.ts +47 -2
- package/src/shared/webviewPolyfill.ts +64 -6
- package/src/bun/core/Utils.ts +0 -301
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// NSWindow lifecycle + delegate. Mirrors win/native_host_runtime.cpp window proc.
|
|
2
|
+
|
|
3
|
+
#import "bunite_mac_internal.h"
|
|
4
|
+
|
|
5
|
+
#include <sstream>
|
|
6
|
+
#include <vector>
|
|
7
|
+
|
|
8
|
+
using bunite_mac::g_runtime;
|
|
9
|
+
using bunite_mac::utf8ToNSString;
|
|
10
|
+
using bunite_mac::bottomLeftToTopLeft;
|
|
11
|
+
|
|
12
|
+
namespace {
|
|
13
|
+
|
|
14
|
+
NSString* kHiddenStyle = @"hidden";
|
|
15
|
+
NSString* kHiddenInsetStyle = @"hiddenInset";
|
|
16
|
+
|
|
17
|
+
NSUInteger styleMaskFor(NSString* tbs) {
|
|
18
|
+
NSUInteger base = NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable;
|
|
19
|
+
if ([tbs isEqualToString:kHiddenStyle]) {
|
|
20
|
+
return base | NSWindowStyleMaskFullSizeContentView; // no titled
|
|
21
|
+
}
|
|
22
|
+
return base | NSWindowStyleMaskTitled;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
std::string movePayload(NSWindow* w) {
|
|
26
|
+
double x, y, ww, hh;
|
|
27
|
+
bottomLeftToTopLeft(w.frame, &x, &y, &ww, &hh);
|
|
28
|
+
(void)ww; (void)hh;
|
|
29
|
+
std::ostringstream o;
|
|
30
|
+
o << "{\"x\":" << x << ",\"y\":" << y
|
|
31
|
+
<< ",\"maximized\":" << (w.zoomed ? "true" : "false")
|
|
32
|
+
<< ",\"minimized\":" << (w.miniaturized ? "true" : "false") << "}";
|
|
33
|
+
return o.str();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
std::string resizePayload(NSWindow* w) {
|
|
37
|
+
double x, y, ww, hh;
|
|
38
|
+
bottomLeftToTopLeft(w.frame, &x, &y, &ww, &hh);
|
|
39
|
+
std::ostringstream o;
|
|
40
|
+
o << "{\"x\":" << x << ",\"y\":" << y
|
|
41
|
+
<< ",\"width\":" << ww << ",\"height\":" << hh
|
|
42
|
+
<< ",\"maximized\":" << (w.zoomed ? "true" : "false")
|
|
43
|
+
<< ",\"minimized\":" << (w.miniaturized ? "true" : "false") << "}";
|
|
44
|
+
return o.str();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
} // namespace
|
|
48
|
+
|
|
49
|
+
// Flipped container so child NSViews use top-left coordinates (matches surface IPC).
|
|
50
|
+
@interface BuniteFlippedView : NSView
|
|
51
|
+
@end
|
|
52
|
+
@implementation BuniteFlippedView
|
|
53
|
+
- (BOOL)isFlipped { return YES; }
|
|
54
|
+
@end
|
|
55
|
+
|
|
56
|
+
@interface BuniteWindowDelegate : NSObject <NSWindowDelegate>
|
|
57
|
+
@property (nonatomic, assign) uint32_t window_id;
|
|
58
|
+
@end
|
|
59
|
+
|
|
60
|
+
@implementation BuniteWindowDelegate
|
|
61
|
+
|
|
62
|
+
- (BOOL)windowShouldClose:(NSWindow*)w {
|
|
63
|
+
(void)w;
|
|
64
|
+
bunite_mac::WindowState* state = bunite_mac::findWindow(self.window_id);
|
|
65
|
+
if (!state) return YES;
|
|
66
|
+
if (state->close_pending.exchange(true)) return NO; // already pending
|
|
67
|
+
bunite_mac::emitWindowEvent(self.window_id, "close-requested");
|
|
68
|
+
return NO; // JS responds with bunite_window_destroy or reset_close_pending
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
- (void)windowWillClose:(NSNotification*)n {
|
|
72
|
+
(void)n;
|
|
73
|
+
uint32_t id = self.window_id;
|
|
74
|
+
bunite_mac::emitWindowEvent(id, "close");
|
|
75
|
+
|
|
76
|
+
// Tear down child views before erasing the window so WKWebViews don't outlive their host.
|
|
77
|
+
std::vector<uint32_t> orphaned;
|
|
78
|
+
bool windows_empty = false;
|
|
79
|
+
{
|
|
80
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
81
|
+
for (auto& [vid, vst] : g_runtime.views) {
|
|
82
|
+
if (vst.window_id == id) orphaned.push_back(vid);
|
|
83
|
+
}
|
|
84
|
+
g_runtime.windows.erase(id);
|
|
85
|
+
windows_empty = g_runtime.windows.empty();
|
|
86
|
+
}
|
|
87
|
+
for (uint32_t vid : orphaned) bunite_mac::removeView(vid);
|
|
88
|
+
if (windows_empty && !g_runtime.shutting_down.load()) {
|
|
89
|
+
bunite_mac::emitWindowEvent(0, "all-windows-closed");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
- (void)windowDidBecomeKey:(NSNotification*)n {
|
|
94
|
+
(void)n;
|
|
95
|
+
bunite_mac::emitWindowEvent(self.window_id, "focus");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
- (void)windowDidResignKey:(NSNotification*)n {
|
|
99
|
+
(void)n;
|
|
100
|
+
bunite_mac::emitWindowEvent(self.window_id, "blur");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
- (void)windowDidMove:(NSNotification*)n {
|
|
104
|
+
bunite_mac::WindowState* state = bunite_mac::findWindow(self.window_id);
|
|
105
|
+
if (!state) return;
|
|
106
|
+
bunite_mac::emitWindowEvent(self.window_id, "move", movePayload(state->window));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
- (void)windowDidResize:(NSNotification*)n {
|
|
110
|
+
bunite_mac::WindowState* state = bunite_mac::findWindow(self.window_id);
|
|
111
|
+
if (!state) return;
|
|
112
|
+
bunite_mac::emitWindowEvent(self.window_id, "resize", resizePayload(state->window));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@end
|
|
116
|
+
|
|
117
|
+
namespace bunite_mac {
|
|
118
|
+
|
|
119
|
+
WindowState* findWindow(uint32_t window_id) {
|
|
120
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
121
|
+
auto it = g_runtime.windows.find(window_id);
|
|
122
|
+
return it == g_runtime.windows.end() ? nullptr : &it->second;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
bool createWindow(uint32_t window_id, double x, double y, double width, double height,
|
|
126
|
+
NSString* title, NSString* title_bar_style,
|
|
127
|
+
bool transparent, bool hidden, bool minimized, bool maximized) {
|
|
128
|
+
if (findWindow(window_id)) {
|
|
129
|
+
BUNITE_WARN("bunite_window_create: id %u already exists.", window_id);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
NSRect frame = topLeftToBottomLeft(x, y, width, height);
|
|
134
|
+
NSUInteger style = styleMaskFor(title_bar_style);
|
|
135
|
+
|
|
136
|
+
NSWindow* window = [[NSWindow alloc] initWithContentRect:frame
|
|
137
|
+
styleMask:style
|
|
138
|
+
backing:NSBackingStoreBuffered
|
|
139
|
+
defer:NO];
|
|
140
|
+
window.releasedWhenClosed = NO; // ARC __strong holds it; AppKit self-release would over-release.
|
|
141
|
+
window.title = title ?: @"";
|
|
142
|
+
window.contentView = [[BuniteFlippedView alloc] initWithFrame:frame];
|
|
143
|
+
|
|
144
|
+
if ([title_bar_style isEqualToString:kHiddenInsetStyle]) {
|
|
145
|
+
window.titlebarAppearsTransparent = YES;
|
|
146
|
+
window.titleVisibility = NSWindowTitleHidden;
|
|
147
|
+
}
|
|
148
|
+
if (transparent) {
|
|
149
|
+
window.opaque = NO;
|
|
150
|
+
window.backgroundColor = [NSColor clearColor];
|
|
151
|
+
window.hasShadow = NO;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
BuniteWindowDelegate* delegate = [[BuniteWindowDelegate alloc] init];
|
|
155
|
+
delegate.window_id = window_id;
|
|
156
|
+
window.delegate = delegate;
|
|
157
|
+
|
|
158
|
+
{
|
|
159
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
160
|
+
auto& state = g_runtime.windows[window_id];
|
|
161
|
+
state.window = window;
|
|
162
|
+
state.delegate = delegate;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!hidden) {
|
|
166
|
+
[window makeKeyAndOrderFront:nil];
|
|
167
|
+
[NSApp activateIgnoringOtherApps:YES];
|
|
168
|
+
}
|
|
169
|
+
if (minimized) [window miniaturize:nil];
|
|
170
|
+
if (maximized && !window.zoomed) [window zoom:nil];
|
|
171
|
+
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
void destroyWindow(uint32_t window_id) {
|
|
176
|
+
__strong NSWindow* window = nil;
|
|
177
|
+
{
|
|
178
|
+
std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
|
|
179
|
+
auto it = g_runtime.windows.find(window_id);
|
|
180
|
+
if (it == g_runtime.windows.end()) return;
|
|
181
|
+
window = it->second.window;
|
|
182
|
+
// Don't erase here — windowWillClose: does it after emitting "close".
|
|
183
|
+
}
|
|
184
|
+
if (window) [window close];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
} // namespace bunite_mac
|
|
@@ -18,13 +18,21 @@ extern "C" {
|
|
|
18
18
|
BUNITE_EXPORT int32_t bunite_abi_version(void);
|
|
19
19
|
BUNITE_EXPORT void bunite_set_log_level(int32_t level);
|
|
20
20
|
BUNITE_EXPORT bool bunite_init(
|
|
21
|
-
const char*
|
|
22
|
-
const char* cef_dir,
|
|
21
|
+
const char* engine_dir,
|
|
23
22
|
bool hide_console,
|
|
24
23
|
bool popup_blocking,
|
|
25
|
-
const char*
|
|
24
|
+
const char* engine_config_json
|
|
26
25
|
);
|
|
26
|
+
BUNITE_EXPORT const char* bunite_engine_name(void);
|
|
27
|
+
BUNITE_EXPORT const char* bunite_engine_version(void);
|
|
27
28
|
BUNITE_EXPORT void bunite_run_loop(void);
|
|
29
|
+
/**
|
|
30
|
+
* Drive one non-blocking iteration of the engine's UI event queue. Required on
|
|
31
|
+
* engines where the UI loop must share the main thread with Bun's libuv loop
|
|
32
|
+
* (macOS WKWebView, Linux WebKitGTK). On engines that own a dedicated UI thread
|
|
33
|
+
* (Windows CEF), this is a no-op.
|
|
34
|
+
*/
|
|
35
|
+
BUNITE_EXPORT void bunite_pump_once(void);
|
|
28
36
|
BUNITE_EXPORT void bunite_quit(void);
|
|
29
37
|
BUNITE_EXPORT void bunite_free_cstring(const char* value);
|
|
30
38
|
BUNITE_EXPORT void bunite_set_webview_event_handler(BuniteWebviewEventHandler handler);
|
|
@@ -85,6 +93,8 @@ BUNITE_EXPORT void bunite_register_appres_route(const char* path);
|
|
|
85
93
|
BUNITE_EXPORT void bunite_unregister_appres_route(const char* path);
|
|
86
94
|
BUNITE_EXPORT void bunite_complete_route_request(uint32_t request_id, const char* html);
|
|
87
95
|
BUNITE_EXPORT void bunite_view_set_visible(uint32_t view_id, bool visible);
|
|
96
|
+
BUNITE_EXPORT void bunite_view_set_input_passthrough(uint32_t view_id, bool passthrough);
|
|
97
|
+
BUNITE_EXPORT void bunite_view_set_mask_region(uint32_t view_id, const double* rects, uint32_t count);
|
|
88
98
|
BUNITE_EXPORT void bunite_view_bring_to_front(uint32_t view_id);
|
|
89
99
|
BUNITE_EXPORT void bunite_view_set_bounds(
|
|
90
100
|
uint32_t view_id,
|
|
@@ -108,16 +118,6 @@ BUNITE_EXPORT void bunite_view_open_devtools(uint32_t view_id);
|
|
|
108
118
|
BUNITE_EXPORT void bunite_view_close_devtools(uint32_t view_id);
|
|
109
119
|
BUNITE_EXPORT void bunite_view_toggle_devtools(uint32_t view_id);
|
|
110
120
|
BUNITE_EXPORT void bunite_complete_permission_request(uint32_t request_id, uint32_t state);
|
|
111
|
-
BUNITE_EXPORT int32_t bunite_show_message_box(
|
|
112
|
-
uint32_t window_id,
|
|
113
|
-
const char* type,
|
|
114
|
-
const char* title,
|
|
115
|
-
const char* message,
|
|
116
|
-
const char* detail,
|
|
117
|
-
const char* buttons,
|
|
118
|
-
int32_t default_id,
|
|
119
|
-
int32_t cancel_id
|
|
120
|
-
);
|
|
121
121
|
#ifdef __cplusplus
|
|
122
122
|
}
|
|
123
123
|
#endif
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <stdint.h>
|
|
4
|
+
|
|
5
|
+
// Engine-agnostic permission bitmask. Adapters translate to native types.
|
|
6
|
+
|
|
7
|
+
#define BUNITE_PERMISSION_MICROPHONE ((uint32_t)(1u << 0))
|
|
8
|
+
#define BUNITE_PERMISSION_CAMERA ((uint32_t)(1u << 1))
|
|
9
|
+
#define BUNITE_PERMISSION_GEOLOCATION ((uint32_t)(1u << 2))
|
|
10
|
+
#define BUNITE_PERMISSION_NOTIFICATIONS ((uint32_t)(1u << 3))
|
|
11
|
+
#define BUNITE_PERMISSION_CLIPBOARD ((uint32_t)(1u << 4))
|
|
12
|
+
#define BUNITE_PERMISSION_SCREEN_CAPTURE ((uint32_t)(1u << 5))
|
|
13
|
+
#define BUNITE_PERMISSION_POINTER_LOCK ((uint32_t)(1u << 6))
|
|
14
|
+
#define BUNITE_PERMISSION_MIDI_SYSEX ((uint32_t)(1u << 7))
|
|
@@ -27,6 +27,11 @@ public:
|
|
|
27
27
|
return it == content_.end() ? std::string{} : it->second;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
bool has(uint32_t webview_id) const {
|
|
31
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
32
|
+
return content_.find(webview_id) != content_.end();
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
void remove(uint32_t webview_id) {
|
|
31
36
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
32
37
|
content_.erase(webview_id);
|
|
@@ -39,9 +44,7 @@ private:
|
|
|
39
44
|
std::map<uint32_t, std::string> content_;
|
|
40
45
|
};
|
|
41
46
|
|
|
42
|
-
//
|
|
43
|
-
// The actual handler function lives in JS; this only stores the set of registered paths
|
|
44
|
-
// and completed route responses.
|
|
47
|
+
// Registered dynamic appres:// paths + completed route responses. JS owns the handler.
|
|
45
48
|
class AppResRouteStorage {
|
|
46
49
|
public:
|
|
47
50
|
static AppResRouteStorage& instance() {
|
|
@@ -102,8 +102,7 @@ public:
|
|
|
102
102
|
CefRefPtr<CefDictionaryValue>&,
|
|
103
103
|
bool*
|
|
104
104
|
) override {
|
|
105
|
-
//
|
|
106
|
-
// all go through BuniteDevToolsClient for proper shutdown sequencing.
|
|
105
|
+
// Route F12/Ctrl+Shift+I/Inspect through BuniteDevToolsClient for shutdown ordering.
|
|
107
106
|
client = new BuniteDevToolsClient();
|
|
108
107
|
}
|
|
109
108
|
|
|
@@ -273,7 +272,7 @@ public:
|
|
|
273
272
|
view_->id,
|
|
274
273
|
"permission-requested",
|
|
275
274
|
"{\"requestId\":" + std::to_string(request_id) +
|
|
276
|
-
",\"kind\":" + std::to_string(requested_permissions) +
|
|
275
|
+
",\"kind\":" + std::to_string(bunite_win::cefPermissionsToBuniteKind(requested_permissions)) +
|
|
277
276
|
",\"url\":\"" + bunite_win::escapeJsonString(requesting_origin.ToString()) + "\"}"
|
|
278
277
|
);
|
|
279
278
|
return true;
|
|
@@ -302,7 +301,7 @@ public:
|
|
|
302
301
|
view_->id,
|
|
303
302
|
"permission-requested",
|
|
304
303
|
"{\"requestId\":" + std::to_string(request_id) +
|
|
305
|
-
",\"kind\":" + std::to_string(requested_permissions) +
|
|
304
|
+
",\"kind\":" + std::to_string(bunite_win::cefMediaAccessToBuniteKind(requested_permissions)) +
|
|
306
305
|
",\"url\":\"" + bunite_win::escapeJsonString(requesting_origin.ToString()) + "\"}"
|
|
307
306
|
);
|
|
308
307
|
return true;
|
|
@@ -449,8 +448,7 @@ void closeViewHost(ViewHost* view) {
|
|
|
449
448
|
return;
|
|
450
449
|
}
|
|
451
450
|
|
|
452
|
-
// Browser not yet created — OnAfterCreated
|
|
453
|
-
// Safety net: if CreateBrowser failed entirely, clean up on CEF thread.
|
|
451
|
+
// Browser not yet created — OnAfterCreated handles closing; fallback cleanup if CreateBrowser failed.
|
|
454
452
|
postCefUiTask([view]() {
|
|
455
453
|
if (view->browser) {
|
|
456
454
|
view->browser->GetHost()->CloseBrowser(true);
|
|
@@ -819,8 +817,6 @@ bool createBrowserForView(ViewHost* view) {
|
|
|
819
817
|
}
|
|
820
818
|
}
|
|
821
819
|
|
|
822
|
-
// CreateBrowser (async) — can be called from any browser process thread.
|
|
823
|
-
// Browser instance will be available in OnAfterCreated callback.
|
|
824
820
|
return CefBrowserHost::CreateBrowser(
|
|
825
821
|
window_info,
|
|
826
822
|
view->client,
|
|
@@ -1,24 +1,89 @@
|
|
|
1
1
|
#include "native_host_internal.h"
|
|
2
2
|
|
|
3
|
+
#include "include/cef_version.h"
|
|
4
|
+
#include "include/cef_version_info.h"
|
|
5
|
+
|
|
3
6
|
using bunite_win::runOnUiThreadSync;
|
|
4
7
|
using bunite_win::runOnCefUiThreadSync;
|
|
5
8
|
|
|
6
|
-
static constexpr int32_t BUNITE_ABI_VERSION =
|
|
9
|
+
static constexpr int32_t BUNITE_ABI_VERSION = 4;
|
|
10
|
+
|
|
11
|
+
namespace {
|
|
12
|
+
|
|
13
|
+
std::string wideToUtf8(const std::wstring& value) {
|
|
14
|
+
if (value.empty()) return {};
|
|
15
|
+
const int bytes = WideCharToMultiByte(
|
|
16
|
+
CP_UTF8, 0, value.data(), static_cast<int>(value.size()), nullptr, 0, nullptr, nullptr);
|
|
17
|
+
std::string out(static_cast<size_t>(bytes), '\0');
|
|
18
|
+
WideCharToMultiByte(
|
|
19
|
+
CP_UTF8, 0, value.data(), static_cast<int>(value.size()),
|
|
20
|
+
out.data(), bytes, nullptr, nullptr);
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
std::string resolveProcessHelperPath() {
|
|
25
|
+
std::wstring buffer(MAX_PATH, L'\0');
|
|
26
|
+
DWORD len = GetModuleFileNameW(
|
|
27
|
+
bunite_win::getCurrentModuleHandle(),
|
|
28
|
+
buffer.data(),
|
|
29
|
+
static_cast<DWORD>(buffer.size()));
|
|
30
|
+
while (len == buffer.size() && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
|
31
|
+
buffer.resize(buffer.size() * 2);
|
|
32
|
+
len = GetModuleFileNameW(
|
|
33
|
+
bunite_win::getCurrentModuleHandle(),
|
|
34
|
+
buffer.data(),
|
|
35
|
+
static_cast<DWORD>(buffer.size()));
|
|
36
|
+
}
|
|
37
|
+
buffer.resize(len);
|
|
38
|
+
|
|
39
|
+
std::filesystem::path dll_path(buffer);
|
|
40
|
+
std::filesystem::path helper_path = dll_path.parent_path() / L"process_helper.exe";
|
|
41
|
+
if (!std::filesystem::exists(helper_path)) {
|
|
42
|
+
BUNITE_ERROR("process_helper.exe not found alongside libBuniteNative.dll at: %s",
|
|
43
|
+
helper_path.string().c_str());
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
return wideToUtf8(helper_path.wstring());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
} // namespace
|
|
7
50
|
|
|
8
51
|
extern "C" BUNITE_EXPORT int32_t bunite_abi_version(void) {
|
|
9
52
|
return BUNITE_ABI_VERSION;
|
|
10
53
|
}
|
|
11
54
|
|
|
55
|
+
extern "C" BUNITE_EXPORT const char* bunite_engine_name(void) {
|
|
56
|
+
return "cef";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
extern "C" BUNITE_EXPORT const char* bunite_engine_version(void) {
|
|
60
|
+
// Prefer runtime libcef.dll version — same-major fallback may load a different CEF than headers. Compile-time before init.
|
|
61
|
+
static std::string cached;
|
|
62
|
+
static std::once_flag once;
|
|
63
|
+
std::call_once(once, []() {
|
|
64
|
+
if (g_runtime.cef_initialized) {
|
|
65
|
+
char buf[128];
|
|
66
|
+
std::snprintf(buf, sizeof(buf), "%d.%d.%d+chromium-%d.%d.%d.%d",
|
|
67
|
+
cef_version_info(0), cef_version_info(1), cef_version_info(2),
|
|
68
|
+
cef_version_info(4), cef_version_info(5), cef_version_info(6),
|
|
69
|
+
cef_version_info(7));
|
|
70
|
+
cached = buf;
|
|
71
|
+
} else {
|
|
72
|
+
cached = CEF_VERSION;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return cached.c_str();
|
|
76
|
+
}
|
|
77
|
+
|
|
12
78
|
extern "C" BUNITE_EXPORT void bunite_set_log_level(int32_t level) {
|
|
13
79
|
buniteSetLogLevel(static_cast<BuniteLogLevel>(level));
|
|
14
80
|
}
|
|
15
81
|
|
|
16
82
|
extern "C" BUNITE_EXPORT bool bunite_init(
|
|
17
|
-
const char*
|
|
18
|
-
const char* cef_dir,
|
|
83
|
+
const char* engine_dir,
|
|
19
84
|
bool hide_console,
|
|
20
85
|
bool popup_blocking,
|
|
21
|
-
const char*
|
|
86
|
+
const char* engine_config_json
|
|
22
87
|
) {
|
|
23
88
|
{
|
|
24
89
|
std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
|
|
@@ -30,11 +95,11 @@ extern "C" BUNITE_EXPORT bool bunite_init(
|
|
|
30
95
|
g_runtime.shutdown_complete = false;
|
|
31
96
|
g_runtime.shutdown_finalize_posted.store(false);
|
|
32
97
|
g_runtime.shutting_down.store(false);
|
|
33
|
-
g_runtime.process_helper_path =
|
|
34
|
-
g_runtime.cef_dir =
|
|
98
|
+
g_runtime.process_helper_path = resolveProcessHelperPath();
|
|
99
|
+
g_runtime.cef_dir = engine_dir ? engine_dir : "";
|
|
35
100
|
g_runtime.popup_blocking = popup_blocking;
|
|
36
101
|
g_runtime.chromium_flags = bunite_win::parseChromiumFlagsJson(
|
|
37
|
-
|
|
102
|
+
engine_config_json ? engine_config_json : "");
|
|
38
103
|
}
|
|
39
104
|
|
|
40
105
|
if (hide_console) {
|
|
@@ -61,6 +126,10 @@ extern "C" BUNITE_EXPORT void bunite_run_loop(void) {
|
|
|
61
126
|
// The native UI thread owns the Win32 + CEF loop after bunite_init succeeds.
|
|
62
127
|
}
|
|
63
128
|
|
|
129
|
+
extern "C" BUNITE_EXPORT void bunite_pump_once(void) {
|
|
130
|
+
// No-op (dedicated UI thread). ABI parity with mac/linux cooperative pumps.
|
|
131
|
+
}
|
|
132
|
+
|
|
64
133
|
extern "C" BUNITE_EXPORT void bunite_free_cstring(const char* value) {
|
|
65
134
|
std::free(const_cast<char*>(value));
|
|
66
135
|
}
|
|
@@ -817,119 +886,3 @@ extern "C" BUNITE_EXPORT void bunite_complete_permission_request(uint32_t reques
|
|
|
817
886
|
});
|
|
818
887
|
}
|
|
819
888
|
|
|
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
|
-
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#include "../shared/ffi_exports.h"
|
|
4
4
|
#include "../shared/log.h"
|
|
5
|
+
#include "../shared/permissions.h"
|
|
5
6
|
#include "../shared/webview_storage.h"
|
|
6
7
|
|
|
7
8
|
#include <windows.h>
|
|
@@ -185,10 +186,11 @@ void uiThreadMain(); // [spawned thread]
|
|
|
185
186
|
|
|
186
187
|
std::wstring utf8ToWide(const std::string& value);
|
|
187
188
|
std::string escapeJsonString(const std::string& value);
|
|
188
|
-
std::vector<std::string> splitButtonLabels(const std::string& buttons_csv);
|
|
189
|
-
std::string trimAsciiWhitespace(const std::string& value);
|
|
190
|
-
std::string toLowerAscii(std::string value);
|
|
191
189
|
bool globMatchCaseInsensitive(const std::string& pattern, const std::string& value);
|
|
190
|
+
|
|
191
|
+
// CEF emits permission bits via two disjoint enums per delegate — two mappers. Unknown bits drop (kind=0 → JS denies).
|
|
192
|
+
uint32_t cefPermissionsToBuniteKind(uint32_t cef_bits); // cef_permission_request_types_t (OnShowPermissionPrompt)
|
|
193
|
+
uint32_t cefMediaAccessToBuniteKind(uint32_t cef_bits); // cef_media_access_permission_types_t (OnRequestMediaAccessPermission)
|
|
192
194
|
std::vector<std::string> parseNavigationRulesJson(const std::string& rules_json);
|
|
193
195
|
std::map<std::string, std::string> parseChromiumFlagsJson(const std::string& json);
|
|
194
196
|
bool shouldAlwaysAllowNavigationUrl(const std::string& url);
|
|
@@ -216,12 +216,8 @@ void uiThreadMain() {
|
|
|
216
216
|
reinterpret_cast<LONG_PTR>(messageWindowProc)
|
|
217
217
|
);
|
|
218
218
|
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
// with wShowWindow=SW_HIDE, which overrides the nCmdShow parameter of
|
|
222
|
-
// the first ShowWindow call in the process. We only consume the
|
|
223
|
-
// override when it would hide windows; legitimate values like
|
|
224
|
-
// SW_SHOWMAXIMIZED are left for the first real window to honour.
|
|
219
|
+
// `bun run` passes STARTF_USESHOWWINDOW + SW_HIDE which overrides nCmdShow on the first ShowWindow.
|
|
220
|
+
// Consume only when it would hide; legitimate values (SW_SHOWMAXIMIZED) pass through.
|
|
225
221
|
{
|
|
226
222
|
STARTUPINFOW si{};
|
|
227
223
|
si.cb = sizeof(si);
|