mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2025-11-23 13:12:06 +00:00
feat: add dialog handler for ModMan
This commit is contained in:
162
src/ModMan/Web/Platform/Windows/AssetHandlerWindows.cpp
Normal file
162
src/ModMan/Web/Platform/Windows/AssetHandlerWindows.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include "AssetHandlerWindows.h"
|
||||
|
||||
#if defined(WEBVIEW_PLATFORM_WINDOWS) && defined(WEBVIEW_EDGE)
|
||||
|
||||
#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>
|
||||
|
||||
using namespace PLATFORM_NAMESPACE_WINDOWS;
|
||||
|
||||
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: " << 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 = 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(URL_PREFIX))
|
||||
{
|
||||
const auto asset = uri.substr(std::char_traits<char>::length(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 PLATFORM_NAMESPACE_WINDOWS
|
||||
{
|
||||
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 PLATFORM_NAMESPACE_WINDOWS
|
||||
|
||||
#endif
|
||||
18
src/ModMan/Web/Platform/Windows/AssetHandlerWindows.h
Normal file
18
src/ModMan/Web/Platform/Windows/AssetHandlerWindows.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "Web/Platform/Platform.h"
|
||||
|
||||
#include <webview/macros.h>
|
||||
|
||||
#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
|
||||
239
src/ModMan/Web/Platform/Windows/DialogHandlerWindows.cpp
Normal file
239
src/ModMan/Web/Platform/Windows/DialogHandlerWindows.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
#include "DialogHandlerWindows.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "PlatformUtilsWindows.h"
|
||||
|
||||
#include <shobjidl.h>
|
||||
#include <thread>
|
||||
#include <windows.h>
|
||||
|
||||
using namespace PLATFORM_NAMESPACE_WINDOWS;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool SetFilters(IFileDialog* pFileOpen, const std::vector<FileDialogFilter>& filters)
|
||||
{
|
||||
if (filters.empty())
|
||||
return true;
|
||||
|
||||
const auto filterCount = filters.size();
|
||||
std::vector<COMDLG_FILTERSPEC> filterSpecs;
|
||||
filterSpecs.reserve(filterCount + 1);
|
||||
|
||||
std::vector<std::wstring> filterStrings;
|
||||
filterStrings.reserve(filterCount * 2);
|
||||
|
||||
for (auto i = 0u; i < filterCount; i++)
|
||||
{
|
||||
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));
|
||||
|
||||
filterSpec.pszName = wName.c_str();
|
||||
filterSpec.pszSpec = wSpec.c_str();
|
||||
|
||||
filterSpecs.emplace_back(filterSpec);
|
||||
}
|
||||
|
||||
COMDLG_FILTERSPEC wildCardSpec;
|
||||
wildCardSpec.pszName = L"*.*";
|
||||
wildCardSpec.pszSpec = L"*.*";
|
||||
filterSpecs.emplace_back(wildCardSpec);
|
||||
|
||||
const auto result = pFileOpen->SetFileTypes(static_cast<UINT>(filterCount + 1), filterSpecs.data());
|
||||
|
||||
return SUCCEEDED(result);
|
||||
}
|
||||
|
||||
DialogCallbackResultType ShowFileDialog(IFileDialog* pFileDialog, std::optional<std::string>& result)
|
||||
{
|
||||
DialogCallbackResultType resultType = FAILED;
|
||||
|
||||
auto hr = pFileDialog->Show(nullptr);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
IShellItem* pItem;
|
||||
hr = pFileDialog->GetResult(&pItem);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
PWSTR pszFilePath;
|
||||
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
|
||||
|
||||
// Display the file name to the user.
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
result = WideStringToString(pszFilePath);
|
||||
CoTaskMemFree(pszFilePath);
|
||||
|
||||
resultType = SUCCESSFUL;
|
||||
}
|
||||
pItem->Release();
|
||||
}
|
||||
}
|
||||
else if (HRESULT_FROM_WIN32(ERROR_CANCELLED) == hr)
|
||||
{
|
||||
resultType = CANCELLED;
|
||||
}
|
||||
|
||||
return resultType;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace PLATFORM_NAMESPACE_WINDOWS
|
||||
{
|
||||
DialogWithCallback::DialogWithCallback() = default;
|
||||
|
||||
void DialogWithCallback::SetCallback(callback_t callback)
|
||||
{
|
||||
m_callback = std::move(callback);
|
||||
}
|
||||
|
||||
FileDialogFilter::FileDialogFilter(std::string name, std::string filter)
|
||||
: m_name(std::move(name)),
|
||||
m_filter(std::move(filter))
|
||||
{
|
||||
}
|
||||
|
||||
FileDialog::FileDialog() = default;
|
||||
|
||||
void FileDialog::AddFilter(std::string name, std::string filter)
|
||||
{
|
||||
m_filters.emplace_back(std::move(name), std::move(filter));
|
||||
}
|
||||
|
||||
OpenFileDialog::OpenFileDialog() = default;
|
||||
|
||||
void OpenFileDialog::OpenAsync()
|
||||
{
|
||||
// Move data out of the dialog object since it may be destroyed
|
||||
callback_t callback(std::move(m_callback));
|
||||
std::vector filters(std::move(m_filters));
|
||||
|
||||
// Windows dialogs are not asynchronous -> Spawn another thread
|
||||
std::thread(
|
||||
[callback, filters]
|
||||
{
|
||||
const auto initResult = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
if (!SUCCEEDED(initResult))
|
||||
{
|
||||
callback(FAILED, std::nullopt);
|
||||
return;
|
||||
}
|
||||
|
||||
DialogCallbackResultType resultType = FAILED;
|
||||
std::optional<std::string> result = std::nullopt;
|
||||
IFileOpenDialog* pFileOpen;
|
||||
|
||||
const auto hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));
|
||||
|
||||
if (SUCCEEDED(hr) && SetFilters(pFileOpen, filters))
|
||||
{
|
||||
resultType = ShowFileDialog(pFileOpen, result);
|
||||
pFileOpen->Release();
|
||||
}
|
||||
|
||||
CoUninitialize();
|
||||
|
||||
callback(resultType, result);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
SaveFileDialog::SaveFileDialog() = default;
|
||||
|
||||
void SaveFileDialog::OpenAsync()
|
||||
{
|
||||
// Move data out of the dialog object since it may be destroyed
|
||||
callback_t callback(std::move(m_callback));
|
||||
std::vector filters(std::move(m_filters));
|
||||
|
||||
// Windows dialogs are not asynchronous -> Spawn another thread
|
||||
std::thread(
|
||||
[callback, filters]
|
||||
{
|
||||
const auto initResult = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
if (!SUCCEEDED(initResult))
|
||||
{
|
||||
callback(FAILED, std::nullopt);
|
||||
return;
|
||||
}
|
||||
|
||||
DialogCallbackResultType resultType = FAILED;
|
||||
std::optional<std::string> result = std::nullopt;
|
||||
IFileSaveDialog* pFileSave;
|
||||
|
||||
const auto hr = CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_ALL, IID_IFileSaveDialog, reinterpret_cast<void**>(&pFileSave));
|
||||
|
||||
if (SUCCEEDED(hr) && SetFilters(pFileSave, filters))
|
||||
{
|
||||
resultType = ShowFileDialog(pFileSave, result);
|
||||
pFileSave->Release();
|
||||
}
|
||||
|
||||
CoUninitialize();
|
||||
|
||||
callback(resultType, result);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
FolderSelectDialog::FolderSelectDialog() = default;
|
||||
|
||||
void FolderSelectDialog::OpenAsync()
|
||||
{
|
||||
// Move data out of the dialog object since it may be destroyed
|
||||
callback_t callback(std::move(m_callback));
|
||||
|
||||
// Windows dialogs are not asynchronous -> Spawn another thread
|
||||
std::thread(
|
||||
[callback]
|
||||
{
|
||||
const auto initResult = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
if (!SUCCEEDED(initResult))
|
||||
{
|
||||
callback(FAILED, std::nullopt);
|
||||
return;
|
||||
}
|
||||
|
||||
std::optional<std::string> result = std::nullopt;
|
||||
IFileOpenDialog* pFileOpen;
|
||||
|
||||
const auto hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));
|
||||
if (!SUCCEEDED(hr))
|
||||
{
|
||||
CoUninitialize();
|
||||
callback(FAILED, std::nullopt);
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD dwOptions = 0;
|
||||
if (!SUCCEEDED(pFileOpen->GetOptions(&dwOptions)))
|
||||
{
|
||||
pFileOpen->Release();
|
||||
CoUninitialize();
|
||||
callback(FAILED, std::nullopt);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SUCCEEDED(pFileOpen->SetOptions(dwOptions | FOS_PICKFOLDERS)))
|
||||
{
|
||||
pFileOpen->Release();
|
||||
CoUninitialize();
|
||||
callback(FAILED, std::nullopt);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto resultType = ShowFileDialog(pFileOpen, result);
|
||||
|
||||
pFileOpen->Release();
|
||||
CoUninitialize();
|
||||
|
||||
callback(resultType, result);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
} // namespace PLATFORM_NAMESPACE_WINDOWS
|
||||
#endif
|
||||
80
src/ModMan/Web/Platform/Windows/DialogHandlerWindows.h
Normal file
80
src/ModMan/Web/Platform/Windows/DialogHandlerWindows.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "Web/Platform/Platform.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace PLATFORM_NAMESPACE_WINDOWS
|
||||
{
|
||||
enum DialogCallbackResultType : std::uint8_t
|
||||
{
|
||||
SUCCESSFUL,
|
||||
CANCELLED,
|
||||
FAILED
|
||||
};
|
||||
|
||||
class DialogWithCallback
|
||||
{
|
||||
public:
|
||||
using callback_t = std::function<void(DialogCallbackResultType resultType, std::optional<std::string> result)>;
|
||||
|
||||
DialogWithCallback();
|
||||
|
||||
void SetCallback(callback_t callback);
|
||||
|
||||
protected:
|
||||
callback_t m_callback;
|
||||
};
|
||||
|
||||
class FileDialogFilter
|
||||
{
|
||||
public:
|
||||
FileDialogFilter(std::string name, std::string filter);
|
||||
|
||||
std::string m_name;
|
||||
std::string m_filter;
|
||||
};
|
||||
|
||||
class FileDialog : public DialogWithCallback
|
||||
{
|
||||
public:
|
||||
FileDialog();
|
||||
|
||||
void AddFilter(std::string name, std::string filter);
|
||||
|
||||
protected:
|
||||
std::vector<FileDialogFilter> m_filters;
|
||||
};
|
||||
|
||||
class OpenFileDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
OpenFileDialog();
|
||||
|
||||
void OpenAsync();
|
||||
};
|
||||
|
||||
class SaveFileDialog : public FileDialog
|
||||
{
|
||||
public:
|
||||
SaveFileDialog();
|
||||
|
||||
void OpenAsync();
|
||||
};
|
||||
|
||||
class FolderSelectDialog : public DialogWithCallback
|
||||
{
|
||||
public:
|
||||
FolderSelectDialog();
|
||||
|
||||
void OpenAsync();
|
||||
};
|
||||
} // namespace PLATFORM_NAMESPACE_WINDOWS
|
||||
|
||||
#endif
|
||||
40
src/ModMan/Web/Platform/Windows/PlatformUtilsWindows.cpp
Normal file
40
src/ModMan/Web/Platform/Windows/PlatformUtilsWindows.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "PlatformUtilsWindows.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <exception>
|
||||
#include <format>
|
||||
#include <Windows.h>
|
||||
|
||||
namespace PLATFORM_NAMESPACE_WINDOWS
|
||||
{
|
||||
std::string WideStringToString(const std::wstring& wideString)
|
||||
{
|
||||
if (wideString.empty())
|
||||
return "";
|
||||
|
||||
const auto sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, wideString.data(), static_cast<int>(wideString.size()), nullptr, 0, nullptr, nullptr);
|
||||
if (sizeNeeded <= 0)
|
||||
throw std::runtime_error(std::format("WideCharToMultiByte() failed: {}", sizeNeeded));
|
||||
|
||||
std::string result(sizeNeeded, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wideString.data(), static_cast<int>(wideString.size()), result.data(), sizeNeeded, nullptr, nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring StringToWideString(const std::string& string)
|
||||
{
|
||||
if (string.empty())
|
||||
return L"";
|
||||
|
||||
const auto sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, string.data(), static_cast<int>(string.size()), nullptr, 0);
|
||||
if (sizeNeeded <= 0)
|
||||
throw std::runtime_error(std::format("MultiByteToWideChar() failed: {}", sizeNeeded));
|
||||
|
||||
std::wstring result(sizeNeeded, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, string.data(), static_cast<int>(string.size()), result.data(), sizeNeeded);
|
||||
return result;
|
||||
}
|
||||
} // namespace PLATFORM_NAMESPACE_WINDOWS
|
||||
|
||||
#endif
|
||||
16
src/ModMan/Web/Platform/Windows/PlatformUtilsWindows.h
Normal file
16
src/ModMan/Web/Platform/Windows/PlatformUtilsWindows.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "Web/Platform/Platform.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace PLATFORM_NAMESPACE_WINDOWS
|
||||
{
|
||||
std::string WideStringToString(const std::wstring& wideString);
|
||||
std::wstring StringToWideString(const std::string& string);
|
||||
} // namespace PLATFORM_NAMESPACE_WINDOWS
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user