bunite-core 0.6.0 → 0.8.0

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.
Files changed (39) hide show
  1. package/package.json +4 -2
  2. package/src/bun/core/App.ts +35 -34
  3. package/src/bun/core/BrowserView.ts +3 -9
  4. package/src/bun/core/singleInstanceLock.ts +91 -0
  5. package/src/bun/index.ts +5 -6
  6. package/src/bun/preload/inline.ts +1 -2
  7. package/src/bun/proc/native.ts +54 -78
  8. package/src/native/linux/bunite_linux_appres.cpp +173 -0
  9. package/src/native/linux/bunite_linux_ffi.cpp +263 -0
  10. package/src/native/linux/bunite_linux_internal.h +148 -0
  11. package/src/native/linux/bunite_linux_preload.cpp +1 -0
  12. package/src/native/linux/bunite_linux_runtime.cpp +120 -0
  13. package/src/native/linux/bunite_linux_utils.cpp +114 -0
  14. package/src/native/linux/bunite_linux_view.cpp +244 -0
  15. package/src/native/linux/bunite_linux_window.cpp +101 -0
  16. package/src/native/mac/bunite_mac_appres.mm +163 -0
  17. package/src/native/mac/bunite_mac_ffi.mm +470 -0
  18. package/src/native/mac/bunite_mac_internal.h +151 -0
  19. package/src/native/mac/bunite_mac_runtime.mm +15 -0
  20. package/src/native/mac/bunite_mac_utils.mm +121 -0
  21. package/src/native/mac/bunite_mac_view.mm +279 -0
  22. package/src/native/mac/bunite_mac_window.mm +187 -0
  23. package/src/native/shared/ffi_exports.h +13 -13
  24. package/src/native/shared/permissions.h +14 -0
  25. package/src/native/shared/webview_storage.h +6 -3
  26. package/src/native/win/native_host_cef.cpp +4 -8
  27. package/src/native/win/native_host_ffi.cpp +76 -123
  28. package/src/native/win/native_host_internal.h +5 -3
  29. package/src/native/win/native_host_runtime.cpp +2 -6
  30. package/src/native/win/native_host_utils.cpp +23 -52
  31. package/src/native/win/process_helper_win.cpp +1 -3
  32. package/src/preload/runtime.ts +1 -3
  33. package/src/preload/tsconfig.tsbuildinfo +1 -1
  34. package/src/preload/webviewElement.ts +3 -8
  35. package/src/shared/paths.ts +35 -44
  36. package/src/shared/platform.ts +1 -2
  37. package/src/shared/rpcDemux.ts +47 -2
  38. package/src/shared/webviewPolyfill.ts +64 -6
  39. 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* process_helper_path,
22
- const char* cef_dir,
21
+ const char* engine_dir,
23
22
  bool hide_console,
24
23
  bool popup_blocking,
25
- const char* chromium_flags_json
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
- // Tracks which appres:// paths have registered dynamic handlers on the Bun side.
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
- // Inject our tracked client so F12, Ctrl+Shift+I, and Inspect Element
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 will check closing flag.
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 = 2;
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* process_helper_path,
18
- const char* cef_dir,
83
+ const char* engine_dir,
19
84
  bool hide_console,
20
85
  bool popup_blocking,
21
- const char* chromium_flags_json
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 = process_helper_path ? process_helper_path : "";
34
- g_runtime.cef_dir = cef_dir ? 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
- chromium_flags_json ? chromium_flags_json : "");
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
- // Consume a hostile STARTUPINFO.wShowWindow override.
220
- // When launched via `bun run`, the child receives STARTF_USESHOWWINDOW
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);