2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2026-07-03 22:30:07 +00:00

refactor: use new webwindowed api (#831)

* chore: update webview with new api

* chore: update modman to use new webview api

* chore: use title handler plugin from webview lib

* chore: use favicon plugin from webview lib

* chore: use vite-plugin-cpp-header from webview repo

* chore: use asset handler from webview lib

* chore: make webview utility

* chore: rename webview to webwindowed

* chore: Rename code usages to webwindowed
This commit is contained in:
Jan
2026-06-16 09:50:34 +02:00
committed by GitHub
parent b2aa4749c1
commit 8dba13f913
41 changed files with 352 additions and 1116 deletions
+3 -3
View File
@@ -2,7 +2,7 @@
#include "FastFileContext.h"
#include "Utils/DispatchableThread.h"
#include "Web/WebViewLib.h"
#include "Web/WebWindowedLib.h"
#include <memory>
@@ -14,8 +14,8 @@ public:
void Startup();
void Destroy();
std::unique_ptr<webview::webview> m_main_webview;
std::unique_ptr<webview::webview> m_dev_tools_webview;
std::shared_ptr<webwindowed::window> m_main_window;
std::shared_ptr<webwindowed::window> m_dev_tools_window;
FastFileContext m_fast_file;
DispatchableThread m_db_thread;
+8 -4
View File
@@ -31,11 +31,15 @@ npm run dev
## How does it work
ModMan uses [`webview`](https://github.com/Laupetin/webview) for providing a web frontend as a native application.
Unlike frameworks like Electron this does not ship a browser engine alongside it, but instead relies on browser APIs of your OS.
On Windows, this makes use of [WebView2](https://learn.microsoft.com/en-us/microsoft-edge/webview2), on Linux it uses [WebKitGTK](https://webkitgtk.org).
ModMan uses [`webwindowed`](https://github.com/Laupetin/webwindowed) for providing a web frontend as a native
application.
Unlike frameworks like Electron this does not ship a browser engine alongside it, but instead relies on browser APIs of
your OS.
On Windows, this makes use of [WebView2](https://learn.microsoft.com/en-us/microsoft-edge/webview2), on Linux it
uses [WebKitGTK](https://webkitgtk.org).
This adds the following dependencies:
- **Windows**: An up-to-date OS with at the very least Windows10. The WebView2 library for development is downloaded by premake.
- **Windows**: An up-to-date OS with at the very least Windows10. The WebView2 library for development is downloaded by
premake.
- **Linux**: Developing and using ModMan requires the following dependencies to be installed: `gtk4 webkitgtk-6.0`
+3 -3
View File
@@ -162,11 +162,11 @@ namespace
namespace ui
{
void RegisterAssetBinds(webview::webview& wv)
void RegisterAssetBinds(webwindowed::commands_builder& commands)
{
Bind<std::string, std::optional<ZoneAssetsDto>>(wv,
Bind<std::string, std::optional<ZoneAssetsDto>>(commands,
"getAssetsForZone",
[](const std::string& zoneName)
[](webwindowed::detail::window_base& calling_window, const std::string& zoneName)
{
return GetZonesForZone(zoneName);
});
+2 -2
View File
@@ -1,8 +1,8 @@
#pragma once
#include "Web/WebViewLib.h"
#include "Web/WebWindowedLib.h"
namespace ui
{
void RegisterAssetBinds(webview::webview& wv);
void RegisterAssetBinds(webwindowed::commands_builder& commands);
} // namespace ui
+5 -5
View File
@@ -7,11 +7,11 @@
namespace ui
{
void RegisterAllBinds(webview::webview& wv)
void RegisterAllBinds(webwindowed::commands_builder& commands)
{
RegisterAssetBinds(wv);
RegisterDialogHandlerBinds(wv);
RegisterUnlinkingBinds(wv);
RegisterZoneBinds(wv);
RegisterAssetBinds(commands);
RegisterDialogHandlerBinds(commands);
RegisterUnlinkingBinds(commands);
RegisterZoneBinds(commands);
}
} // namespace ui
+2 -2
View File
@@ -1,8 +1,8 @@
#pragma once
#include "Web/WebViewLib.h"
#include "Web/WebWindowedLib.h"
namespace ui
{
void RegisterAllBinds(webview::webview& wv);
void RegisterAllBinds(webwindowed::commands_builder& commands);
}
+30 -28
View File
@@ -32,24 +32,24 @@ namespace
NLOHMANN_DEFINE_TYPE_EXTENSION(SaveFileDialogDto, filters);
void ReplyWithDialogResult(webview::webview& wv,
void ReplyWithDialogResult(webwindowed::detail::window_base& calling_window,
const std::string& id,
const ui::DialogCallbackResultType resultType,
const std::optional<std::string>& result)
{
if (resultType == ui::DialogCallbackResultType::FAILED)
ui::PromiseReject(wv, id, result);
ui::PromiseReject(calling_window, id, result);
else
ui::PromiseResolve(wv, id, result);
ui::PromiseResolve(calling_window, id, result);
}
void OpenFileDialogBind(webview::webview& wv, const std::string& id, const std::optional<OpenFileDialogDto>& dto)
void OpenFileDialogBind(webwindowed::detail::window_base& calling_window, const std::string& id, const std::optional<OpenFileDialogDto>& dto)
{
ui::OpenFileDialog dialog;
dialog.SetCallback(
[&wv, id](const ui::DialogCallbackResultType resultType, const std::optional<std::string>& result)
[&calling_window, id](const ui::DialogCallbackResultType resultType, const std::optional<std::string>& result)
{
ReplyWithDialogResult(wv, id, resultType, result);
ReplyWithDialogResult(calling_window, id, resultType, result);
});
if (dto && dto->filters)
@@ -63,13 +63,13 @@ namespace
dialog.OpenAsync();
}
void SaveFileDialogBind(webview::webview& wv, const std::string& id, const std::optional<SaveFileDialogDto>& dto)
void SaveFileDialogBind(webwindowed::detail::window_base& calling_window, const std::string& id, const std::optional<SaveFileDialogDto>& dto)
{
ui::SaveFileDialog dialog;
dialog.SetCallback(
[&wv, id](const ui::DialogCallbackResultType resultType, const std::optional<std::string>& result)
[&calling_window, id](const ui::DialogCallbackResultType resultType, const std::optional<std::string>& result)
{
ReplyWithDialogResult(wv, id, resultType, result);
ReplyWithDialogResult(calling_window, id, resultType, result);
});
if (dto && dto->filters)
@@ -83,13 +83,13 @@ namespace
dialog.OpenAsync();
}
void FolderSelectDialogBind(webview::webview& wv, const std::string& id)
void FolderSelectDialogBind(webwindowed::detail::window_base& calling_window, const std::string& id)
{
ui::FolderSelectDialog dialog;
dialog.SetCallback(
[&wv, id](const ui::DialogCallbackResultType resultType, const std::optional<std::string>& result)
[&calling_window, id](const ui::DialogCallbackResultType resultType, const std::optional<std::string>& result)
{
ReplyWithDialogResult(wv, id, resultType, result);
ReplyWithDialogResult(calling_window, id, resultType, result);
});
dialog.OpenAsync();
@@ -98,27 +98,29 @@ namespace
namespace ui
{
void RegisterDialogHandlerBinds(webview::webview& wv)
void RegisterDialogHandlerBinds(webwindowed::commands_builder& commands)
{
BindAsync<std::optional<OpenFileDialogDto>>(wv,
"openFileDialog",
[&wv](const std::string& id, const std::optional<OpenFileDialogDto>& dto)
{
OpenFileDialogBind(wv, id, dto);
});
BindAsync<std::optional<OpenFileDialogDto>>(
commands,
"openFileDialog",
[](const std::string& id, webwindowed::detail::window_base& calling_window, const std::optional<OpenFileDialogDto>& dto)
{
OpenFileDialogBind(calling_window, id, dto);
});
BindAsync<std::optional<SaveFileDialogDto>>(wv,
"saveFileDialog",
[&wv](const std::string& id, const std::optional<SaveFileDialogDto>& dto)
{
SaveFileDialogBind(wv, id, dto);
});
BindAsync<std::optional<SaveFileDialogDto>>(
commands,
"saveFileDialog",
[](const std::string& id, webwindowed::detail::window_base& calling_window, const std::optional<SaveFileDialogDto>& dto)
{
SaveFileDialogBind(calling_window, id, dto);
});
BindAsync(wv,
BindAsync(commands,
"folderSelectDialog",
[&wv](const std::string& id)
[](const std::string& id, webwindowed::detail::window_base& calling_window)
{
FolderSelectDialogBind(wv, id);
FolderSelectDialogBind(calling_window, id);
});
}
} // namespace ui
+2 -2
View File
@@ -1,8 +1,8 @@
#pragma once
#include "Web/WebViewLib.h"
#include "Web/WebWindowedLib.h"
namespace ui
{
void RegisterDialogHandlerBinds(webview::webview& wv);
void RegisterDialogHandlerBinds(webwindowed::commands_builder& commands);
}
+11 -9
View File
@@ -78,22 +78,24 @@ namespace
return {};
}
void UnlinkZone(webview::webview& wv, std::string id, std::string zoneName) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
void UnlinkZone(webwindowed::detail::window_base& calling_window,
std::string id,
std::string zoneName) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
{
ModManContext::Get().m_db_thread.Dispatch(
[&wv, id, zoneName]
[&calling_window, id, zoneName]
{
auto result = UnlinkZoneInDbThread(zoneName);
if (result)
{
con::debug("Unlinked zone \"{}\"", zoneName);
ui::PromiseResolve(wv, id, true);
ui::PromiseResolve(calling_window, id, true);
}
else
{
con::warn("Failed to unlink zone \"{}\": {}", zoneName, result.error());
ui::PromiseReject(wv, id, std::move(result).error());
ui::PromiseReject(calling_window, id, std::move(result).error());
}
});
}
@@ -107,16 +109,16 @@ namespace ui
.zoneName = std::move(zoneName),
.percentage = percentage,
};
Notify(*ModManContext::Get().m_main_webview, "zoneUnlinkProgress", dto);
Notify(*ModManContext::Get().m_main_window, "zoneUnlinkProgress", dto);
}
void RegisterUnlinkingBinds(webview::webview& wv)
void RegisterUnlinkingBinds(webwindowed::commands_builder& commands)
{
BindAsync<std::string>(wv,
BindAsync<std::string>(commands,
"unlinkZone",
[&wv](const std::string& id, std::string zoneName)
[](const std::string& id, webwindowed::detail::window_base& calling_window, std::string zoneName)
{
UnlinkZone(wv, id, std::move(zoneName));
UnlinkZone(calling_window, id, std::move(zoneName));
});
}
} // namespace ui
+4 -2
View File
@@ -1,10 +1,12 @@
#pragma once
#include "Web/WebViewLib.h"
#include "Web/WebWindowedLib.h"
#include <string>
namespace ui
{
void NotifyZoneUnlinkProgress(std::string zoneName, double percentage);
void RegisterUnlinkingBinds(webview::webview& wv);
void RegisterUnlinkingBinds(webwindowed::commands_builder& commands);
} // namespace ui
+24 -20
View File
@@ -89,16 +89,18 @@ namespace
return result;
}
void LoadFastFile(webview::webview& wv, std::string id, std::string path) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
void LoadFastFile(webwindowed::detail::window_base& calling_window,
std::string id,
std::string path) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
{
ModManContext::Get().m_db_thread.Dispatch(
[&wv, id, path]
[&calling_window, id, path]
{
auto maybeZone = ModManContext::Get().m_fast_file.LoadFastFile(path);
if (maybeZone)
{
ui::PromiseResolve(wv,
ui::PromiseResolve(calling_window,
id,
ZoneLoadedDto{
.zone = CreateZoneDto(*maybeZone.value()),
@@ -108,26 +110,28 @@ namespace
else
{
con::warn("Failed to load zone \"{}\": {}", path, maybeZone.error());
ui::PromiseReject(wv, id, std::move(maybeZone).error());
ui::PromiseReject(calling_window, id, std::move(maybeZone).error());
}
});
}
void UnloadZone(webview::webview& wv, std::string id, std::string zoneName) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
void UnloadZone(webwindowed::detail::window_base& calling_window,
std::string id,
std::string zoneName) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
{
ModManContext::Get().m_db_thread.Dispatch(
[&wv, id, zoneName]
[&calling_window, id, zoneName]
{
auto result = ModManContext::Get().m_fast_file.UnloadZone(zoneName);
if (result)
{
con::debug("Unloaded zone \"{}\"", zoneName);
ui::PromiseResolve(wv, id, true);
ui::PromiseResolve(calling_window, id, true);
}
else
{
con::warn("Failed unloading zone {}: {}", zoneName, result.error());
ui::PromiseReject(wv, id, std::move(result).error());
ui::PromiseReject(calling_window, id, std::move(result).error());
}
});
}
@@ -141,7 +145,7 @@ namespace ui
.zoneName = std::move(zoneName),
.percentage = percentage,
};
Notify(*ModManContext::Get().m_main_webview, "zoneLoadProgress", dto);
Notify(*ModManContext::Get().m_main_window, "zoneLoadProgress", dto);
}
void NotifyZoneLoaded(const LoadedZone& loadedZone)
@@ -149,7 +153,7 @@ namespace ui
const ZoneLoadedDto dto{
.zone = CreateZoneDto(loadedZone),
};
Notify(*ModManContext::Get().m_main_webview, "zoneLoaded", dto);
Notify(*ModManContext::Get().m_main_window, "zoneLoaded", dto);
}
void NotifyZoneUnloaded(std::string zoneName)
@@ -157,30 +161,30 @@ namespace ui
const ZoneUnloadedDto dto{
.zoneName = std::move(zoneName),
};
Notify(*ModManContext::Get().m_main_webview, "zoneUnloaded", dto);
Notify(*ModManContext::Get().m_main_window, "zoneUnloaded", dto);
}
void RegisterZoneBinds(webview::webview& wv)
void RegisterZoneBinds(webwindowed::commands_builder& commands)
{
BindRetOnly<std::vector<ZoneDto>>(wv,
BindRetOnly<std::vector<ZoneDto>>(commands,
"getZones",
[]
[](webwindowed::detail::window_base& calling_window)
{
return GetLoadedZones();
});
BindAsync<std::string>(wv,
BindAsync<std::string>(commands,
"loadFastFile",
[&wv](const std::string& id, std::string path)
[](const std::string& id, webwindowed::detail::window_base& calling_window, std::string path)
{
LoadFastFile(wv, id, std::move(path));
LoadFastFile(calling_window, id, std::move(path));
});
BindAsync<std::string>(wv,
BindAsync<std::string>(commands,
"unloadZone",
[&wv](const std::string& id, std::string zoneName)
[](const std::string& id, webwindowed::detail::window_base& calling_window, std::string zoneName)
{
UnloadZone(wv, id, std::move(zoneName));
UnloadZone(calling_window, id, std::move(zoneName));
});
}
} // namespace ui
+2 -2
View File
@@ -1,7 +1,7 @@
#pragma once
#include "Context/FastFileContext.h"
#include "Web/WebViewLib.h"
#include "Web/WebWindowedLib.h"
namespace ui
{
@@ -9,5 +9,5 @@ namespace ui
void NotifyZoneLoaded(const LoadedZone& loadedZone);
void NotifyZoneUnloaded(std::string zoneName);
void RegisterZoneBinds(webview::webview& wv);
void RegisterZoneBinds(webwindowed::commands_builder& commands);
} // namespace ui
-17
View File
@@ -1,17 +0,0 @@
#pragma once
#include "Web/Platform/Platform.h"
#include "Web/WebViewLib.h"
#include <webview/macros.h>
namespace ui
{
#if defined(PLATFORM_WINDOWS)
constexpr auto URL_PREFIX = "http://modman.local/";
#elif defined(PLATFORM_LINUX)
constexpr auto URL_PREFIX = "modman://localhost/";
#endif
void InstallAssetHandler(webview::webview& wv);
} // namespace ui
-10
View File
@@ -1,10 +0,0 @@
#pragma once
#include "Web/WebViewLib.h"
#include <webview/macros.h>
namespace ui
{
void InstallFaviconHandler(webview::webview& wv);
} // namespace ui
@@ -1,53 +0,0 @@
#include "Web/Platform/AssetHandler.h"
#include "Web/Platform/Platform.h"
#ifdef PLATFORM_LINUX
#include "Web/UiAssets.h"
#include <format>
#include <iostream>
#include <unordered_map>
namespace
{
std::unordered_map<std::string, UiFile> assetLookup;
void ModManUriSchemeRequestCb(WebKitURISchemeRequest* request, gpointer user_data)
{
const gchar* asset = webkit_uri_scheme_request_get_path(request);
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, ui::GetMimeTypeForFileName(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;
}
}
} // namespace
namespace ui
{
void InstallAssetHandler(webview::webview& wv)
{
const auto widget = static_cast<GtkWidget*>(wv.browser_controller().value());
const auto webView = WEBKIT_WEB_VIEW(widget);
const auto context = webkit_web_view_get_context(webView);
assetLookup = ui::BuildUiFileLookup();
webkit_web_context_register_uri_scheme(context, "modman", ModManUriSchemeRequestCb, NULL, nullptr);
}
} // namespace ui
#endif
@@ -1,22 +0,0 @@
#include "Web/Platform/FaviconHandler.h"
#include "Web/Platform/Platform.h"
#ifdef PLATFORM_LINUX
#include "Web/UiAssets.h"
#include <format>
#include <iostream>
#include <unordered_map>
namespace ui
{
void InstallFaviconHandler(webview::webview& wv)
{
// The icon system on Linux works a bit different than on Windows
// and doesn't really support this kind of dynamic icon setting
// we skip it for now
}
} // namespace ui
#endif
@@ -1,30 +0,0 @@
#include "Web/Platform/Platform.h"
#include "Web/Platform/TitleHandler.h"
#ifdef PLATFORM_LINUX
#include "Web/UiAssets.h"
#include <format>
#include <iostream>
#include <unordered_map>
namespace ui
{
void InstallTitleHandler(webview::webview& wv)
{
const auto webViewWidget = static_cast<GtkWidget*>(wv.browser_controller().value());
const auto webView = WEBKIT_WEB_VIEW(webViewWidget);
const auto windowWidget = static_cast<GtkWidget*>(wv.window().value());
const auto window = GTK_WINDOW(windowWidget);
auto on_title_changed = +[](GtkWidget* widget, GParamSpec paramSpec, GtkWindow* window)
{
gtk_window_set_title(window, webkit_web_view_get_title(WEBKIT_WEB_VIEW(widget)));
};
g_signal_connect(G_OBJECT(webView), "notify::title", G_CALLBACK(on_title_changed), (gpointer)window);
}
} // namespace ui
#endif
-10
View File
@@ -1,10 +0,0 @@
#pragma once
#include "Web/WebViewLib.h"
#include <webview/macros.h>
namespace ui
{
void InstallTitleHandler(webview::webview& wv);
} // namespace ui
@@ -1,161 +0,0 @@
#include "Web/Platform/AssetHandler.h"
#include "Web/Platform/Platform.h"
#ifdef PLATFORM_WINDOWS
#include "PlatformUtilsWindows.h"
#include "Web/UiAssets.h"
#include <Windows.h>
#include <format>
#include <iostream>
#include <sstream>
#include <webview/detail/backends/win32_edge.hh>
#include <wrl/event.h>
namespace
{
constexpr auto LOCALHOST_PREFIX = "http://localhost:";
std::unordered_map<std::string, UiFile> assetLookup;
std::wstring HeadersForAssetName(const std::string& assetName, const size_t contentLength)
{
std::wstringstream wss;
wss << std::format(L"Content-Length: {}\n", contentLength);
wss << L"Content-Type: " << utils::StringToWideString(ui::GetMimeTypeForFileName(assetName));
return wss.str();
}
HRESULT HandleResourceRequested(ICoreWebView2_22* core22, IUnknown* args)
{
Microsoft::WRL::ComPtr<ICoreWebView2WebResourceRequestedEventArgs2> webResourceRequestArgs;
if (SUCCEEDED(args->QueryInterface(IID_PPV_ARGS(&webResourceRequestArgs))))
{
COREWEBVIEW2_WEB_RESOURCE_REQUEST_SOURCE_KINDS requestSourceKind = COREWEBVIEW2_WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL;
if (!SUCCEEDED(webResourceRequestArgs->get_RequestedSourceKind(&requestSourceKind)))
{
std::cerr << "Failed to get requested source kind\n";
return S_FALSE;
}
Microsoft::WRL::ComPtr<ICoreWebView2WebResourceRequest> request;
if (!SUCCEEDED(webResourceRequestArgs->get_Request(&request)))
{
std::cerr << "Failed to get request\n";
return S_FALSE;
}
LPWSTR wUri;
if (!SUCCEEDED(request->get_Uri(&wUri)))
{
std::cerr << "Failed to get uri\n";
return S_FALSE;
}
Microsoft::WRL::ComPtr<ICoreWebView2Environment> environment;
if (!SUCCEEDED(core22->get_Environment(&environment)))
{
std::cerr << "Failed to get environment\n";
return S_FALSE;
}
Microsoft::WRL::ComPtr<ICoreWebView2WebResourceResponse> response;
const auto uri = utils::WideStringToString(wUri);
bool fileFound = false;
#ifdef _DEBUG
// Allow dev server access
if (uri.starts_with(LOCALHOST_PREFIX))
return S_OK;
#endif
if (uri.starts_with(ui::URL_PREFIX))
{
const auto asset = uri.substr(std::char_traits<char>::length(ui::URL_PREFIX) - 1);
const auto foundUiFile = assetLookup.find(asset);
if (foundUiFile != assetLookup.end())
{
const Microsoft::WRL::ComPtr<IStream> responseStream =
SHCreateMemStream(static_cast<const BYTE*>(foundUiFile->second.data), static_cast<UINT>(foundUiFile->second.dataSize));
const auto headers = HeadersForAssetName(asset, foundUiFile->second.dataSize);
if (!SUCCEEDED(environment->CreateWebResourceResponse(responseStream.Get(), 200, L"OK", headers.data(), &response)))
{
std::cerr << "Failed to create web resource\n";
return S_FALSE;
}
fileFound = true;
}
}
if (!fileFound)
{
if (!SUCCEEDED(environment->CreateWebResourceResponse(nullptr, 404, L"Not found", L"", &response)))
{
std::cerr << "Failed to create web resource\n";
return S_FALSE;
}
}
if (!SUCCEEDED(webResourceRequestArgs->put_Response(response.Get())))
{
std::cerr << "Failed to put response\n";
return S_FALSE;
}
return S_OK;
}
return S_FALSE;
}
} // namespace
namespace ui
{
void InstallAssetHandler(webview::webview& wv)
{
assetLookup = ui::BuildUiFileLookup();
const auto controller = static_cast<ICoreWebView2Controller*>(wv.browser_controller().value());
Microsoft::WRL::ComPtr<ICoreWebView2> core;
if (!SUCCEEDED(controller->get_CoreWebView2(&core)))
{
std::cerr << "Failed to get webview\n";
return;
}
Microsoft::WRL::ComPtr<ICoreWebView2_22> core22;
if (!SUCCEEDED(core->QueryInterface(IID_PPV_ARGS(&core22))))
{
std::cerr << "Failed to get core22\n";
return;
}
if (!SUCCEEDED(core22->AddWebResourceRequestedFilterWithRequestSourceKinds(
L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL, COREWEBVIEW2_WEB_RESOURCE_REQUEST_SOURCE_KINDS_ALL)))
{
std::cerr << "Failed to install request filter\n";
return;
}
EventRegistrationToken token;
if (!SUCCEEDED(core->add_WebResourceRequested(Microsoft::WRL::Callback<ICoreWebView2WebResourceRequestedEventHandler>(
[core22](ICoreWebView2* sender, IUnknown* args) -> HRESULT
{
return HandleResourceRequested(core22.Get(), args);
})
.Get(),
&token)))
{
std::cerr << "Failed to add resource requested filter\n";
}
}
} // namespace ui
#endif
@@ -1,150 +0,0 @@
#include "Web/Platform/FaviconHandler.h"
#include "Web/Platform/Platform.h"
#ifdef PLATFORM_WINDOWS
#include "PlatformUtilsWindows.h"
#include "Web/UiAssets.h"
#include <Windows.h>
#include <format>
#include <gdiplus.h>
#include <iostream>
#include <sstream>
#include <webview/detail/backends/win32_edge.hh>
#include <wrl/event.h>
namespace
{
class UniqueHIcon
{
public:
UniqueHIcon()
: m_icon(nullptr)
{
}
~UniqueHIcon()
{
if (m_icon)
{
DestroyIcon(m_icon);
m_icon = nullptr;
}
}
UniqueHIcon(const UniqueHIcon& other) = default;
UniqueHIcon(UniqueHIcon&& other) noexcept
: m_icon(other.m_icon)
{
other.m_icon = nullptr;
}
UniqueHIcon& operator=(const UniqueHIcon& other) = default;
UniqueHIcon& operator=(UniqueHIcon&& other) noexcept
{
m_icon = other.m_icon;
other.m_icon = nullptr;
return *this;
}
HICON& get()
{
return m_icon;
}
private:
HICON m_icon;
};
std::unordered_map<HWND, UniqueHIcon> icons;
HRESULT HandleFaviconChanged(ICoreWebView2_15* core15, HWND window)
{
LPWSTR url;
if (!SUCCEEDED(core15->get_FaviconUri(&url)))
{
std::cerr << "Failed to get favicon uri\n";
return S_FALSE;
}
const std::wstring strUrl(url);
CoTaskMemFree(url);
if (strUrl.empty())
{
icons.erase(icons.find(window));
SendMessage(window, WM_SETICON, ICON_SMALL, (LPARAM)NULL);
}
else
{
if (!SUCCEEDED(core15->GetFavicon(COREWEBVIEW2_FAVICON_IMAGE_FORMAT_PNG,
Microsoft::WRL::Callback<ICoreWebView2GetFaviconCompletedHandler>(
[window](HRESULT errorCode, IStream* iconStream) -> HRESULT
{
Gdiplus::Bitmap iconBitmap(iconStream);
UniqueHIcon icon;
if (iconBitmap.GetHICON(&icon.get()) == Gdiplus::Status::Ok)
{
SendMessage(window, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(icon.get()));
icons.emplace(window, std::move(icon)).first->second.get();
}
else
{
icons.erase(icons.find(window));
SendMessage(window, WM_SETICON, ICON_SMALL, NULL);
}
return S_OK;
})
.Get())))
{
std::cerr << "Failed to get favicon\n";
return S_FALSE;
}
}
return S_OK;
}
} // namespace
namespace ui
{
void InstallFaviconHandler(webview::webview& wv)
{
const auto controller = static_cast<ICoreWebView2Controller*>(wv.browser_controller().value());
auto window = static_cast<HWND>(wv.window().value());
Microsoft::WRL::ComPtr<ICoreWebView2> core;
if (!SUCCEEDED(controller->get_CoreWebView2(&core)))
{
std::cerr << "Failed to get webview\n";
return;
}
Microsoft::WRL::ComPtr<ICoreWebView2_15> core15;
if (!SUCCEEDED(core->QueryInterface(IID_PPV_ARGS(&core15))))
{
std::cerr << "Failed to get core15\n";
return;
}
const Gdiplus::GdiplusStartupInput gdiPlusStartupInput;
ULONG_PTR gdiPlusToken;
Gdiplus::GdiplusStartup(&gdiPlusToken, &gdiPlusStartupInput, nullptr);
EventRegistrationToken token;
if (!SUCCEEDED(core15->add_FaviconChanged(Microsoft::WRL::Callback<ICoreWebView2FaviconChangedEventHandler>(
[core15, window](ICoreWebView2* sender, IUnknown* args) -> HRESULT
{
return HandleFaviconChanged(core15.Get(), window);
})
.Get(),
&token)))
{
std::cerr << "Failed to add favicon handler\n";
}
}
} // namespace ui
#endif
@@ -1,62 +0,0 @@
#include "Web/Platform/Platform.h"
#include "Web/Platform/TitleHandler.h"
#ifdef PLATFORM_WINDOWS
#include "PlatformUtilsWindows.h"
#include "Web/UiAssets.h"
#include <Windows.h>
#include <format>
#include <iostream>
#include <sstream>
#include <webview/detail/backends/win32_edge.hh>
#include <wrl/event.h>
namespace
{
HRESULT HandleTitleChanged(ICoreWebView2* core, HWND window)
{
LPWSTR title;
if (!SUCCEEDED(core->get_DocumentTitle(&title)))
{
std::cerr << "Failed to get title\n";
return S_FALSE;
}
SetWindowTextW(window, title);
CoTaskMemFree(title);
return S_OK;
}
} // namespace
namespace ui
{
void InstallTitleHandler(webview::webview& wv)
{
const auto controller = static_cast<ICoreWebView2Controller*>(wv.browser_controller().value());
auto window = static_cast<HWND>(wv.window().value());
Microsoft::WRL::ComPtr<ICoreWebView2> core;
if (!SUCCEEDED(controller->get_CoreWebView2(&core)))
{
std::cerr << "Failed to get webview\n";
return;
}
EventRegistrationToken token;
if (!SUCCEEDED(core->add_DocumentTitleChanged(Microsoft::WRL::Callback<ICoreWebView2DocumentTitleChangedEventHandler>(
[window](ICoreWebView2* sender, IUnknown* args) -> HRESULT
{
return HandleTitleChanged(sender, window);
})
.Get(),
&token)))
{
std::cerr << "Failed to add title handler\n";
}
}
} // namespace ui
#endif
-34
View File
@@ -1,34 +0,0 @@
#include "UiAssets.h"
#include <format>
namespace ui
{
std::unordered_map<std::string, UiFile> BuildUiFileLookup()
{
std::unordered_map<std::string, UiFile> result;
for (const auto& asset : MOD_MAN_UI_FILES)
{
result.emplace(std::format("/{}", asset.filename), asset);
}
return result;
}
const char* GetMimeTypeForFileName(const std::string& fileName)
{
const char* mimeType;
if (fileName.ends_with(".html"))
mimeType = "text/html";
else if (fileName.ends_with(".js"))
mimeType = "text/javascript";
else if (fileName.ends_with(".css"))
mimeType = "text/css";
else
mimeType = "application/octet-stream";
return mimeType;
}
} // namespace ui
-12
View File
@@ -1,12 +0,0 @@
#pragma once
#include "Web/ViteAssets.h"
#include <string>
#include <unordered_map>
namespace ui
{
std::unordered_map<std::string, UiFile> BuildUiFileLookup();
const char* GetMimeTypeForFileName(const std::string& fileName);
} // namespace ui
+118 -109
View File
@@ -1,7 +1,7 @@
#pragma once
#include "Utils/Logging/Log.h"
#include "WebViewLib.h"
#include "WebWindowedLib.h"
#pragma warning(push, 0)
#include <nlohmann/json.hpp>
@@ -9,145 +9,154 @@
namespace ui
{
inline void Bind(webview::webview& wv, const std::string& name, std::function<void()> fn)
inline void Bind(webwindowed::commands_builder& commands, const std::string& name, std::function<void(webwindowed::detail::window_base& calling_window)> fn)
{
wv.bind(name,
[fn](const std::string& req) -> std::string
{
fn();
return "";
});
commands.add_command_void(name,
[fn2 = std::move(fn)](webwindowed::detail::window_base& calling_window, std::string message_json_str) -> std::string
{
fn2(calling_window);
return "";
});
}
template<typename TInput> void Bind(webview::webview& wv, const std::string& name, std::function<void(TInput)> fn)
template<typename TInput>
void
Bind(webwindowed::commands_builder& commands, const std::string& name, std::function<void(webwindowed::detail::window_base& calling_window, TInput)> fn)
{
wv.bind(name,
[fn](const std::string& req) -> std::string
{
TInput param;
try
{
const auto json = nlohmann::json::parse(req);
if (!json.is_array())
{
con::error("Webview params are not an array: {}", req);
return "";
}
commands.add_command_void(name,
[fn2 = std::move(fn)](webwindowed::detail::window_base& calling_window, std::string message_json_str) -> std::string
{
TInput param;
try
{
const auto json = nlohmann::json::parse(message_json_str);
if (!json.is_array())
{
con::error("Webwindowed params are not an array: {}", message_json_str);
return "";
}
if (json.empty())
param = nlohmann::json().get<TInput>();
else
param = json.at(0).get<TInput>();
}
catch (const nlohmann::json::exception& e)
{
con::error("Failed to parse json of webview call: {}", e.what());
return "";
}
if (json.empty())
param = nlohmann::json().get<TInput>();
else
param = json.at(0).get<TInput>();
}
catch (const nlohmann::json::exception& e)
{
con::error("Failed to parse json of webwindowed call: {}", e.what());
return "";
}
fn(std::move(param));
return "";
});
fn2(calling_window, std::move(param));
return "";
});
}
template<typename TReturn> void BindRetOnly(webview::webview& wv, const std::string& name, std::function<TReturn()> fn)
template<typename TReturn>
void BindRetOnly(webwindowed::commands_builder& commands,
const std::string& name,
std::function<TReturn(webwindowed::detail::window_base& calling_window)> fn)
{
wv.bind(name,
[fn](const std::string& req) -> std::string
{
auto result = fn();
commands.add_command_sync(name,
[fn2 = std::move(fn)](webwindowed::detail::window_base& calling_window, std::string message_json_str) -> std::string
{
auto result = fn2(calling_window);
return nlohmann::json(result).dump();
});
return nlohmann::json(result).dump();
});
}
template<typename TInput, typename TReturn> void Bind(webview::webview& wv, const std::string& name, std::function<TReturn(TInput)> fn)
template<typename TInput, typename TReturn>
void Bind(webwindowed::commands_builder& commands,
const std::string& name,
std::function<TReturn(webwindowed::detail::window_base& calling_window, TInput)> fn)
{
wv.bind(name,
[fn](const std::string& req) -> std::string
{
TInput param;
try
{
const auto json = nlohmann::json::parse(req);
if (!json.is_array())
{
con::error("Webview params are not an array: {}", req);
return "";
}
commands.add_command_sync(name,
[fn2 = std::move(fn)](webwindowed::detail::window_base& calling_window, std::string message_json_str) -> std::string
{
TInput param;
try
{
const auto json = nlohmann::json::parse(message_json_str);
if (!json.is_array())
{
con::error("Webwindowed params are not an array: {}", message_json_str);
return "";
}
if (json.empty())
param = nlohmann::json().get<TInput>();
else
param = json.at(0).get<TInput>();
}
catch (const nlohmann::json::exception& e)
{
con::error("Failed to parse json of webview call: {}", e.what());
return "";
}
if (json.empty())
param = nlohmann::json().get<TInput>();
else
param = json.at(0).get<TInput>();
}
catch (const nlohmann::json::exception& e)
{
con::error("Failed to parse json of webwindowed call: {}", e.what());
return "";
}
auto result = fn(std::move(param));
return nlohmann::json(result).dump();
});
auto result = fn2(calling_window, std::move(param));
return nlohmann::json(result).dump();
});
}
inline void BindAsync(webview::webview& wv, const std::string& name, std::function<void(const std::string& id)> fn)
inline void BindAsync(webwindowed::commands_builder& commands,
const std::string& name,
std::function<void(const std::string& id, webwindowed::detail::window_base& calling_window)> fn)
{
wv.bind(
name,
[fn](const std::string& id, const std::string& req, void* /* arg */)
{
fn(id);
},
nullptr);
commands.add_command_async(name,
[fn2 = std::move(fn)](std::string promise_id, webwindowed::detail::window_base& calling_window, std::string message_json_str)
{
fn2(promise_id, calling_window);
});
}
template<typename TInput> void BindAsync(webview::webview& wv, const std::string& name, std::function<void(const std::string& id, TInput)> fn)
template<typename TInput>
void BindAsync(webwindowed::commands_builder& commands,
const std::string& name,
std::function<void(const std::string& id, webwindowed::detail::window_base& calling_window, TInput)> fn)
{
wv.bind(
name,
[fn](const std::string& id, const std::string& req, void* /* arg */)
{
TInput param;
try
{
const auto json = nlohmann::json::parse(req);
if (!json.is_array())
{
con::error("Webview params are not an array: {}", req);
return "";
}
commands.add_command_async(name,
[fn2 = std::move(fn)](std::string promise_id, webwindowed::detail::window_base& calling_window, std::string message_json_str)
{
TInput param;
try
{
const auto json = nlohmann::json::parse(message_json_str);
if (!json.is_array())
{
con::error("Webwindowed params are not an array: {}", message_json_str);
return "";
}
if (json.empty())
param = nlohmann::json().get<TInput>();
else
param = json.at(0).get<TInput>();
}
catch (const nlohmann::json::exception& e)
{
con::error("Failed to parse json of webview call: {}", e.what());
return "";
}
if (json.empty())
param = nlohmann::json().get<TInput>();
else
param = json.at(0).get<TInput>();
}
catch (const nlohmann::json::exception& e)
{
con::error("Failed to parse json of webwindowed call: {}", e.what());
return "";
}
fn(id, std::move(param));
return "";
},
nullptr);
fn2(promise_id, calling_window, std::move(param));
return "";
});
}
template<typename TPayload> void PromiseResolve(webview::webview& wv, const std::string& id, const TPayload& payload)
template<typename TPayload> void PromiseResolve(webwindowed::detail::window_base& window, const std::string& id, const TPayload& payload)
{
wv.resolve(id, 0, nlohmann::json(payload).dump());
window.promise_resolve(id, nlohmann::json(payload).dump());
}
template<typename TPayload> void PromiseReject(webview::webview& wv, const std::string& id, const TPayload& payload)
template<typename TPayload> void PromiseReject(webwindowed::detail::window_base& window, const std::string& id, const TPayload& payload)
{
wv.resolve(id, 1, nlohmann::json(payload).dump());
window.promise_reject(id, nlohmann::json(payload).dump());
}
template<typename TPayload> void Notify(webview::webview& wv, const std::string& eventKey, const TPayload& payload)
template<typename TPayload> void Notify(webwindowed::detail::window_base& window, const std::string& eventKey, const TPayload& payload)
{
wv.notify(eventKey, nlohmann::json(payload).dump());
window.notify(eventKey, nlohmann::json(payload).dump());
}
} // namespace ui
@@ -9,7 +9,12 @@
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
#include <webview/webview.h>
#include <webwindowed/webwindowed.hpp>
// Plugins
#include <webwindowed/plugin/asset_handler.hpp>
#include <webwindowed/plugin/favicon_handler.hpp>
#include <webwindowed/plugin/title_handler.hpp>
#ifdef _MSC_VER
#pragma warning(pop)
+41 -56
View File
@@ -2,17 +2,13 @@
#include "GitVersion.h"
#include "ModManArgs.h"
#include "Web/Binds/Binds.h"
#include "Web/Platform/AssetHandler.h"
#include "Web/Platform/FaviconHandler.h"
#include "Web/Platform/TitleHandler.h"
#include "Web/UiCommunication.h"
#include "Web/ViteAssets.h"
#include "Web/WebViewLib.h"
#include "Web/WebWindowedLib.h"
#include <format>
#include <iostream>
#include <string>
#include <thread>
#ifdef _WIN32
#include <Windows.h>
@@ -29,22 +25,13 @@ namespace
auto& context = ModManContext::Get();
try
{
context.m_dev_tools_webview = std::make_unique<webview::webview>(false, nullptr);
auto& newWindow = *context.m_dev_tools_webview;
ui::InstallFaviconHandler(newWindow);
ui::InstallTitleHandler(newWindow);
context.m_dev_tools_window = std::make_shared<webwindowed::window>();
auto& newWindow = *context.m_dev_tools_window;
newWindow.set_title("Devtools");
newWindow.set_size(640, 480, WEBVIEW_HINT_NONE);
newWindow.set_size(480, 320, WEBVIEW_HINT_MIN);
newWindow.navigate(std::format("http://localhost:{}/__devtools__/", VITE_DEV_SERVER_PORT));
}
catch (const webview::exception& e)
{
std::cerr << e.what() << '\n';
}
newWindow.set_title("Devtools");
newWindow.set_window_size(640, 480);
newWindow.set_window_min(480, 320);
(void)newWindow.navigate(std::format("http://localhost:{}/__devtools__/", VITE_DEV_SERVER_PORT));
}
#endif
@@ -53,47 +40,45 @@ namespace
con::debug("Creating main window");
auto& context = ModManContext::Get();
try
{
context.m_main_webview = std::make_unique<webview::webview>(
#ifdef _DEBUG
true,
#else
false,
#endif
nullptr);
auto& newWindow = *context.m_main_webview;
newWindow.set_title("OpenAssetTools ModMan");
newWindow.set_size(1280, 640, WEBVIEW_HINT_NONE);
newWindow.set_size(640, 480, WEBVIEW_HINT_MIN);
ui::InstallAssetHandler(newWindow);
ui::InstallFaviconHandler(newWindow);
ui::InstallTitleHandler(newWindow);
ui::RegisterAllBinds(newWindow);
context.m_main_window = std::make_shared<webwindowed::window>();
auto& newWindow = *context.m_main_window;
#ifdef _DEBUG
newWindow.navigate(VITE_DEV_SERVER ? std::format("http://localhost:{}", VITE_DEV_SERVER_PORT) : std::format("{}index.html", ui::URL_PREFIX));
if (VITE_DEV_SERVER)
{
newWindow.dispatch(
[]
{
SpawnDevToolsWindow();
});
}
#else
newWindow.navigate(std::format("{}index.html", ui::URL_PREFIX));
newWindow.set_debug(true);
#endif
newWindow.run();
}
catch (const webview::exception& e)
newWindow.set_title("OpenAssetTools ModMan");
// newWindow.set_window_min(640, 480);
newWindow.set_window_size(1280, 640);
const auto assetHandlerPlugin = std::make_shared<webwindowed::asset_handler_plugin>(VITE_ASSETS, std::extent_v<decltype(VITE_ASSETS)>);
assetHandlerPlugin->set_protocol_name("modman");
newWindow.register_plugin(assetHandlerPlugin);
webwindowed::commands_builder commands;
ui::RegisterAllBinds(commands);
newWindow.set_commands(commands.build());
#ifdef _DEBUG
auto result = newWindow.navigate(VITE_DEV_SERVER ? std::format("http://localhost:{}", VITE_DEV_SERVER_PORT)
: assetHandlerPlugin->get_url_for_asset("index.html"));
if (VITE_DEV_SERVER)
{
std::cerr << e.what() << '\n';
return 1;
newWindow.dispatch(
[]
{
SpawnDevToolsWindow();
});
}
#else
auto result = newWindow.navigate(assetHandlerPlugin->get_url_for_asset("index.html"));
#endif
webwindowed::app app;
app.register_plugin(std::make_shared<webwindowed::favicon_handler_plugin>());
app.register_plugin(std::make_shared<webwindowed::title_handler_plugin>());
(void)app.run(context.m_main_window);
return 0;
}