diff --git a/src/ModMan/webview/edge/CustomProtocolHandlerEdge.cpp b/src/ModMan/Web/Edge/CustomProtocolHandlerEdge.cpp similarity index 96% rename from src/ModMan/webview/edge/CustomProtocolHandlerEdge.cpp rename to src/ModMan/Web/Edge/CustomProtocolHandlerEdge.cpp index 8e2f351a..5116b549 100644 --- a/src/ModMan/webview/edge/CustomProtocolHandlerEdge.cpp +++ b/src/ModMan/Web/Edge/CustomProtocolHandlerEdge.cpp @@ -18,7 +18,9 @@ namespace { - constexpr auto MOD_MAN_URL_PREFIX = "http://modman/"; + constexpr auto MOD_MAN_URL_PREFIX = "modman://localhost/"; + + std::unordered_map assetLookup; std::string wide_string_to_string(const std::wstring& wide_string) { @@ -65,6 +67,8 @@ namespace edge { void InstallCustomProtocolHandler(webview::webview& wv) { + assetLookup = BuildUiFileLookup(); + const auto controller = static_cast(wv.browser_controller().value()); Microsoft::WRL::ComPtr core; if (!SUCCEEDED(controller->get_CoreWebView2(&core))) @@ -131,8 +135,8 @@ namespace edge { const auto asset = uri.substr(std::char_traits::length(MOD_MAN_URL_PREFIX)); - const auto foundUiFile = MOD_MAN_UI_FILES.find(asset); - if (foundUiFile != MOD_MAN_UI_FILES.end()) + const auto foundUiFile = assetLookup.find(asset); + if (foundUiFile != assetLookup.end()) { Microsoft::WRL::ComPtr response_stream = SHCreateMemStream( static_cast(foundUiFile->second.data), foundUiFile->second.dataSize); diff --git a/src/ModMan/webview/edge/CustomProtocolHandlerEdge.h b/src/ModMan/Web/Edge/CustomProtocolHandlerEdge.h similarity index 100% rename from src/ModMan/webview/edge/CustomProtocolHandlerEdge.h rename to src/ModMan/Web/Edge/CustomProtocolHandlerEdge.h diff --git a/src/ModMan/Web/Gtk/CustomProtocolHandlerGtk.cpp b/src/ModMan/Web/Gtk/CustomProtocolHandlerGtk.cpp new file mode 100644 index 00000000..8036e279 --- /dev/null +++ b/src/ModMan/Web/Gtk/CustomProtocolHandlerGtk.cpp @@ -0,0 +1,106 @@ +#include "CustomProtocolHandlerGtk.h" + +#pragma warning(push, 0) +#include +#include +#pragma warning(pop) + +#if defined(WEBVIEW_PLATFORM_LINUX) && defined(WEBVIEW_GTK) + +#include "Web/UiAssets.h" + +#include +#include + +#define G_SPAWN_ERROR g_spawn_error_quark() + +G_DEFINE_QUARK(g - spawn - error - quark, g_spawn_error) + +namespace +{ + std::unordered_map assetLookup; + + const char* ContentTypeForAssetName(const std::string& assetName) + { + const char* mimeType; + + if (assetName.ends_with(".html")) + { + mimeType = "text/html"; + } + else if (assetName.ends_with(".js")) + { + mimeType = "text/javascript"; + } + else if (assetName.ends_with(".css")) + { + mimeType = "text/css"; + } + else + { + mimeType = "application/octet-stream"; + } + + return mimeType; + } + + void ModManUriSchemeRequestCb(WebKitURISchemeRequest* request, gpointer user_data) + { + const gchar* asset = webkit_uri_scheme_request_get_path(request); + + std::cout << std::format("Modman request: {}\n", asset); + + const auto foundUiFile = assetLookup.find(asset); + if (foundUiFile != assetLookup.end()) + { + gsize stream_length = foundUiFile->second.dataSize; + GInputStream* stream = g_memory_input_stream_new_from_data(foundUiFile->second.data, foundUiFile->second.dataSize, nullptr); + + webkit_uri_scheme_request_finish(request, stream, stream_length, ContentTypeForAssetName(foundUiFile->second.filename)); + g_object_unref(stream); + } + else + { + GError* error = g_error_new(G_SPAWN_ERROR, 123, "Could not find %s.", asset); + webkit_uri_scheme_request_finish_error(request, error); + g_error_free(error); + return; + } + } + + void OnResourceLoadStarted(WebKitWebView* self, WebKitWebResource* resource, WebKitURIRequest* request, gpointer user_data) + { + std::cout << webkit_uri_request_get_http_method(request) << " " << webkit_uri_request_get_uri(request) << "\n"; + + std::string uri(webkit_uri_request_get_uri(request)); + if (uri.starts_with("http://")) + { + uri = std::format("modman://{}", uri.substr(7)); + } + else if (uri.starts_with("https://")) + { + uri = std::format("modman://{}", uri.substr(8)); + } + + std::cout << std::format("Setting uri: {}\n", uri); + webkit_uri_request_set_uri(request, uri.c_str()); + } +} // namespace + +namespace gtk +{ + void InstallCustomProtocolHandler(webview::webview& wv) + { + const auto widget = static_cast(wv.browser_controller().value()); + const auto webView = WEBKIT_WEB_VIEW(widget); + const auto context = webkit_web_view_get_context(webView); + + assetLookup = BuildUiFileLookup(); + + g_signal_connect(webView, "resource-load-started", G_CALLBACK(OnResourceLoadStarted), NULL); + + webkit_web_context_register_uri_scheme(context, "modman", ModManUriSchemeRequestCb, NULL, nullptr); + } +} // namespace gtk + +#endif diff --git a/src/ModMan/webview/gtk/CustomProtocolHandlerGtk.h b/src/ModMan/Web/Gtk/CustomProtocolHandlerGtk.h similarity index 100% rename from src/ModMan/webview/gtk/CustomProtocolHandlerGtk.h rename to src/ModMan/Web/Gtk/CustomProtocolHandlerGtk.h diff --git a/src/ModMan/Web/UiAssets.cpp b/src/ModMan/Web/UiAssets.cpp new file mode 100644 index 00000000..3ddadbf4 --- /dev/null +++ b/src/ModMan/Web/UiAssets.cpp @@ -0,0 +1,15 @@ +#include "UiAssets.h" + +#include + +std::unordered_map BuildUiFileLookup() +{ + std::unordered_map result; + + for (const auto& asset : MOD_MAN_UI_FILES) + { + result.emplace(std::format("/{}", asset.filename), asset); + } + + return result; +} diff --git a/src/ModMan/Web/UiAssets.h b/src/ModMan/Web/UiAssets.h new file mode 100644 index 00000000..50231baa --- /dev/null +++ b/src/ModMan/Web/UiAssets.h @@ -0,0 +1,8 @@ +#pragma once + +#include "ui/modmanui.h" + +#include +#include + +std::unordered_map BuildUiFileLookup(); diff --git a/src/ModMan/main.cpp b/src/ModMan/main.cpp index 98bfa0a9..64661cd0 100644 --- a/src/ModMan/main.cpp +++ b/src/ModMan/main.cpp @@ -2,8 +2,8 @@ #include "webview/webview.h" #pragma warning(pop) -#include "webview/edge/CustomProtocolHandlerEdge.h" -#include "webview/gtk/CustomProtocolHandlerGtk.h" +#include "Web/Edge/CustomProtocolHandlerEdge.h" +#include "Web/Gtk/CustomProtocolHandlerGtk.h" #include #include @@ -57,7 +57,11 @@ int main() edge::InstallCustomProtocolHandler(w); #endif - w.navigate("http://modman/index.html"); +#if defined(WEBVIEW_PLATFORM_LINUX) && defined(WEBVIEW_GTK) + gtk::InstallCustomProtocolHandler(w); +#endif + + w.navigate("modman://localhost/index.html"); w.run(); } catch (const webview::exception& e) diff --git a/src/ModMan/webview/gtk/CustomProtocolHandlerGtk.cpp b/src/ModMan/webview/gtk/CustomProtocolHandlerGtk.cpp deleted file mode 100644 index 6cb05149..00000000 --- a/src/ModMan/webview/gtk/CustomProtocolHandlerGtk.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -namespace webkitgtk -{ - void InstallCustomProtocolHandler(); -} diff --git a/src/ModManUi/build/HeaderTransformationPlugin.ts b/src/ModManUi/build/HeaderTransformationPlugin.ts index eb862985..bf1f4706 100644 --- a/src/ModManUi/build/HeaderTransformationPlugin.ts +++ b/src/ModManUi/build/HeaderTransformationPlugin.ts @@ -47,13 +47,10 @@ export function headerTransformationPlugin(): Plugin { return { name: "header-transformation", apply: "build", - config(config) { - config.base = "http://modman"; - }, generateBundle(options: OutputOptions, bundle: OutputBundle, isWrite: boolean) { const includesStr: string[] = [`#include "index.html.h"`]; const uiFilesStr: string[] = [ - `{ "index.html", { "index.html", INDEX_HTML, std::extent_v } }`, + `{ "index.html", INDEX_HTML, std::extent_v }`, ]; for (const curBundle of Object.values(bundle)) { @@ -66,7 +63,7 @@ export function headerTransformationPlugin(): Plugin { includesStr.push(`#include "${curBundle.fileName}.h"`); uiFilesStr.push( - `{ "${curBundle.fileName}", { "${curBundle.fileName}", ${varName}, std::extent_v } }`, + `{ "${curBundle.fileName}", ${varName}, std::extent_v }`, ); curBundle.fileName = `${curBundle.fileName}.h`; @@ -79,8 +76,8 @@ export function headerTransformationPlugin(): Plugin { ${includesStr.join("\n")} -#include -#include +#include +#include struct UiFile { @@ -89,9 +86,9 @@ struct UiFile const size_t dataSize; }; -static inline const std::unordered_map MOD_MAN_UI_FILES({ +static inline const UiFile MOD_MAN_UI_FILES[] { ${uiFilesStr.join(",\n")} -}); +}; `, }); }, @@ -106,7 +103,6 @@ ${uiFilesStr.join(",\n")} }, ) { html = html.replaceAll("index.js.h", "index.js"); - // .replaceAll("http://modman-resource/", "modman-resource://"); html = createTransformedTextSource(createVarName("index.html"), html); ctx.filename = `${ctx.filename}.h`; diff --git a/src/ModManUi/package-lock.json b/src/ModManUi/package-lock.json index 73392ec1..46c3d517 100644 --- a/src/ModManUi/package-lock.json +++ b/src/ModManUi/package-lock.json @@ -101,6 +101,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -653,6 +654,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -699,6 +701,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2274,6 +2277,7 @@ "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/types": "8.45.0", @@ -3035,6 +3039,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3228,6 +3233,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -3761,6 +3767,7 @@ "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3822,6 +3829,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -3869,6 +3877,7 @@ "integrity": "sha512-K6tP0dW8FJVZLQxa2S7LcE1lLw3X8VvB3t887Q6CLrFVxHYBXGANbXvwNzYIu6Ughx1bSJ5BDT0YB3ybPT39lw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", @@ -4669,6 +4678,7 @@ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -5477,6 +5487,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5516,6 +5527,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -5757,6 +5769,7 @@ "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -6261,6 +6274,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6384,6 +6398,7 @@ "integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -6627,6 +6642,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -6706,6 +6722,7 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz", "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.22", "@vue/compiler-sfc": "3.5.22",