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,173 @@
|
|
|
1
|
+
#include "bunite_linux_internal.h"
|
|
2
|
+
#include "webview_storage.h"
|
|
3
|
+
|
|
4
|
+
#include <gio/gio.h>
|
|
5
|
+
|
|
6
|
+
#include <atomic>
|
|
7
|
+
#include <filesystem>
|
|
8
|
+
#include <optional>
|
|
9
|
+
#include <string>
|
|
10
|
+
|
|
11
|
+
namespace bunite_linux {
|
|
12
|
+
|
|
13
|
+
namespace {
|
|
14
|
+
|
|
15
|
+
std::atomic<bool> g_scheme_registered{false};
|
|
16
|
+
|
|
17
|
+
void fail(WebKitURISchemeRequest* req, GIOErrorEnum code, const char* msg) {
|
|
18
|
+
GError* err = g_error_new_literal(G_IO_ERROR, code, msg);
|
|
19
|
+
webkit_uri_scheme_request_finish_error(req, err);
|
|
20
|
+
g_error_free(err);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Reject escapes via symlinks or `..`.
|
|
24
|
+
std::optional<std::filesystem::path> resolveUnderRoot(
|
|
25
|
+
const std::filesystem::path& root, const std::string& rel
|
|
26
|
+
) {
|
|
27
|
+
std::error_code ec;
|
|
28
|
+
auto resolved = std::filesystem::weakly_canonical(root / rel, ec);
|
|
29
|
+
if (ec) return std::nullopt;
|
|
30
|
+
auto root_str = root.string();
|
|
31
|
+
auto resolved_str = resolved.string();
|
|
32
|
+
if (resolved_str == root_str) return resolved;
|
|
33
|
+
if (resolved_str.rfind(root_str + "/", 0) != 0) return std::nullopt;
|
|
34
|
+
return resolved;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// dir → /index.html; missing → +".html". Each fallback re-validated under root.
|
|
38
|
+
std::optional<std::filesystem::path> withFallback(
|
|
39
|
+
const std::filesystem::path& root, const std::filesystem::path& candidate
|
|
40
|
+
) {
|
|
41
|
+
std::error_code ec;
|
|
42
|
+
if (std::filesystem::is_regular_file(candidate, ec)) return candidate;
|
|
43
|
+
if (std::filesystem::is_directory(candidate, ec)) {
|
|
44
|
+
auto rel = std::filesystem::relative(candidate, root).string();
|
|
45
|
+
auto idx = resolveUnderRoot(root, rel + "/index.html");
|
|
46
|
+
if (idx && std::filesystem::is_regular_file(*idx, ec)) return idx;
|
|
47
|
+
}
|
|
48
|
+
auto rel = std::filesystem::relative(candidate, root).string();
|
|
49
|
+
auto withExt = resolveUnderRoot(root, rel + ".html");
|
|
50
|
+
if (withExt && std::filesystem::is_regular_file(*withExt, ec)) return withExt;
|
|
51
|
+
return std::nullopt;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
bool hasUnsafeSegment(const std::string& path) {
|
|
55
|
+
size_t pos = 0;
|
|
56
|
+
while (pos <= path.size()) {
|
|
57
|
+
size_t next = path.find('/', pos);
|
|
58
|
+
std::string seg = path.substr(pos, next == std::string::npos ? std::string::npos : next - pos);
|
|
59
|
+
if (seg == "." || seg == "..") return true;
|
|
60
|
+
if (seg.find('\\') != std::string::npos) return true;
|
|
61
|
+
if (next == std::string::npos) break;
|
|
62
|
+
pos = next + 1;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
std::string mimeFor(const std::filesystem::path& path) {
|
|
68
|
+
gchar* content_type = g_content_type_guess(path.string().c_str(), nullptr, 0, nullptr);
|
|
69
|
+
if (!content_type) return "application/octet-stream";
|
|
70
|
+
gchar* mime = g_content_type_get_mime_type(content_type);
|
|
71
|
+
std::string out = mime ? mime : "application/octet-stream";
|
|
72
|
+
g_free(content_type);
|
|
73
|
+
g_free(mime);
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
void on_appres_request(WebKitURISchemeRequest* req, gpointer user_data) {
|
|
78
|
+
(void)user_data;
|
|
79
|
+
WebKitWebView* wv = webkit_uri_scheme_request_get_web_view(req);
|
|
80
|
+
const uint32_t view_id = viewIdForWebView(wv);
|
|
81
|
+
const char* uri_c = webkit_uri_scheme_request_get_uri(req);
|
|
82
|
+
if (!uri_c) { fail(req, G_IO_ERROR_INVALID_FILENAME, "Empty URI"); return; }
|
|
83
|
+
|
|
84
|
+
std::string uri = uri_c;
|
|
85
|
+
auto prefix_end = uri.find("://");
|
|
86
|
+
if (prefix_end == std::string::npos) {
|
|
87
|
+
fail(req, G_IO_ERROR_INVALID_FILENAME, "Invalid URI"); return;
|
|
88
|
+
}
|
|
89
|
+
auto rest = uri.substr(prefix_end + 3);
|
|
90
|
+
auto first_slash = rest.find('/');
|
|
91
|
+
std::string host = (first_slash == std::string::npos) ? rest : rest.substr(0, first_slash);
|
|
92
|
+
std::string path = (first_slash == std::string::npos) ? "" : rest.substr(first_slash + 1);
|
|
93
|
+
|
|
94
|
+
if (host != "app.internal") {
|
|
95
|
+
fail(req, G_IO_ERROR_INVALID_ARGUMENT, "Invalid appres host"); return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (char c : {'?', '#'}) {
|
|
99
|
+
auto pos = path.find(c);
|
|
100
|
+
if (pos != std::string::npos) path = path.substr(0, pos);
|
|
101
|
+
}
|
|
102
|
+
if (gchar* decoded = g_uri_unescape_string(path.c_str(), nullptr)) {
|
|
103
|
+
path = decoded;
|
|
104
|
+
g_free(decoded);
|
|
105
|
+
} else {
|
|
106
|
+
fail(req, G_IO_ERROR_INVALID_FILENAME, "Invalid percent-encoding"); return;
|
|
107
|
+
}
|
|
108
|
+
if (path.find('\0') != std::string::npos) {
|
|
109
|
+
fail(req, G_IO_ERROR_INVALID_FILENAME, "NUL in path"); return;
|
|
110
|
+
}
|
|
111
|
+
while (!path.empty() && path.back() == '/') path.pop_back();
|
|
112
|
+
if (path.empty()) path = "index.html";
|
|
113
|
+
|
|
114
|
+
if (hasUnsafeSegment(path)) {
|
|
115
|
+
fail(req, G_IO_ERROR_INVALID_FILENAME, "Unsafe path segment"); return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (path == "internal/index.html" && bunite::WebviewContentStorage::instance().has(view_id)) {
|
|
119
|
+
std::string stored = bunite::WebviewContentStorage::instance().get(view_id);
|
|
120
|
+
GInputStream* stream = g_memory_input_stream_new_from_data(
|
|
121
|
+
g_memdup2(stored.data(), stored.size()), (gssize)stored.size(), g_free);
|
|
122
|
+
webkit_uri_scheme_request_finish(req, stream, (gint64)stored.size(), "text/html");
|
|
123
|
+
g_object_unref(stream);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (bunite::AppResRouteStorage::instance().hasRoute(path)) {
|
|
128
|
+
uint32_t request_id = g_runtime.next_route_request_id++;
|
|
129
|
+
g_object_ref(req);
|
|
130
|
+
g_runtime.pending_route_tasks[request_id] = { view_id, req };
|
|
131
|
+
std::string payload = "{\"requestId\":" + std::to_string(request_id) +
|
|
132
|
+
",\"path\":\"" + escapeJsonString(path) + "\"}";
|
|
133
|
+
emitWebviewEvent(view_id, "route-request", payload);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
auto* state = findView(view_id);
|
|
138
|
+
if (!state || state->appres_root.empty()) {
|
|
139
|
+
fail(req, G_IO_ERROR_NOT_FOUND, "appres root not configured"); return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
std::error_code ec;
|
|
143
|
+
auto root = std::filesystem::weakly_canonical(std::filesystem::path(state->appres_root), ec);
|
|
144
|
+
if (ec) { fail(req, G_IO_ERROR_NOT_FOUND, "Bad appres root"); return; }
|
|
145
|
+
|
|
146
|
+
auto candidate = resolveUnderRoot(root, path);
|
|
147
|
+
if (!candidate) { fail(req, G_IO_ERROR_NOT_FOUND, "Path escapes root"); return; }
|
|
148
|
+
auto resolved = withFallback(root, *candidate);
|
|
149
|
+
if (!resolved) { fail(req, G_IO_ERROR_NOT_FOUND, "File not found"); return; }
|
|
150
|
+
|
|
151
|
+
gchar* contents = nullptr;
|
|
152
|
+
gsize length = 0;
|
|
153
|
+
GError* err = nullptr;
|
|
154
|
+
if (!g_file_get_contents(resolved->string().c_str(), &contents, &length, &err)) {
|
|
155
|
+
webkit_uri_scheme_request_finish_error(req, err);
|
|
156
|
+
g_error_free(err);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
GInputStream* stream = g_memory_input_stream_new_from_data(contents, (gssize)length, g_free);
|
|
161
|
+
std::string mime = mimeFor(*resolved);
|
|
162
|
+
webkit_uri_scheme_request_finish(req, stream, (gint64)length, mime.c_str());
|
|
163
|
+
g_object_unref(stream);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
} // namespace
|
|
167
|
+
|
|
168
|
+
void registerAppresScheme(WebKitWebContext* ctx) {
|
|
169
|
+
if (g_scheme_registered.exchange(true)) return;
|
|
170
|
+
webkit_web_context_register_uri_scheme(ctx, "appres", on_appres_request, nullptr, nullptr);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
} // namespace bunite_linux
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#include "bunite_linux_internal.h"
|
|
2
|
+
#include "webview_storage.h"
|
|
3
|
+
|
|
4
|
+
#include <cstdlib>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
#include <mutex>
|
|
7
|
+
#include <string>
|
|
8
|
+
|
|
9
|
+
using bunite_linux::g_runtime;
|
|
10
|
+
using bunite_linux::runOnUiThreadSync;
|
|
11
|
+
|
|
12
|
+
#define BUNITE_LINUX_TODO(name) \
|
|
13
|
+
do { \
|
|
14
|
+
static std::once_flag once; \
|
|
15
|
+
std::call_once(once, []() { \
|
|
16
|
+
BUNITE_WARN("%s not implemented yet on linux (skeleton stub)", name); \
|
|
17
|
+
}); \
|
|
18
|
+
} while (0)
|
|
19
|
+
|
|
20
|
+
extern "C" BUNITE_EXPORT bool bunite_window_create(
|
|
21
|
+
uint32_t window_id, double x, double y, double width, double height,
|
|
22
|
+
const char* title, const char* title_bar_style,
|
|
23
|
+
bool transparent, bool hidden, bool minimized, bool maximized
|
|
24
|
+
) {
|
|
25
|
+
return runOnUiThreadSync([=]() -> bool {
|
|
26
|
+
return bunite_linux::createWindow(window_id, x, y, width, height, title, title_bar_style,
|
|
27
|
+
transparent, hidden, minimized, maximized);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
extern "C" BUNITE_EXPORT void bunite_window_destroy(uint32_t window_id) {
|
|
32
|
+
runOnUiThreadSync([=]() { bunite_linux::destroyWindow(window_id); });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
extern "C" BUNITE_EXPORT void bunite_window_reset_close_pending(uint32_t window_id) {
|
|
36
|
+
runOnUiThreadSync([=]() {
|
|
37
|
+
if (auto* s = bunite_linux::findWindow(window_id)) s->close_pending.store(false);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
extern "C" BUNITE_EXPORT void bunite_window_show(uint32_t window_id) {
|
|
42
|
+
runOnUiThreadSync([=]() {
|
|
43
|
+
if (auto* s = bunite_linux::findWindow(window_id)) gtk_window_present(s->window);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
extern "C" BUNITE_EXPORT void bunite_window_close(uint32_t window_id) {
|
|
48
|
+
runOnUiThreadSync([=]() {
|
|
49
|
+
if (auto* s = bunite_linux::findWindow(window_id)) gtk_window_close(s->window);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
extern "C" BUNITE_EXPORT void bunite_window_set_title(uint32_t window_id, const char* title) {
|
|
54
|
+
std::string t = title ? title : "";
|
|
55
|
+
runOnUiThreadSync([=]() {
|
|
56
|
+
if (auto* s = bunite_linux::findWindow(window_id)) gtk_window_set_title(s->window, t.c_str());
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
extern "C" BUNITE_EXPORT void bunite_window_minimize(uint32_t window_id) {
|
|
61
|
+
runOnUiThreadSync([=]() {
|
|
62
|
+
auto* s = bunite_linux::findWindow(window_id);
|
|
63
|
+
if (s && !s->minimized.exchange(true)) gtk_window_minimize(s->window);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
extern "C" BUNITE_EXPORT void bunite_window_unminimize(uint32_t window_id) {
|
|
68
|
+
runOnUiThreadSync([=]() {
|
|
69
|
+
auto* s = bunite_linux::findWindow(window_id);
|
|
70
|
+
// GTK4 has no unminimize — present() raises and de-minimizes.
|
|
71
|
+
if (s && s->minimized.exchange(false)) gtk_window_present(s->window);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
extern "C" BUNITE_EXPORT bool bunite_window_is_minimized(uint32_t window_id) {
|
|
76
|
+
return runOnUiThreadSync([=]() -> bool {
|
|
77
|
+
auto* s = bunite_linux::findWindow(window_id);
|
|
78
|
+
return s ? s->minimized.load() : false;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
extern "C" BUNITE_EXPORT void bunite_window_maximize(uint32_t window_id) {
|
|
83
|
+
runOnUiThreadSync([=]() {
|
|
84
|
+
auto* s = bunite_linux::findWindow(window_id);
|
|
85
|
+
if (s && !s->maximized.exchange(true)) gtk_window_maximize(s->window);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
extern "C" BUNITE_EXPORT void bunite_window_unmaximize(uint32_t window_id) {
|
|
90
|
+
runOnUiThreadSync([=]() {
|
|
91
|
+
auto* s = bunite_linux::findWindow(window_id);
|
|
92
|
+
if (s && s->maximized.exchange(false)) gtk_window_unmaximize(s->window);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
extern "C" BUNITE_EXPORT bool bunite_window_is_maximized(uint32_t window_id) {
|
|
97
|
+
return runOnUiThreadSync([=]() -> bool {
|
|
98
|
+
auto* s = bunite_linux::findWindow(window_id);
|
|
99
|
+
return s ? s->maximized.load() : false;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
extern "C" BUNITE_EXPORT void bunite_window_set_frame(
|
|
104
|
+
uint32_t window_id, double x, double y, double width, double height
|
|
105
|
+
) {
|
|
106
|
+
(void)x; (void)y;
|
|
107
|
+
runOnUiThreadSync([=]() {
|
|
108
|
+
if (auto* s = bunite_linux::findWindow(window_id))
|
|
109
|
+
gtk_window_set_default_size(s->window, (int)width, (int)height);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
extern "C" BUNITE_EXPORT bool bunite_view_create(
|
|
114
|
+
uint32_t view_id, uint32_t window_id,
|
|
115
|
+
const char* url, const char* html, const char* preload,
|
|
116
|
+
const char* appres_root, const char* navigation_rules_json,
|
|
117
|
+
double x, double y, double width, double height,
|
|
118
|
+
bool auto_resize, bool sandbox, const char* preload_origins_json
|
|
119
|
+
) {
|
|
120
|
+
(void)sandbox;
|
|
121
|
+
return runOnUiThreadSync([=]() -> bool {
|
|
122
|
+
return bunite_linux::createView(view_id, window_id, url, html, preload, appres_root,
|
|
123
|
+
navigation_rules_json, preload_origins_json,
|
|
124
|
+
x, y, width, height, auto_resize);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
extern "C" BUNITE_EXPORT void bunite_view_execute_javascript(uint32_t view_id, const char* script) {
|
|
129
|
+
std::string s = script ? script : "";
|
|
130
|
+
runOnUiThreadSync([=]() {
|
|
131
|
+
if (auto* v = bunite_linux::findView(view_id))
|
|
132
|
+
webkit_web_view_evaluate_javascript(v->webview, s.c_str(), -1, nullptr, nullptr, nullptr, nullptr, nullptr);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
extern "C" BUNITE_EXPORT void bunite_view_load_url(uint32_t view_id, const char* url) {
|
|
137
|
+
std::string s = url ? url : "";
|
|
138
|
+
runOnUiThreadSync([=]() {
|
|
139
|
+
auto* v = bunite_linux::findView(view_id);
|
|
140
|
+
if (!v) return;
|
|
141
|
+
bunite::WebviewContentStorage::instance().remove(view_id);
|
|
142
|
+
webkit_web_view_load_uri(v->webview, s.c_str());
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
extern "C" BUNITE_EXPORT void bunite_view_load_html(uint32_t view_id, const char* html) {
|
|
147
|
+
std::string content = html ? html : "";
|
|
148
|
+
runOnUiThreadSync([=]() {
|
|
149
|
+
auto* v = bunite_linux::findView(view_id);
|
|
150
|
+
if (!v) return;
|
|
151
|
+
bunite::WebviewContentStorage::instance().set(view_id, content);
|
|
152
|
+
webkit_web_view_load_uri(v->webview, "appres://app.internal/internal/index.html");
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
extern "C" BUNITE_EXPORT void bunite_register_appres_route(const char* path) {
|
|
157
|
+
bunite::AppResRouteStorage::instance().registerRoute(path ? path : "");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
extern "C" BUNITE_EXPORT void bunite_unregister_appres_route(const char* path) {
|
|
161
|
+
bunite::AppResRouteStorage::instance().unregisterRoute(path ? path : "");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
extern "C" BUNITE_EXPORT void bunite_complete_route_request(uint32_t request_id, const char* html) {
|
|
165
|
+
std::string body = html ? html : "";
|
|
166
|
+
runOnUiThreadSync([=]() {
|
|
167
|
+
auto it = bunite_linux::g_runtime.pending_route_tasks.find(request_id);
|
|
168
|
+
if (it == bunite_linux::g_runtime.pending_route_tasks.end()) return;
|
|
169
|
+
WebKitURISchemeRequest* req = it->second.request;
|
|
170
|
+
bunite_linux::g_runtime.pending_route_tasks.erase(it);
|
|
171
|
+
GInputStream* stream = g_memory_input_stream_new_from_data(
|
|
172
|
+
g_memdup2(body.data(), body.size()), (gssize)body.size(), g_free);
|
|
173
|
+
webkit_uri_scheme_request_finish(req, stream, (gint64)body.size(), "text/html");
|
|
174
|
+
g_object_unref(stream);
|
|
175
|
+
g_object_unref(req);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_visible(uint32_t view_id, bool visible) {
|
|
180
|
+
runOnUiThreadSync([=]() {
|
|
181
|
+
if (auto* v = bunite_linux::findView(view_id)) {
|
|
182
|
+
gtk_widget_set_visible(v->container ? v->container : GTK_WIDGET(v->webview), visible);
|
|
183
|
+
if (visible) bunite_linux::queueViewRedraw(v->webview);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_input_passthrough(uint32_t view_id, bool passthrough) {
|
|
189
|
+
runOnUiThreadSync([=]() {
|
|
190
|
+
if (auto* v = bunite_linux::findView(view_id))
|
|
191
|
+
gtk_widget_set_can_target(GTK_WIDGET(v->webview), !passthrough);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_mask_region(uint32_t view_id, const double* rects, uint32_t count) {
|
|
196
|
+
// GTK4 has no per-widget region cull primitive — mask not implemented.
|
|
197
|
+
(void)view_id; (void)rects; (void)count; BUNITE_LINUX_TODO("bunite_view_set_mask_region");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
extern "C" BUNITE_EXPORT void bunite_view_bring_to_front(uint32_t view_id) {
|
|
201
|
+
runOnUiThreadSync([=]() {
|
|
202
|
+
auto* v = bunite_linux::findView(view_id);
|
|
203
|
+
if (!v) return;
|
|
204
|
+
// GtkOverlay handles z-order; reparenting blanks child surfaces under WSLg.
|
|
205
|
+
bunite_linux::queueViewRedraw(v->webview);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_bounds(
|
|
210
|
+
uint32_t view_id, double x, double y, double width, double height
|
|
211
|
+
) {
|
|
212
|
+
runOnUiThreadSync([=]() { bunite_linux::applyViewBounds(view_id, x, y, width, height); });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_bounds_async(
|
|
216
|
+
uint32_t view_id, double x, double y, double width, double height
|
|
217
|
+
) {
|
|
218
|
+
auto* invoke = new std::function<void()>(
|
|
219
|
+
[=]() { bunite_linux::applyViewBounds(view_id, x, y, width, height); });
|
|
220
|
+
g_main_context_invoke_full(g_runtime.ui_context, G_PRIORITY_DEFAULT,
|
|
221
|
+
+[](gpointer data) -> gboolean {
|
|
222
|
+
(*static_cast<std::function<void()>*>(data))();
|
|
223
|
+
return G_SOURCE_REMOVE;
|
|
224
|
+
},
|
|
225
|
+
invoke,
|
|
226
|
+
+[](gpointer data) { delete static_cast<std::function<void()>*>(data); });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
extern "C" BUNITE_EXPORT void bunite_view_set_anchor(uint32_t view_id, int mode, double inset) {
|
|
230
|
+
(void)view_id; (void)mode; (void)inset; BUNITE_LINUX_TODO("bunite_view_set_anchor");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
extern "C" BUNITE_EXPORT void bunite_view_go_back(uint32_t view_id) {
|
|
234
|
+
runOnUiThreadSync([=]() {
|
|
235
|
+
if (auto* v = bunite_linux::findView(view_id)) webkit_web_view_go_back(v->webview);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
extern "C" BUNITE_EXPORT void bunite_view_reload(uint32_t view_id) {
|
|
240
|
+
runOnUiThreadSync([=]() {
|
|
241
|
+
if (auto* v = bunite_linux::findView(view_id)) webkit_web_view_reload(v->webview);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
extern "C" BUNITE_EXPORT void bunite_view_remove(uint32_t view_id) {
|
|
246
|
+
runOnUiThreadSync([=]() { bunite_linux::removeView(view_id); });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
extern "C" BUNITE_EXPORT void bunite_view_open_devtools(uint32_t view_id) {
|
|
250
|
+
(void)view_id; BUNITE_LINUX_TODO("bunite_view_open_devtools");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
extern "C" BUNITE_EXPORT void bunite_view_close_devtools(uint32_t view_id) {
|
|
254
|
+
(void)view_id; BUNITE_LINUX_TODO("bunite_view_close_devtools");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
extern "C" BUNITE_EXPORT void bunite_view_toggle_devtools(uint32_t view_id) {
|
|
258
|
+
(void)view_id; BUNITE_LINUX_TODO("bunite_view_toggle_devtools");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
extern "C" BUNITE_EXPORT void bunite_complete_permission_request(uint32_t request_id, uint32_t state) {
|
|
262
|
+
(void)request_id; (void)state; BUNITE_LINUX_TODO("bunite_complete_permission_request");
|
|
263
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "callbacks.h"
|
|
4
|
+
#include "ffi_exports.h"
|
|
5
|
+
#include "log.h"
|
|
6
|
+
#include "permissions.h"
|
|
7
|
+
|
|
8
|
+
#include <gtk/gtk.h>
|
|
9
|
+
#include <webkit/webkit.h>
|
|
10
|
+
|
|
11
|
+
#include <atomic>
|
|
12
|
+
#include <condition_variable>
|
|
13
|
+
#include <cstdint>
|
|
14
|
+
#include <functional>
|
|
15
|
+
#include <mutex>
|
|
16
|
+
#include <pthread.h>
|
|
17
|
+
#include <string>
|
|
18
|
+
#include <unordered_map>
|
|
19
|
+
#include <vector>
|
|
20
|
+
|
|
21
|
+
namespace bunite_linux {
|
|
22
|
+
|
|
23
|
+
struct WindowState {
|
|
24
|
+
GtkWindow* window = nullptr;
|
|
25
|
+
// gtk_window_set_child takes one widget — overlay holds main view + surfaces.
|
|
26
|
+
GtkOverlay* host = nullptr;
|
|
27
|
+
std::atomic<bool> close_pending{false};
|
|
28
|
+
// GTK4 lacks is_minimized; is_maximized lies on WSLg/tiling WMs. Track logically.
|
|
29
|
+
std::atomic<bool> minimized{false};
|
|
30
|
+
std::atomic<bool> maximized{false};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
struct ViewState {
|
|
34
|
+
GtkWidget* container = nullptr;
|
|
35
|
+
WebKitWebView* webview = nullptr;
|
|
36
|
+
uint32_t window_id = 0;
|
|
37
|
+
std::string appres_root;
|
|
38
|
+
std::string preload_script;
|
|
39
|
+
std::string stored_html;
|
|
40
|
+
std::vector<std::string> navigation_rules;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
struct RuntimeState {
|
|
44
|
+
std::mutex object_mutex;
|
|
45
|
+
std::unordered_map<uint32_t, WindowState> windows;
|
|
46
|
+
std::unordered_map<uint32_t, ViewState> views;
|
|
47
|
+
|
|
48
|
+
bool initialized = false;
|
|
49
|
+
bool popup_blocking = false;
|
|
50
|
+
std::atomic<bool> shutting_down{false};
|
|
51
|
+
|
|
52
|
+
GtkApplication* app = nullptr;
|
|
53
|
+
GMainContext* ui_context = nullptr;
|
|
54
|
+
pthread_t ui_thread = 0;
|
|
55
|
+
bool ui_thread_set = false;
|
|
56
|
+
|
|
57
|
+
std::unordered_map<uint32_t, WebKitPermissionRequest*> pending_permissions;
|
|
58
|
+
uint32_t next_permission_request_id = 1;
|
|
59
|
+
|
|
60
|
+
struct PendingRoute { uint32_t view_id; WebKitURISchemeRequest* request; };
|
|
61
|
+
std::unordered_map<uint32_t, PendingRoute> pending_route_tasks;
|
|
62
|
+
uint32_t next_route_request_id = 1;
|
|
63
|
+
|
|
64
|
+
BuniteWebviewEventHandler webview_event_handler = nullptr;
|
|
65
|
+
BuniteWindowEventHandler window_event_handler = nullptr;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
extern RuntimeState g_runtime;
|
|
69
|
+
|
|
70
|
+
bool isOnMainThread();
|
|
71
|
+
|
|
72
|
+
template <typename Block>
|
|
73
|
+
auto runOnUiThreadSync(Block block) -> decltype(block()) {
|
|
74
|
+
using R = decltype(block());
|
|
75
|
+
if (isOnMainThread()) return block();
|
|
76
|
+
if constexpr (std::is_void_v<R>) {
|
|
77
|
+
std::mutex m;
|
|
78
|
+
std::condition_variable cv;
|
|
79
|
+
bool done = false;
|
|
80
|
+
auto invoke = [&]() {
|
|
81
|
+
block();
|
|
82
|
+
{
|
|
83
|
+
std::lock_guard<std::mutex> lock(m);
|
|
84
|
+
done = true;
|
|
85
|
+
}
|
|
86
|
+
cv.notify_all();
|
|
87
|
+
};
|
|
88
|
+
auto trampoline = +[](gpointer data) -> gboolean {
|
|
89
|
+
(*static_cast<decltype(invoke)*>(data))();
|
|
90
|
+
return G_SOURCE_REMOVE;
|
|
91
|
+
};
|
|
92
|
+
g_main_context_invoke_full(g_runtime.ui_context, G_PRIORITY_DEFAULT, trampoline, &invoke, nullptr);
|
|
93
|
+
std::unique_lock<std::mutex> lock(m);
|
|
94
|
+
cv.wait(lock, [&]() { return done; });
|
|
95
|
+
} else {
|
|
96
|
+
R result{};
|
|
97
|
+
std::mutex m;
|
|
98
|
+
std::condition_variable cv;
|
|
99
|
+
bool done = false;
|
|
100
|
+
auto invoke = [&]() {
|
|
101
|
+
result = block();
|
|
102
|
+
{
|
|
103
|
+
std::lock_guard<std::mutex> lock(m);
|
|
104
|
+
done = true;
|
|
105
|
+
}
|
|
106
|
+
cv.notify_all();
|
|
107
|
+
};
|
|
108
|
+
auto trampoline = +[](gpointer data) -> gboolean {
|
|
109
|
+
(*static_cast<decltype(invoke)*>(data))();
|
|
110
|
+
return G_SOURCE_REMOVE;
|
|
111
|
+
};
|
|
112
|
+
g_main_context_invoke_full(g_runtime.ui_context, G_PRIORITY_DEFAULT, trampoline, &invoke, nullptr);
|
|
113
|
+
std::unique_lock<std::mutex> lock(m);
|
|
114
|
+
cv.wait(lock, [&]() { return done; });
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
std::string escapeJsonString(const std::string& value);
|
|
120
|
+
|
|
121
|
+
void emitWindowEvent(uint32_t window_id, const char* event_name, const std::string& payload = {});
|
|
122
|
+
void emitWebviewEvent(uint32_t view_id, const char* event_name, const std::string& payload = {});
|
|
123
|
+
|
|
124
|
+
bool globMatchCaseInsensitive(const std::string& pattern, const std::string& value);
|
|
125
|
+
std::vector<std::string> parseNavigationRulesJson(const std::string& json);
|
|
126
|
+
bool shouldAlwaysAllowNavigationUrl(const std::string& url);
|
|
127
|
+
bool shouldAllowNavigation(const ViewState* view, const std::string& url);
|
|
128
|
+
|
|
129
|
+
WindowState* findWindow(uint32_t window_id);
|
|
130
|
+
bool createWindow(uint32_t window_id, double x, double y, double width, double height,
|
|
131
|
+
const char* title, const char* title_bar_style,
|
|
132
|
+
bool transparent, bool hidden, bool minimized, bool maximized);
|
|
133
|
+
void destroyWindow(uint32_t window_id);
|
|
134
|
+
|
|
135
|
+
ViewState* findView(uint32_t view_id);
|
|
136
|
+
uint32_t viewIdForWebView(WebKitWebView* wv);
|
|
137
|
+
bool createView(uint32_t view_id, uint32_t window_id,
|
|
138
|
+
const char* url, const char* html, const char* preload, const char* appres_root,
|
|
139
|
+
const char* navigation_rules_json, const char* preload_origins_json,
|
|
140
|
+
double x, double y, double width, double height, bool auto_resize);
|
|
141
|
+
void removeView(uint32_t view_id);
|
|
142
|
+
void detachViewSideState(uint32_t view_id);
|
|
143
|
+
void applyViewBounds(uint32_t view_id, double x, double y, double width, double height);
|
|
144
|
+
void queueViewRedraw(WebKitWebView* wv);
|
|
145
|
+
|
|
146
|
+
void registerAppresScheme(WebKitWebContext* ctx);
|
|
147
|
+
|
|
148
|
+
} // namespace bunite_linux
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#include "bunite_linux_internal.h"
|