diff --git a/src/ModMan/Web/Platform/AssetHandler.h b/src/ModMan/Web/Platform/AssetHandler.h index ebdb1ebf..89256ce2 100644 --- a/src/ModMan/Web/Platform/AssetHandler.h +++ b/src/ModMan/Web/Platform/AssetHandler.h @@ -1,7 +1,17 @@ #pragma once -#ifdef _WIN32 -#include "Windows/AssetHandlerWindows.h" -#elif defined(__linux__) -#include "Linux/AssetHandlerLinux.h" +#include "Web/Platform/Platform.h" +#include "Web/WebViewLib.h" + +#include + +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 diff --git a/src/ModMan/Web/Platform/FaviconHandler.h b/src/ModMan/Web/Platform/FaviconHandler.h new file mode 100644 index 00000000..52d2016e --- /dev/null +++ b/src/ModMan/Web/Platform/FaviconHandler.h @@ -0,0 +1,10 @@ +#pragma once + +#include "Web/WebViewLib.h" + +#include + +namespace ui +{ + void InstallFaviconHandler(webview::webview& wv); +} // namespace ui diff --git a/src/ModMan/Web/Platform/Linux/AssetHandlerLinux.cpp b/src/ModMan/Web/Platform/Linux/AssetHandlerLinux.cpp index bb41be84..ae3826e3 100644 --- a/src/ModMan/Web/Platform/Linux/AssetHandlerLinux.cpp +++ b/src/ModMan/Web/Platform/Linux/AssetHandlerLinux.cpp @@ -1,6 +1,7 @@ -#include "AssetHandlerLinux.h" +#include "Web/Platform/AssetHandler.h" +#include "Web/Platform/Platform.h" -#if defined(WEBVIEW_PLATFORM_LINUX) && defined(WEBVIEW_GTK) +#ifdef PLATFORM_LINUX #include "Web/UiAssets.h" @@ -8,8 +9,6 @@ #include #include -using namespace PLATFORM_NAMESPACE_LINUX; - namespace { std::unordered_map assetLookup; @@ -37,7 +36,7 @@ namespace } } // namespace -namespace PLATFORM_NAMESPACE_LINUX +namespace ui { void InstallAssetHandler(webview::webview& wv) { @@ -49,6 +48,6 @@ namespace PLATFORM_NAMESPACE_LINUX webkit_web_context_register_uri_scheme(context, "modman", ModManUriSchemeRequestCb, NULL, nullptr); } -} // namespace PLATFORM_NAMESPACE_LINUX +} // namespace ui #endif diff --git a/src/ModMan/Web/Platform/Linux/AssetHandlerLinux.h b/src/ModMan/Web/Platform/Linux/AssetHandlerLinux.h deleted file mode 100644 index e8f49f2f..00000000 --- a/src/ModMan/Web/Platform/Linux/AssetHandlerLinux.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "Web/Platform/Platform.h" - -#include - -#if defined(WEBVIEW_PLATFORM_LINUX) && defined(WEBVIEW_GTK) - -#include "Web/WebViewLib.h" - -namespace PLATFORM_NAMESPACE_LINUX -{ - constexpr auto URL_PREFIX = "modman://localhost/"; - - void InstallAssetHandler(webview::webview& wv); -} // namespace PLATFORM_NAMESPACE_LINUX - -#endif diff --git a/src/ModMan/Web/Platform/Linux/DialogHandlerLinux.cpp b/src/ModMan/Web/Platform/Linux/DialogHandlerLinux.cpp index 16fcdc6e..72963ac5 100644 --- a/src/ModMan/Web/Platform/Linux/DialogHandlerLinux.cpp +++ b/src/ModMan/Web/Platform/Linux/DialogHandlerLinux.cpp @@ -1,6 +1,7 @@ #include "Web/Platform/DialogHandler.h" +#include "Web/Platform/Platform.h" -#ifdef __linux__ +#ifdef PLATFORM_LINUX #include #include @@ -37,8 +38,6 @@ namespace #endif } - void OpenFileDialog() {} - void SetFilters(GtkFileDialog* pDialog, const std::vector& filters) { if (filters.empty()) diff --git a/src/ModMan/Web/Platform/Linux/FaviconHandlerLinux.cpp b/src/ModMan/Web/Platform/Linux/FaviconHandlerLinux.cpp new file mode 100644 index 00000000..c0928934 --- /dev/null +++ b/src/ModMan/Web/Platform/Linux/FaviconHandlerLinux.cpp @@ -0,0 +1,22 @@ +#include "Web/Platform/FaviconHandler.h" +#include "Web/Platform/Platform.h" + +#ifdef PLATFORM_LINUX + +#include "Web/UiAssets.h" + +#include +#include +#include + +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 diff --git a/src/ModMan/Web/Platform/Linux/TitleHandlerLinux.cpp b/src/ModMan/Web/Platform/Linux/TitleHandlerLinux.cpp new file mode 100644 index 00000000..78eb8e1a --- /dev/null +++ b/src/ModMan/Web/Platform/Linux/TitleHandlerLinux.cpp @@ -0,0 +1,30 @@ +#include "Web/Platform/Platform.h" +#include "Web/Platform/TitleHandler.h" + +#ifdef PLATFORM_LINUX + +#include "Web/UiAssets.h" + +#include +#include +#include + +namespace ui +{ + void InstallTitleHandler(webview::webview& wv) + { + const auto webViewWidget = static_cast(wv.browser_controller().value()); + const auto webView = WEBKIT_WEB_VIEW(webViewWidget); + const auto windowWidget = static_cast(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 diff --git a/src/ModMan/Web/Platform/Platform.h b/src/ModMan/Web/Platform/Platform.h index e531deca..787fe814 100644 --- a/src/ModMan/Web/Platform/Platform.h +++ b/src/ModMan/Web/Platform/Platform.h @@ -1,10 +1,7 @@ #pragma once -#define PLATFORM_NAMESPACE_WINDOWS windows -#define PLATFORM_NAMESPACE_LINUX linux - #ifdef _WIN32 -#define PLATFORM_NAMESPACE PLATFORM_NAMESPACE_WINDOWS +#define PLATFORM_WINDOWS #elif defined(__linux__) -#define PLATFORM_NAMESPACE PLATFORM_NAMESPACE_LINUX +#define PLATFORM_LINUX #endif diff --git a/src/ModMan/Web/Platform/TitleHandler.h b/src/ModMan/Web/Platform/TitleHandler.h new file mode 100644 index 00000000..4ff8ffae --- /dev/null +++ b/src/ModMan/Web/Platform/TitleHandler.h @@ -0,0 +1,10 @@ +#pragma once + +#include "Web/WebViewLib.h" + +#include + +namespace ui +{ + void InstallTitleHandler(webview::webview& wv); +} // namespace ui diff --git a/src/ModMan/Web/Platform/Windows/AssetHandlerWindows.cpp b/src/ModMan/Web/Platform/Windows/AssetHandlerWindows.cpp index ba400bd0..9e2309bb 100644 --- a/src/ModMan/Web/Platform/Windows/AssetHandlerWindows.cpp +++ b/src/ModMan/Web/Platform/Windows/AssetHandlerWindows.cpp @@ -1,6 +1,7 @@ -#include "AssetHandlerWindows.h" +#include "Web/Platform/AssetHandler.h" +#include "Web/Platform/Platform.h" -#if defined(WEBVIEW_PLATFORM_WINDOWS) && defined(WEBVIEW_EDGE) +#ifdef PLATFORM_WINDOWS #include "PlatformUtilsWindows.h" #include "Web/UiAssets.h" @@ -12,8 +13,6 @@ #include #include -using namespace PLATFORM_NAMESPACE_WINDOWS; - namespace { constexpr auto LOCALHOST_PREFIX = "http://localhost:"; @@ -25,7 +24,7 @@ namespace std::wstringstream wss; wss << std::format(L"Content-Length: {}\n", contentLength); - wss << L"Content-Type: " << StringToWideString(ui::GetMimeTypeForFileName(assetName)); + wss << L"Content-Type: " << utils::StringToWideString(ui::GetMimeTypeForFileName(assetName)); return wss.str(); } @@ -65,7 +64,7 @@ namespace Microsoft::WRL::ComPtr response; - const auto uri = WideStringToString(wUri); + const auto uri = utils::WideStringToString(wUri); bool fileFound = false; #ifdef _DEBUG @@ -74,9 +73,9 @@ namespace return S_OK; #endif - if (uri.starts_with(URL_PREFIX)) + if (uri.starts_with(ui::URL_PREFIX)) { - const auto asset = uri.substr(std::char_traits::length(URL_PREFIX) - 1); + const auto asset = uri.substr(std::char_traits::length(ui::URL_PREFIX) - 1); const auto foundUiFile = assetLookup.find(asset); if (foundUiFile != assetLookup.end()) @@ -117,7 +116,7 @@ namespace } } // namespace -namespace PLATFORM_NAMESPACE_WINDOWS +namespace ui { void InstallAssetHandler(webview::webview& wv) { @@ -157,6 +156,6 @@ namespace PLATFORM_NAMESPACE_WINDOWS std::cerr << "Failed to add resource requested filter\n"; } } -} // namespace PLATFORM_NAMESPACE_WINDOWS +} // namespace ui #endif diff --git a/src/ModMan/Web/Platform/Windows/AssetHandlerWindows.h b/src/ModMan/Web/Platform/Windows/AssetHandlerWindows.h deleted file mode 100644 index b6475fd9..00000000 --- a/src/ModMan/Web/Platform/Windows/AssetHandlerWindows.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "Web/Platform/Platform.h" - -#include - -#if defined(WEBVIEW_PLATFORM_WINDOWS) && defined(WEBVIEW_EDGE) - -#include "Web/WebViewLib.h" - -namespace PLATFORM_NAMESPACE_WINDOWS -{ - constexpr auto URL_PREFIX = "http://modman.local/"; - - void InstallAssetHandler(webview::webview& wv); -} // namespace PLATFORM_NAMESPACE_WINDOWS - -#endif diff --git a/src/ModMan/Web/Platform/Windows/DialogHandlerWindows.cpp b/src/ModMan/Web/Platform/Windows/DialogHandlerWindows.cpp index 68b34f23..396b31ba 100644 --- a/src/ModMan/Web/Platform/Windows/DialogHandlerWindows.cpp +++ b/src/ModMan/Web/Platform/Windows/DialogHandlerWindows.cpp @@ -1,6 +1,7 @@ #include "Web/Platform/DialogHandler.h" +#include "Web/Platform/Platform.h" -#ifdef _WIN32 +#ifdef PLATFORM_WINDOWS #include "PlatformUtilsWindows.h" @@ -8,8 +9,6 @@ #include #include -using namespace PLATFORM_NAMESPACE_WINDOWS; - namespace { bool SetFilters(IFileDialog* pFileOpen, const std::vector& filters) @@ -29,8 +28,8 @@ namespace const auto& filter = filters[i]; COMDLG_FILTERSPEC filterSpec; - const auto& wName = filterStrings.emplace_back(StringToWideString(filter.m_name)); - const auto& wSpec = filterStrings.emplace_back(StringToWideString(filter.m_filter)); + const auto& wName = filterStrings.emplace_back(utils::StringToWideString(filter.m_name)); + const auto& wSpec = filterStrings.emplace_back(utils::StringToWideString(filter.m_filter)); filterSpec.pszName = wName.c_str(); filterSpec.pszSpec = wSpec.c_str(); @@ -65,7 +64,7 @@ namespace // Display the file name to the user. if (SUCCEEDED(hr)) { - result = WideStringToString(pszFilePath); + result = utils::WideStringToString(pszFilePath); CoTaskMemFree(pszFilePath); resultType = ui::DialogCallbackResultType::SUCCESSFUL; diff --git a/src/ModMan/Web/Platform/Windows/FaviconHandlerWindows.cpp b/src/ModMan/Web/Platform/Windows/FaviconHandlerWindows.cpp new file mode 100644 index 00000000..856deb68 --- /dev/null +++ b/src/ModMan/Web/Platform/Windows/FaviconHandlerWindows.cpp @@ -0,0 +1,150 @@ +#include "Web/Platform/FaviconHandler.h" +#include "Web/Platform/Platform.h" + +#ifdef PLATFORM_WINDOWS + +#include "PlatformUtilsWindows.h" +#include "Web/UiAssets.h" + +#include +#include +#include +#include +#include +#include +#include + +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 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( + [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(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(wv.browser_controller().value()); + auto window = static_cast(wv.window().value()); + Microsoft::WRL::ComPtr core; + if (!SUCCEEDED(controller->get_CoreWebView2(&core))) + { + std::cerr << "Failed to get webview\n"; + return; + } + + Microsoft::WRL::ComPtr 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( + [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 diff --git a/src/ModMan/Web/Platform/Windows/PlatformUtilsWindows.cpp b/src/ModMan/Web/Platform/Windows/PlatformUtilsWindows.cpp index b9e419ad..17c43fef 100644 --- a/src/ModMan/Web/Platform/Windows/PlatformUtilsWindows.cpp +++ b/src/ModMan/Web/Platform/Windows/PlatformUtilsWindows.cpp @@ -1,12 +1,12 @@ #include "PlatformUtilsWindows.h" -#ifdef _WIN32 +#ifdef PLATFORM_WINDOWS #include #include #include -namespace PLATFORM_NAMESPACE_WINDOWS +namespace utils { std::string WideStringToString(const std::wstring& wideString) { @@ -35,6 +35,6 @@ namespace PLATFORM_NAMESPACE_WINDOWS MultiByteToWideChar(CP_UTF8, 0, string.data(), static_cast(string.size()), result.data(), sizeNeeded); return result; } -} // namespace PLATFORM_NAMESPACE_WINDOWS +} // namespace utils #endif diff --git a/src/ModMan/Web/Platform/Windows/PlatformUtilsWindows.h b/src/ModMan/Web/Platform/Windows/PlatformUtilsWindows.h index 0edb4b86..5476f3f6 100644 --- a/src/ModMan/Web/Platform/Windows/PlatformUtilsWindows.h +++ b/src/ModMan/Web/Platform/Windows/PlatformUtilsWindows.h @@ -1,16 +1,15 @@ #pragma once -#ifdef _WIN32 - #include "Web/Platform/Platform.h" -#include +#ifdef PLATFORM_WINDOWS + #include -namespace PLATFORM_NAMESPACE_WINDOWS +namespace utils { std::string WideStringToString(const std::wstring& wideString); std::wstring StringToWideString(const std::string& string); -} // namespace PLATFORM_NAMESPACE_WINDOWS +} // namespace utils #endif diff --git a/src/ModMan/Web/Platform/Windows/TitleHandlerWindows.cpp b/src/ModMan/Web/Platform/Windows/TitleHandlerWindows.cpp new file mode 100644 index 00000000..37548484 --- /dev/null +++ b/src/ModMan/Web/Platform/Windows/TitleHandlerWindows.cpp @@ -0,0 +1,62 @@ +#include "Web/Platform/Platform.h" +#include "Web/Platform/TitleHandler.h" + +#ifdef PLATFORM_WINDOWS + +#include "PlatformUtilsWindows.h" +#include "Web/UiAssets.h" + +#include +#include +#include +#include +#include +#include + +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(wv.browser_controller().value()); + auto window = static_cast(wv.window().value()); + Microsoft::WRL::ComPtr 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( + [window](ICoreWebView2* sender, IUnknown* args) -> HRESULT + { + return HandleTitleChanged(sender, window); + }) + .Get(), + &token))) + { + std::cerr << "Failed to add title handler\n"; + } + } +} // namespace ui + +#endif diff --git a/src/ModMan/main.cpp b/src/ModMan/main.cpp index b7f82fa6..fe8eeba1 100644 --- a/src/ModMan/main.cpp +++ b/src/ModMan/main.cpp @@ -3,6 +3,8 @@ #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" @@ -17,7 +19,6 @@ #endif using namespace std::string_literals; -using namespace PLATFORM_NAMESPACE; namespace { @@ -32,6 +33,8 @@ namespace { context.m_dev_tools_webview = std::make_unique(false, nullptr); auto& newWindow = *context.m_dev_tools_webview; + ui::InstallFaviconHandler(newWindow); + ui::InstallTitleHandler(newWindow); newWindow.set_title("Devtools"); newWindow.set_size(640, 480, WEBVIEW_HINT_NONE); @@ -65,11 +68,13 @@ namespace newWindow.set_size(1280, 640, WEBVIEW_HINT_NONE); newWindow.set_size(640, 480, WEBVIEW_HINT_MIN); - InstallAssetHandler(newWindow); + ui::InstallAssetHandler(newWindow); + ui::InstallFaviconHandler(newWindow); + ui::InstallTitleHandler(newWindow); ui::RegisterAllBinds(newWindow); #ifdef _DEBUG - newWindow.navigate(VITE_DEV_SERVER ? std::format("http://localhost:{}", VITE_DEV_SERVER_PORT) : std::format("{}index.html", URL_PREFIX)); + newWindow.navigate(VITE_DEV_SERVER ? std::format("http://localhost:{}", VITE_DEV_SERVER_PORT) : std::format("{}index.html", ui::URL_PREFIX)); if (VITE_DEV_SERVER) { @@ -80,7 +85,7 @@ namespace }); } #else - newWindow.navigate(std::format("{}index.html", URL_PREFIX)); + newWindow.navigate(std::format("{}index.html", ui::URL_PREFIX)); #endif newWindow.run(); } diff --git a/thirdparty/webview.lua b/thirdparty/webview.lua index 5fef3927..20fecd52 100644 --- a/thirdparty/webview.lua +++ b/thirdparty/webview.lua @@ -13,6 +13,7 @@ function webview:link(links) if os.host() == "windows" then links:add("WebView2LoaderStatic") + links:add("gdiplus.lib") filter "platforms:x86" libdirs {