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.
Files changed (40) hide show
  1. package/package.json +3 -2
  2. package/src/bun/core/App.ts +155 -15
  3. package/src/bun/core/BrowserView.ts +124 -44
  4. package/src/bun/core/BrowserWindow.ts +94 -47
  5. package/src/bun/core/Socket.ts +2 -1
  6. package/src/bun/core/SurfaceBrowserIPC.ts +65 -0
  7. package/src/bun/core/SurfaceManager.ts +201 -0
  8. package/src/bun/core/SurfaceRegistry.ts +60 -0
  9. package/src/bun/core/Utils.ts +275 -46
  10. package/src/bun/events/appEvents.ts +2 -1
  11. package/src/bun/events/webviewEvents.ts +1 -3
  12. package/src/bun/events/windowEvents.ts +2 -0
  13. package/src/bun/index.ts +4 -3
  14. package/src/bun/preload/inline.ts +19 -25
  15. package/src/bun/proc/native.ts +158 -122
  16. package/src/native/shared/callbacks.h +6 -6
  17. package/src/native/shared/ffi_exports.h +123 -119
  18. package/src/native/shared/log.h +24 -0
  19. package/src/native/shared/webview_storage.h +5 -5
  20. package/src/native/win/native_host_appres.cpp +258 -0
  21. package/src/native/win/native_host_cef.cpp +834 -0
  22. package/src/native/win/native_host_ffi.cpp +935 -0
  23. package/src/native/win/native_host_internal.h +285 -0
  24. package/src/native/win/native_host_runtime.cpp +286 -0
  25. package/src/native/win/native_host_utils.cpp +314 -0
  26. package/src/native/win/process_helper_win.cpp +126 -26
  27. package/src/preload/runtime.built.js +1 -1
  28. package/src/preload/runtime.ts +65 -42
  29. package/src/preload/tsconfig.json +2 -1
  30. package/src/preload/tsconfig.tsbuildinfo +1 -0
  31. package/src/preload/webviewElement.ts +307 -0
  32. package/src/shared/cefVersion.ts +2 -0
  33. package/src/shared/log.ts +40 -0
  34. package/src/shared/paths.ts +122 -52
  35. package/src/shared/rpc.ts +7 -1
  36. package/src/shared/webviewPolyfill.ts +80 -0
  37. package/src/view/index.ts +8 -5
  38. package/src/native/shared/cef_response_filter.h +0 -116
  39. package/src/native/win/native_host.cpp +0 -2453
  40. package/src/types/config.ts +0 -29
@@ -0,0 +1,285 @@
1
+ #pragma once
2
+
3
+ #include "../shared/ffi_exports.h"
4
+ #include "../shared/log.h"
5
+ #include "../shared/webview_storage.h"
6
+
7
+ #include <windows.h>
8
+ #include <ole2.h>
9
+
10
+ #include <algorithm>
11
+ #include <atomic>
12
+ #include <chrono>
13
+ #include <cctype>
14
+ #include <condition_variable>
15
+ #include <cstdint>
16
+ #include <cstdio>
17
+ #include <cstring>
18
+ #include <cstdlib>
19
+ #include <filesystem>
20
+ #include <fstream>
21
+ #include <functional>
22
+ #include <future>
23
+ #include <map>
24
+ #include <memory>
25
+ #include <mutex>
26
+ #include <optional>
27
+ #include <queue>
28
+ #include <sstream>
29
+ #include <string>
30
+ #include <thread>
31
+ #include <vector>
32
+
33
+ #include "include/cef_app.h"
34
+ #include "include/cef_browser.h"
35
+ #include "include/cef_client.h"
36
+ #include "include/cef_command_line.h"
37
+ #include "include/cef_parser.h"
38
+ #include "include/cef_permission_handler.h"
39
+ #include "include/cef_resource_handler.h"
40
+ #include "include/cef_resource_request_handler.h"
41
+ #include "include/cef_scheme.h"
42
+ #include "include/cef_task.h"
43
+ #include "include/wrapper/cef_helpers.h"
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Constants
47
+ // ---------------------------------------------------------------------------
48
+
49
+ constexpr wchar_t kWindowClass[] = L"BuniteWindowClass";
50
+ constexpr UINT kRunQueuedTaskMessage = WM_APP + 1;
51
+ constexpr UINT kFinalizeShutdownMessage = WM_APP + 4;
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Enums
55
+ // ---------------------------------------------------------------------------
56
+
57
+ enum class PermissionRequestKind {
58
+ Prompt,
59
+ MediaAccess
60
+ };
61
+
62
+ // View anchor modes for automatic layout during window resize.
63
+ enum class ViewAnchorMode {
64
+ None = 0, // manual bounds
65
+ Fill = 1, // fill client area
66
+ Top = 2, // top strip (fixed height)
67
+ BelowTop = 3 // below top strip
68
+ };
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Structures
72
+ // ---------------------------------------------------------------------------
73
+
74
+ struct PendingPermissionRequest {
75
+ PermissionRequestKind kind;
76
+ uint32_t permissions = 0;
77
+ CefRefPtr<CefPermissionPromptCallback> prompt_callback;
78
+ CefRefPtr<CefMediaAccessCallback> media_callback;
79
+ };
80
+
81
+ struct WindowHost;
82
+
83
+ struct ViewHost {
84
+ uint32_t id = 0;
85
+ WindowHost* window = nullptr;
86
+ RECT bounds{0, 0, 0, 0};
87
+ std::string url;
88
+ std::string html;
89
+ std::string preload_script;
90
+ std::string appres_root;
91
+ std::vector<std::string> navigation_rules;
92
+ int anchor_mode = static_cast<int>(ViewAnchorMode::Fill);
93
+ double anchor_inset = 0;
94
+ bool sandbox = false;
95
+ std::vector<std::string> preload_origins;
96
+ std::atomic<bool> closing = false;
97
+ CefRefPtr<CefBrowser> browser;
98
+ CefRefPtr<CefClient> client;
99
+
100
+ // Pending state: applied in OnAfterCreated when browser HWND becomes available.
101
+ bool pending_visible = true;
102
+ bool pending_bring_to_front = false;
103
+ bool pending_passthrough = false;
104
+ bool has_pending_bounds = false;
105
+ RECT pending_bounds{0, 0, 0, 0};
106
+ };
107
+
108
+ struct WindowHost {
109
+ uint32_t id = 0;
110
+ HWND hwnd = nullptr;
111
+ std::wstring title;
112
+ std::wstring title_bar_style;
113
+ RECT frame{0, 0, 0, 0};
114
+ bool transparent = false;
115
+ bool hidden = false;
116
+ bool minimized = false;
117
+ bool maximized = false;
118
+ bool restore_maximized_after_minimize = false;
119
+ std::atomic<bool> close_pending = false;
120
+ std::atomic<bool> closing = false;
121
+ std::vector<ViewHost*> views;
122
+ };
123
+
124
+ struct RuntimeState {
125
+ std::mutex lifecycle_mutex;
126
+ std::condition_variable lifecycle_cv;
127
+ bool init_finished = false;
128
+ bool init_success = false;
129
+ bool initialized = false;
130
+ std::atomic<bool> shutting_down{false};
131
+ bool shutdown_complete = false;
132
+ std::atomic<bool> shutdown_finalize_posted{false};
133
+
134
+ std::thread ui_thread;
135
+ DWORD ui_thread_id = 0;
136
+ HWND message_window = nullptr;
137
+
138
+ std::mutex task_mutex;
139
+ std::queue<std::function<void()>> queued_tasks;
140
+
141
+ std::mutex object_mutex;
142
+ std::map<uint32_t, WindowHost*> windows_by_id;
143
+ std::map<uint32_t, ViewHost*> views_by_id;
144
+ std::map<int, uint32_t> browser_to_view_id;
145
+
146
+ std::mutex permission_mutex;
147
+ std::map<uint32_t, PendingPermissionRequest> pending_permissions;
148
+ uint32_t next_permission_request_id = 1;
149
+
150
+ std::mutex route_mutex;
151
+ struct PendingRouteRequest {
152
+ std::string path;
153
+ CefRefPtr<CefCallback> callback;
154
+ CefRefPtr<CefRequest> cef_request;
155
+ };
156
+ std::map<uint32_t, PendingRouteRequest> pending_routes;
157
+ uint32_t next_route_request_id = 1;
158
+
159
+ BuniteWebviewEventHandler webview_event_handler = nullptr;
160
+ BuniteWindowEventHandler window_event_handler = nullptr;
161
+
162
+ bool cef_initialized = false;
163
+ std::string process_helper_path;
164
+ std::string cef_dir;
165
+ bool popup_blocking = false;
166
+ std::map<std::string, std::string> chromium_flags;
167
+ std::atomic<int> devtools_browser_count = 0;
168
+ };
169
+
170
+ extern RuntimeState g_runtime;
171
+
172
+ // ---------------------------------------------------------------------------
173
+ // Cross-TU function declarations (namespace bunite_win)
174
+ // ---------------------------------------------------------------------------
175
+
176
+ namespace bunite_win {
177
+
178
+ HINSTANCE getCurrentModuleHandle();
179
+ bool isOnUiThread(); // [any thread]
180
+ void postUiTask(std::function<void()> task); // [any thread]
181
+ void postCefUiTask(std::function<void()> task); // [any thread]
182
+ void executeQueuedUiTasks(); // [UI thread]
183
+ bool registerWindowClasses(); // [UI thread]
184
+ void uiThreadMain(); // [spawned thread]
185
+
186
+ std::wstring utf8ToWide(const std::string& value);
187
+ 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
+ bool globMatchCaseInsensitive(const std::string& pattern, const std::string& value);
192
+ std::vector<std::string> parseNavigationRulesJson(const std::string& rules_json);
193
+ std::map<std::string, std::string> parseChromiumFlagsJson(const std::string& json);
194
+ bool shouldAlwaysAllowNavigationUrl(const std::string& url);
195
+ bool shouldAllowNavigation(const ViewHost* view, const std::string& url);
196
+ void emitWindowEvent(uint32_t window_id, const char* event_name, const std::string& payload = {});
197
+ void emitWebviewEvent(uint32_t view_id, const char* event_name, const std::string& payload = {});
198
+
199
+ std::string normalizeAppResPath(const std::string& url);
200
+ std::string getMimeType(const std::filesystem::path& file_path);
201
+ std::string getAppResRootForView(uint32_t view_id);
202
+ std::optional<std::string> loadAppResResource(uint32_t view_id, const std::string& url, std::string& mime_type);
203
+ void registerAppResSchemeHandlers(); // [CEF UI thread]
204
+
205
+ void removeBrowserMapping(int browser_id);
206
+ void syncWindowFrame(WindowHost* window); // [UI thread]
207
+ void resizeViewToFit(ViewHost* view); // [UI thread]
208
+ void finalizeViewHost(ViewHost* view); // [CEF UI thread]
209
+ void closeViewHost(ViewHost* view); // [any thread]
210
+ void destroyWindowHost(WindowHost* window); // [UI thread]
211
+ void finalizeWindowHost(WindowHost* window); // [UI thread]
212
+ void openDevToolsForView(ViewHost* view); // [CEF UI thread]
213
+ void closeDevToolsForView(ViewHost* view); // [CEF UI thread]
214
+ void toggleDevToolsForView(ViewHost* view); // [CEF UI thread]
215
+ bool initializeCefOnUiThread(); // [UI thread]
216
+ void shutdownCefOnUiThread(); // [UI thread]
217
+ void cancelPendingPermissionRequestsOnUiThread(); // [CEF UI thread]
218
+ void cancelPendingRouteRequestsOnUiThread(); // [CEF UI thread]
219
+ void maybeCompleteShutdownOnUiThread(); // [CEF UI thread]
220
+ void closeAllWindowsForShutdown(); // [UI thread]
221
+ WindowHost* getWindowHostById(uint32_t window_id);
222
+ ViewHost* getViewHostById(uint32_t view_id);
223
+ DWORD makeWindowStyle(const std::wstring& title_bar_style);
224
+ bool createBrowserForView(ViewHost* view); // [UI thread]
225
+
226
+ class CefClosureTask : public CefTask {
227
+ public:
228
+ explicit CefClosureTask(std::function<void()> fn) : fn_(std::move(fn)) {}
229
+ void Execute() override { fn_(); }
230
+ private:
231
+ std::function<void()> fn_;
232
+ IMPLEMENT_REFCOUNTING(CefClosureTask);
233
+ };
234
+
235
+ template <typename Result>
236
+ Result runOnUiThreadSync(std::function<Result()> task) {
237
+ if (isOnUiThread()) {
238
+ return task();
239
+ }
240
+
241
+ auto packaged = std::make_shared<std::packaged_task<Result()>>(std::move(task));
242
+ auto future = packaged->get_future();
243
+ postUiTask([packaged]() { (*packaged)(); });
244
+ return future.get();
245
+ }
246
+
247
+ template <>
248
+ inline void runOnUiThreadSync<void>(std::function<void()> task) {
249
+ if (isOnUiThread()) {
250
+ task();
251
+ return;
252
+ }
253
+
254
+ auto packaged = std::make_shared<std::packaged_task<void()>>(std::move(task));
255
+ auto future = packaged->get_future();
256
+ postUiTask([packaged]() { (*packaged)(); });
257
+ future.get();
258
+ }
259
+
260
+ template <typename Result>
261
+ Result runOnCefUiThreadSync(std::function<Result()> task) {
262
+ if (CefCurrentlyOn(TID_UI)) {
263
+ return task();
264
+ }
265
+
266
+ auto packaged = std::make_shared<std::packaged_task<Result()>>(std::move(task));
267
+ auto future = packaged->get_future();
268
+ postCefUiTask([packaged]() { (*packaged)(); });
269
+ return future.get();
270
+ }
271
+
272
+ template <>
273
+ inline void runOnCefUiThreadSync<void>(std::function<void()> task) {
274
+ if (CefCurrentlyOn(TID_UI)) {
275
+ task();
276
+ return;
277
+ }
278
+
279
+ auto packaged = std::make_shared<std::packaged_task<void()>>(std::move(task));
280
+ auto future = packaged->get_future();
281
+ postCefUiTask([packaged]() { (*packaged)(); });
282
+ future.get();
283
+ }
284
+
285
+ } // namespace bunite_win
@@ -0,0 +1,286 @@
1
+ #include "native_host_internal.h"
2
+
3
+ RuntimeState g_runtime;
4
+
5
+ namespace bunite_win {
6
+
7
+ HINSTANCE getCurrentModuleHandle() {
8
+ HMODULE module = nullptr;
9
+ GetModuleHandleExW(
10
+ GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
11
+ reinterpret_cast<LPCWSTR>(&getCurrentModuleHandle),
12
+ &module
13
+ );
14
+ return module;
15
+ }
16
+
17
+ bool isOnUiThread() {
18
+ return g_runtime.ui_thread_id != 0 && GetCurrentThreadId() == g_runtime.ui_thread_id;
19
+ }
20
+
21
+ void postUiTask(std::function<void()> task) {
22
+ {
23
+ std::lock_guard<std::mutex> lock(g_runtime.task_mutex);
24
+ g_runtime.queued_tasks.push(std::move(task));
25
+ }
26
+
27
+ if (g_runtime.message_window) {
28
+ PostMessageW(g_runtime.message_window, kRunQueuedTaskMessage, 0, 0);
29
+ }
30
+ }
31
+
32
+ void postCefUiTask(std::function<void()> task) {
33
+ CefPostTask(TID_UI, new CefClosureTask(std::move(task)));
34
+ }
35
+
36
+ void executeQueuedUiTasks() {
37
+ for (;;) {
38
+ std::queue<std::function<void()>> tasks;
39
+ {
40
+ std::lock_guard<std::mutex> lock(g_runtime.task_mutex);
41
+ if (g_runtime.queued_tasks.empty()) {
42
+ break;
43
+ }
44
+ tasks.swap(g_runtime.queued_tasks);
45
+ }
46
+
47
+ while (!tasks.empty()) {
48
+ auto task = std::move(tasks.front());
49
+ tasks.pop();
50
+ task();
51
+ }
52
+ }
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Window procedures
57
+ // ---------------------------------------------------------------------------
58
+
59
+ static LRESULT CALLBACK messageWindowProc(HWND hwnd, UINT message, WPARAM w_param, LPARAM l_param) {
60
+ switch (message) {
61
+ case kRunQueuedTaskMessage:
62
+ executeQueuedUiTasks();
63
+ return 0;
64
+
65
+ case kFinalizeShutdownMessage:
66
+ shutdownCefOnUiThread();
67
+ PostQuitMessage(0);
68
+ return 0;
69
+ }
70
+
71
+ return DefWindowProcW(hwnd, message, w_param, l_param);
72
+ }
73
+
74
+ static LRESULT CALLBACK buniteWindowProc(HWND hwnd, UINT message, WPARAM w_param, LPARAM l_param) {
75
+ WindowHost* window = reinterpret_cast<WindowHost*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
76
+
77
+ if (message == WM_NCCREATE) {
78
+ auto* create_struct = reinterpret_cast<CREATESTRUCTW*>(l_param);
79
+ window = static_cast<WindowHost*>(create_struct->lpCreateParams);
80
+ SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(window));
81
+ return DefWindowProcW(hwnd, message, w_param, l_param);
82
+ }
83
+
84
+ switch (message) {
85
+ case WM_SETFOCUS:
86
+ if (window) {
87
+ emitWindowEvent(window->id, "focus");
88
+ }
89
+ break;
90
+
91
+ case WM_KILLFOCUS:
92
+ if (window) {
93
+ emitWindowEvent(window->id, "blur");
94
+ }
95
+ break;
96
+
97
+ case WM_MOVE:
98
+ if (window) {
99
+ syncWindowFrame(window);
100
+ emitWindowEvent(
101
+ window->id,
102
+ "move",
103
+ "{\"x\":" + std::to_string(window->frame.left) +
104
+ ",\"y\":" + std::to_string(window->frame.top) +
105
+ ",\"maximized\":" + (window->maximized ? "true" : "false") +
106
+ ",\"minimized\":" + (window->minimized ? "true" : "false") + "}"
107
+ );
108
+ }
109
+ break;
110
+
111
+ case WM_ERASEBKGND:
112
+ return 1;
113
+
114
+ case WM_SIZE:
115
+ if (window) {
116
+ syncWindowFrame(window);
117
+
118
+ {
119
+ std::vector<ViewHost*> views_copy;
120
+ {
121
+ std::lock_guard<std::mutex> lock(g_runtime.object_mutex);
122
+ views_copy = window->views;
123
+ }
124
+ for (auto* view : views_copy) {
125
+ resizeViewToFit(view);
126
+ }
127
+ }
128
+
129
+ emitWindowEvent(
130
+ window->id,
131
+ "resize",
132
+ "{\"x\":" + std::to_string(window->frame.left) +
133
+ ",\"y\":" + std::to_string(window->frame.top) +
134
+ ",\"width\":" + std::to_string(window->frame.right - window->frame.left) +
135
+ ",\"height\":" + std::to_string(window->frame.bottom - window->frame.top) +
136
+ ",\"maximized\":" + (window->maximized ? "true" : "false") +
137
+ ",\"minimized\":" + (window->minimized ? "true" : "false") + "}"
138
+ );
139
+ }
140
+ break;
141
+
142
+ case WM_CLOSE:
143
+ if (window && !window->close_pending.exchange(true)) {
144
+ emitWindowEvent(window->id, "close-requested");
145
+ }
146
+ return 0;
147
+
148
+ case WM_ENDSESSION:
149
+ if (w_param && window) {
150
+ g_runtime.shutting_down.store(true);
151
+ destroyWindowHost(window);
152
+ }
153
+ return 0;
154
+
155
+ case WM_NCDESTROY:
156
+ if (window) {
157
+ window->hwnd = nullptr;
158
+ SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
159
+ finalizeWindowHost(window);
160
+ }
161
+ return 0;
162
+ }
163
+
164
+ return DefWindowProcW(hwnd, message, w_param, l_param);
165
+ }
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // Window class registration & UI thread entry
169
+ // ---------------------------------------------------------------------------
170
+
171
+ bool registerWindowClasses() {
172
+ static std::once_flag once;
173
+ static bool registered = false;
174
+
175
+ std::call_once(once, []() {
176
+ HINSTANCE module = getCurrentModuleHandle();
177
+
178
+ WNDCLASSW window_class{};
179
+ window_class.lpfnWndProc = buniteWindowProc;
180
+ window_class.hInstance = module;
181
+ window_class.lpszClassName = kWindowClass;
182
+ window_class.hCursor = LoadCursorW(nullptr, IDC_ARROW);
183
+ window_class.hbrBackground = nullptr;
184
+
185
+ registered = RegisterClassW(&window_class) != 0;
186
+ });
187
+
188
+ return registered;
189
+ }
190
+
191
+ void uiThreadMain() {
192
+ g_runtime.ui_thread_id = GetCurrentThreadId();
193
+ OleInitialize(nullptr);
194
+
195
+ bool init_success = false;
196
+ if (registerWindowClasses()) {
197
+ g_runtime.message_window = CreateWindowExW(
198
+ 0,
199
+ L"STATIC",
200
+ L"",
201
+ 0,
202
+ 0,
203
+ 0,
204
+ 0,
205
+ 0,
206
+ nullptr,
207
+ nullptr,
208
+ getCurrentModuleHandle(),
209
+ nullptr
210
+ );
211
+
212
+ if (g_runtime.message_window) {
213
+ SetWindowLongPtrW(
214
+ g_runtime.message_window,
215
+ GWLP_WNDPROC,
216
+ reinterpret_cast<LONG_PTR>(messageWindowProc)
217
+ );
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.
225
+ {
226
+ STARTUPINFOW si{};
227
+ si.cb = sizeof(si);
228
+ GetStartupInfoW(&si);
229
+ if ((si.dwFlags & STARTF_USESHOWWINDOW) && si.wShowWindow == SW_HIDE) {
230
+ ShowWindow(g_runtime.message_window, SW_HIDE);
231
+ }
232
+ }
233
+
234
+ init_success = initializeCefOnUiThread();
235
+ } else {
236
+ BUNITE_ERROR("Failed to create message window (err=%lu).", GetLastError());
237
+ }
238
+ } else {
239
+ BUNITE_ERROR("Failed to register window classes.");
240
+ }
241
+
242
+ {
243
+ std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
244
+ g_runtime.init_success = init_success;
245
+ g_runtime.init_finished = true;
246
+ g_runtime.initialized = init_success;
247
+ }
248
+ g_runtime.lifecycle_cv.notify_all();
249
+
250
+ if (!init_success) {
251
+ if (g_runtime.message_window) {
252
+ DestroyWindow(g_runtime.message_window);
253
+ g_runtime.message_window = nullptr;
254
+ }
255
+ g_runtime.ui_thread_id = 0;
256
+ {
257
+ std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
258
+ g_runtime.shutdown_complete = true;
259
+ }
260
+ g_runtime.lifecycle_cv.notify_all();
261
+ return;
262
+ }
263
+
264
+ MSG msg{};
265
+ while (GetMessageW(&msg, nullptr, 0, 0) > 0) {
266
+ TranslateMessage(&msg);
267
+ DispatchMessageW(&msg);
268
+ }
269
+
270
+ if (g_runtime.message_window) {
271
+ DestroyWindow(g_runtime.message_window);
272
+ g_runtime.message_window = nullptr;
273
+ }
274
+
275
+ OleUninitialize();
276
+
277
+ g_runtime.ui_thread_id = 0;
278
+ {
279
+ std::lock_guard<std::mutex> lock(g_runtime.lifecycle_mutex);
280
+ g_runtime.initialized = false;
281
+ g_runtime.shutdown_complete = true;
282
+ }
283
+ g_runtime.lifecycle_cv.notify_all();
284
+ }
285
+
286
+ } // namespace bunite_win