Files
msime-windows/server/src/webview2/windows_webview2.cpp
T

823 lines
28 KiB
C++

#include "windows_webview2.h"
#include "utils/common_utils.h"
#include <debugapi.h>
#include <filesystem>
#include <string>
#include <windows.h>
#include <dwmapi.h>
#include "global/globals.h"
#include "fmt/xchar.h"
int boundRightExtra = 1000;
int boundBottomExtra = 1000;
std::wstring bodyRes = L"";
std::wstring ReadHtmlFile(const std::wstring &filePath)
{
std::wifstream file(filePath);
if (!file)
{
// TODO: Log
return L"";
}
// Use Boost Locale to handle UTF-8
file.imbue(boost::locale::generator().generate("en_US.UTF-8"));
std::wstringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
inline std::wstring GetAppdataPath()
{
return string_to_wstring(CommonUtils::get_local_appdata_path()) + //
LR"(\)" + //
GlobalIme::AppName + //
LR"(\)" + //
LR"(webview2)";
}
void UpdateHtmlContentWithJavaScript(ComPtr<ICoreWebView2> webview, const std::wstring &newContent)
{
if (webview != nullptr)
{
std::wstring script;
script.reserve(256);
script.append(L"document.getElementById('realContainer').innerHTML = `");
script.append(newContent);
script.append(L"`;\n");
script.append(L"window.ClearState();\n");
script.append(L"var el = document.getElementById('realContainerParent');\n");
script.append(L"if (el) {\n");
script.append(L" el.style.marginTop = \"");
script.append(std::to_wstring(Global::MarginTop));
script.append(L"px\";\n");
script.append(L"}\n");
// OutputDebugString(fmt::format(L"UpdateHtmlContentWithJavaScript: {}\n", script).c_str());
webview->ExecuteScript(script.c_str(), nullptr);
}
}
int PrepareHtmlForWnds()
{
std::wstring assetPath = L"C:\\Users\\SonnyCalcr\\AppData\\Local\\MetasequoiaImeTsf";
//
// 候选窗口
//
std::wstring htmlCandWnd = L"/html/webview2/candwnd/vertical_candidate_window_dark.html";
std::wstring bodyHtmlCandWnd = L"/html/webview2/candwnd/body/vertical_candidate_window_dark.html";
std::wstring measureHtmlCandWnd = L"/html/webview2/candwnd/body/vertical_candidate_window_dark_measure.html";
bool isHorizontal = false;
bool isNormal = true;
if (isHorizontal)
{
htmlCandWnd = L"/html/candwnd/horizontal_candidate_window_dark.html";
bodyHtmlCandWnd = L"/html/candwnd/body/horizontal_candidate_window_dark.html";
if (isNormal)
{
htmlCandWnd = L"/html/candwnd/horizontal_candidate_window_dark_normal.html";
bodyHtmlCandWnd = L"/html/candwnd/body/horizontal_candidate_window_dark_normal.html";
}
}
std::wstring entireHtmlPathCandWnd = std::filesystem::current_path().wstring() + htmlCandWnd;
entireHtmlPathCandWnd = assetPath + htmlCandWnd;
::HTMLStringCandWnd = ReadHtmlFile(entireHtmlPathCandWnd);
std::wstring bodyHtmlPathCandWnd = std::filesystem::current_path().wstring() + bodyHtmlCandWnd;
bodyHtmlPathCandWnd = assetPath + bodyHtmlCandWnd;
::BodyStringCandWnd = ReadHtmlFile(bodyHtmlPathCandWnd);
std::wstring measureHtmlPathCandWnd = std::filesystem::current_path().wstring() + measureHtmlCandWnd;
measureHtmlPathCandWnd = assetPath + measureHtmlCandWnd;
::MeasureStringCandWnd = ReadHtmlFile(measureHtmlPathCandWnd);
//
// 托盘语言区菜单窗口
//
std::wstring htmlMenuWnd = L"/html/webview2/menu/default.html";
std::wstring entireHtmlPathMenuWnd = std::filesystem::current_path().wstring() + htmlMenuWnd;
entireHtmlPathMenuWnd = assetPath + htmlMenuWnd;
::HTMLStringMenuWnd = ReadHtmlFile(entireHtmlPathMenuWnd);
//
// settings 窗口
//
std::wstring htmlSettingsWnd = L"/html/webview2/settings/default.html";
std::wstring entireHtmlPathSettingsWnd = std::filesystem::current_path().wstring() + htmlSettingsWnd;
entireHtmlPathSettingsWnd = assetPath + htmlSettingsWnd;
::HTMLStringSettingsWnd = ReadHtmlFile(entireHtmlPathSettingsWnd);
//
// floating toolbar 窗口
//
std::wstring htmlFtbWnd = L"/html/webview2/ftb/default.html";
std::wstring entireHtmlPathFtbWnd = std::filesystem::current_path().wstring() + htmlFtbWnd;
entireHtmlPathFtbWnd = assetPath + htmlFtbWnd;
::HTMLStringFtbWnd = ReadHtmlFile(entireHtmlPathFtbWnd);
return 0;
}
//
//
// 候选窗口 webview
//
//
void UpdateMeasureContentWithJavaScript(ComPtr<ICoreWebView2> webview, const std::wstring &newContent)
{
if (webview != nullptr)
{
std::wstring script;
script.reserve(256);
script.append(L"document.getElementById('measureContainer').innerHTML = `");
script.append(newContent);
script.append(L"`;\n");
webview->ExecuteScript(script.c_str(), nullptr);
}
}
void ResetContainerHoverCandWnd(ComPtr<ICoreWebView2> webview)
{
if (webview != nullptr)
{
std::wstring script = LR"(
const realContainer = document.getElementById('realContainer');
realContainer.classList.remove('hover-active');
)";
webview->ExecuteScript(script.c_str(), nullptr);
}
}
void DisableMouseForAWhileWhenShownCandWnd(ComPtr<ICoreWebView2> webview)
{
if (webview != nullptr)
{
std::wstring script = LR"(
if (window.mouseBlockTimeout) {
clearTimeout(window.mouseBlockTimeout);
}
document.documentElement.style.pointerEvents = "none";
window.mouseBlockTimeout = setTimeout(() => {
document.documentElement.style.pointerEvents = "auto";
window.mouseBlockTimeout = null;
}, 500);
)";
webview->ExecuteScript(script.c_str(), nullptr);
}
}
void InflateCandWnd(std::wstring &str)
{
std::wstringstream wss(str);
std::wstring token;
std::vector<std::wstring> words;
while (std::getline(wss, token, L','))
{
words.push_back(token);
}
int size = words.size();
while (words.size() < 9)
{
words.push_back(L"");
}
std::wstring result = fmt::format( //
BodyStringCandWnd, //
words[0], //
words[1], //
words[2], //
words[3], //
words[4], //
words[5], //
words[6], //
words[7], //
words[8] //
); //
if (size < 9)
{
size_t pos = result.find(fmt::format(L"<!--{}Anchor-->", size));
// result = result.substr(0, pos) + L"</div>";
result = result.substr(0, pos);
}
UpdateHtmlContentWithJavaScript(webviewCandWnd, result);
}
void InflateMeasureDivCandWnd(std::wstring &str)
{
std::wstringstream wss(str);
std::wstring token;
std::vector<std::wstring> words;
while (std::getline(wss, token, L','))
{
words.push_back(token);
}
int size = words.size();
while (words.size() < 9)
{
words.push_back(L"");
}
std::wstring result = fmt::format( //
::MeasureStringCandWnd, //
words[0], //
words[1], //
words[2], //
words[3], //
words[4], //
words[5], //
words[6], //
words[7], //
words[8] //
); //
if (size < 9)
{
size_t pos = result.find(fmt::format(L"<!--{}Anchor-->", size));
// result = result.substr(0, pos) + L"</div>";
result = result.substr(0, pos);
}
UpdateMeasureContentWithJavaScript(webviewCandWnd, result);
}
/**
* @brief Handle candidate window webview2 controller creation
*
* @param hwnd
* @param result
* @param controller
* @return HRESULT
*/
HRESULT OnControllerCreatedCandWnd( //
HWND hwnd, //
HRESULT result, //
ICoreWebView2Controller *controller //
)
{
if (!controller || FAILED(result))
{
ShowErrorMessage(hwnd, L"Failed to create WebView2 controller.");
return E_FAIL;
}
webviewControllerCandWnd = controller;
webviewControllerCandWnd->get_CoreWebView2(webviewCandWnd.GetAddressOf());
if (!webviewCandWnd)
{
ShowErrorMessage(hwnd, L"Failed to get WebView2 instance.");
return E_FAIL;
}
// Configure WebView settings
ComPtr<ICoreWebView2Settings> settings;
if (SUCCEEDED(webviewCandWnd->get_Settings(&settings)))
{
settings->put_IsScriptEnabled(TRUE);
settings->put_AreDefaultScriptDialogsEnabled(TRUE);
settings->put_IsWebMessageEnabled(TRUE);
settings->put_AreHostObjectsAllowed(TRUE);
}
// Configure virtual host path
if (SUCCEEDED(webviewCandWnd->QueryInterface(IID_PPV_ARGS(&webview3CandWnd))))
{
const std::wstring assetPath = fmt::format( //
L"{}\\{}\\html\\webview2\\candwnd", //
string_to_wstring(CommonUtils::get_local_appdata_path()), //
GlobalIme::AppName //
);
// Assets mapping
webview3CandWnd->SetVirtualHostNameToFolderMapping( //
L"candwnd", //
assetPath.c_str(), //
COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY_CORS //
); //
}
// Set transparent background
if (SUCCEEDED(controller->QueryInterface(IID_PPV_ARGS(&webviewController2CandWnd))))
{
COREWEBVIEW2_COLOR backgroundColor = {0, 0, 0, 0};
webviewController2CandWnd->put_DefaultBackgroundColor(backgroundColor);
}
// Adjust to window size
RECT bounds;
GetClientRect(hwnd, &bounds);
bounds.right += boundRightExtra;
bounds.bottom += boundBottomExtra;
webviewControllerCandWnd->put_Bounds(bounds);
// Navigate to HTML
HRESULT hr = webviewCandWnd->NavigateToString(HTMLStringCandWnd.c_str());
if (FAILED(hr))
{
ShowErrorMessage(hwnd, L"Failed to navigate to string.");
}
/* Debug console */
// webview->OpenDevToolsWindow();
return S_OK;
}
/**
* @brief Handle candidate window webview2 environment creation
*
* @param hwnd
* @param result
* @param env
* @return HRESULT
*/
HRESULT OnEnvironmentCreated(HWND hwnd, HRESULT result, ICoreWebView2Environment *env)
{
if (FAILED(result) || !env)
{
ShowErrorMessage(hwnd, L"Failed to create WebView2 environment.");
return result;
}
// Create WebView2 controller
return env->CreateCoreWebView2Controller( //
hwnd, //
Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>( //
[hwnd](HRESULT result, //
ICoreWebView2Controller *controller) -> HRESULT { //
return OnControllerCreatedCandWnd(hwnd, result, controller); //
}) //
.Get() //
); //
}
/**
* @brief 初始化候选窗口的 webview
*
* @param hwnd
*/
void InitWebviewCandWnd(HWND hwnd)
{
std::wstring appDataPath = GetAppdataPath();
CreateCoreWebView2EnvironmentWithOptions( //
nullptr, //
appDataPath.c_str(), //
nullptr, //
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>( //
[hwnd](HRESULT result, ICoreWebView2Environment *env) -> HRESULT { //
return OnEnvironmentCreated(hwnd, result, env); //
}) //
.Get() //
); //
}
//
//
// 菜单窗口 webview
//
//
/**
* @brief Handle menu window webview2 controller creation
*
* @param hwnd
* @param result
* @param controller
* @return HRESULT
*/
HRESULT OnControllerCreatedMenuWnd( //
HWND hwnd, //
HRESULT result, //
ICoreWebView2Controller *controller //
)
{
if (!controller || FAILED(result))
{
OutputDebugString(fmt::format(L"Failed to create menu window webview2 controller.\n").c_str());
return E_FAIL;
}
/* 给 controller 和 webview 赋值 */
webviewControllerMenuWnd = controller;
webviewControllerMenuWnd->get_CoreWebView2(webviewMenuWnd.GetAddressOf());
if (!webviewMenuWnd)
{
OutputDebugString(fmt::format(L"Failed to get webview2 instance.\n").c_str());
return E_FAIL;
}
// Configure webviewMenuWindow settings
ComPtr<ICoreWebView2Settings> settings;
if (SUCCEEDED(webviewMenuWnd->get_Settings(&settings)))
{
settings->put_IsScriptEnabled(TRUE);
settings->put_AreDefaultScriptDialogsEnabled(TRUE);
settings->put_IsWebMessageEnabled(TRUE);
settings->put_AreHostObjectsAllowed(TRUE);
}
// Configure virtual host path
if (SUCCEEDED(webviewMenuWnd->QueryInterface(IID_PPV_ARGS(&webview3MenuWnd))))
{
// Assets mapping
webview3MenuWnd->SetVirtualHostNameToFolderMapping( //
L"appassets", //
::LocalAssetsPath.c_str(), //
COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY_CORS //
); //
}
// Set transparent background
if (SUCCEEDED(controller->QueryInterface(IID_PPV_ARGS(&webviewController2MenuWnd))))
{
COREWEBVIEW2_COLOR backgroundColor = {0, 0, 0, 0};
webviewController2MenuWnd->put_DefaultBackgroundColor(backgroundColor);
}
// Adjust to window size
RECT bounds;
GetClientRect(hwnd, &bounds);
bounds.right += boundRightExtra;
bounds.bottom += boundBottomExtra;
webviewControllerMenuWnd->put_Bounds(bounds);
// Navigate to HTML
HRESULT hr = webviewMenuWnd->NavigateToString(::HTMLStringMenuWnd.c_str());
if (FAILED(hr))
{
OutputDebugString(fmt::format(L"Failed to navigate to string.\n").c_str());
}
/* Debug console */
// webviewMenuWindow->OpenDevToolsWindow();
return S_OK;
}
/**
* @brief Handle menu window webview2 environment creation
*
* @param hwnd
* @param result
* @param env
* @return HRESULT
*/
HRESULT OnMenuWindowEnvironmentCreated(HWND hwnd, HRESULT result, ICoreWebView2Environment *env)
{
if (FAILED(result) || !env)
{
OutputDebugString(fmt::format(L"Failed to create menu window webview2 environment.\n").c_str());
return result;
}
// Create WebView2 controller
return env->CreateCoreWebView2Controller( //
hwnd, //
Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>( //
[hwnd](HRESULT result, ICoreWebView2Controller *controller) -> HRESULT { //
return OnControllerCreatedMenuWnd(hwnd, result, controller); //
}) //
.Get() //
); //
}
/**
* @brief 初始化菜单窗口的 webview
*
* @param hwnd
*/
void InitWebviewMenuWnd(HWND hwnd)
{
std::wstring appDataPath = GetAppdataPath();
CreateCoreWebView2EnvironmentWithOptions( //
nullptr, //
appDataPath.c_str(), //
nullptr, //
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>( //
[hwnd](HRESULT result, ICoreWebView2Environment *env) -> HRESULT { //
return OnMenuWindowEnvironmentCreated(hwnd, result, env); //
}) //
.Get() //
); //
}
//
//
// settings 窗口 webview
//
//
/**
* @brief Handle settings window webview2 controller creation
*
* @param hwnd
* @param result
* @param controller
* @return HRESULT
*/
HRESULT OnControllerCreatedSettingsWnd( //
HWND hwnd, //
HRESULT result, //
ICoreWebView2Controller *controller //
)
{
if (!controller || FAILED(result))
{
OutputDebugString(fmt::format(L"Failed to create settings window webview2 controller.\n").c_str());
return E_FAIL;
}
/* 给 controller 和 webview 赋值 */
webviewControllerSettingsWnd = controller;
webviewControllerSettingsWnd->get_CoreWebView2(webviewSettingsWnd.GetAddressOf());
if (!webviewSettingsWnd)
{
OutputDebugString(fmt::format(L"Failed to get webview2 instance.\n").c_str());
return E_FAIL;
}
// Configure webviewSettingsWindow settings
ComPtr<ICoreWebView2Settings> settings;
if (SUCCEEDED(webviewSettingsWnd->get_Settings(&settings)))
{
settings->put_IsScriptEnabled(TRUE);
settings->put_AreDefaultScriptDialogsEnabled(TRUE);
settings->put_IsWebMessageEnabled(TRUE);
settings->put_AreHostObjectsAllowed(TRUE);
}
// Configure virtual host path
if (SUCCEEDED(webviewSettingsWnd->QueryInterface(IID_PPV_ARGS(&webview3SettingsWnd))))
{
const std::wstring assetPath = fmt::format( //
L"{}\\{}\\html\\webview2\\settings", //
string_to_wstring(CommonUtils::get_local_appdata_path()), //
GlobalIme::AppName //
);
// Assets mapping
webview3SettingsWnd->SetVirtualHostNameToFolderMapping( //
L"imesettings", //
assetPath.c_str(), //
COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY_CORS //
); //
}
// Set transparent background
if (SUCCEEDED(controller->QueryInterface(IID_PPV_ARGS(&webviewController2SettingsWnd))))
{
COREWEBVIEW2_COLOR backgroundColor = {0, 0, 0, 0};
webviewController2SettingsWnd->put_DefaultBackgroundColor(backgroundColor);
}
// Adjust to window size
RECT bounds;
GetClientRect(hwnd, &bounds);
webviewControllerSettingsWnd->put_Bounds(bounds);
// Navigate to HTML
// HRESULT hr = webviewSettingsWnd->NavigateToString(::HTMLStringSettingsWnd.c_str());
std::wstring url = L"https://imesettings/default.html";
HRESULT hr = webviewSettingsWnd->Navigate(url.c_str());
if (FAILED(hr))
{
OutputDebugString(fmt::format(L"Failed to navigate to string.\n").c_str());
}
EventRegistrationToken navCompletedToken;
webviewSettingsWnd->add_NavigationCompleted(
Callback<ICoreWebView2NavigationCompletedEventHandler>( //
[hwnd](ICoreWebView2 *sender, ICoreWebView2NavigationCompletedEventArgs *args) -> HRESULT {
BOOL success;
args->get_IsSuccess(&success);
if (success)
{
OutputDebugString(L"✅ WebView2 页面加载完成\n");
// 在这里执行你想等页面准备好的逻辑,比如执行 JS 初始化
BOOL cloak = FALSE;
DwmSetWindowAttribute(hwnd, DWMWA_CLOAK, &cloak, sizeof(cloak));
}
return S_OK;
})
.Get(),
&navCompletedToken);
/* Debug console */
// webviewSettingsWindow->OpenDevToolsWindow();
return S_OK;
}
/**
* @brief Handle settings window webview2 environment creation
*
* @param hwnd
* @param result
* @param env
* @return HRESULT
*/
HRESULT OnSettingsWindowEnvironmentCreated(HWND hwnd, HRESULT result, ICoreWebView2Environment *env)
{
if (FAILED(result) || !env)
{
OutputDebugString(fmt::format(L"Failed to create settings window webview2 environment.\n").c_str());
return result;
}
// Create WebView2 controller
return env->CreateCoreWebView2Controller( //
hwnd, //
Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>( //
[hwnd](HRESULT result, ICoreWebView2Controller *controller) -> HRESULT { //
return OnControllerCreatedSettingsWnd(hwnd, result, controller); //
}) //
.Get() //
); //
}
/**
* @brief 初始化 settings 窗口的 webview
*
* @param hwnd
*/
void InitWebviewSettingsWnd(HWND hwnd)
{
std::wstring appDataPath = GetAppdataPath();
CreateCoreWebView2EnvironmentWithOptions( //
nullptr, //
appDataPath.c_str(), //
nullptr, //
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>( //
[hwnd](HRESULT result, ICoreWebView2Environment *env) -> HRESULT { //
return OnSettingsWindowEnvironmentCreated(hwnd, result, env); //
}) //
.Get() //
); //
}
//
//
// toolbar 窗口 webview
//
//
/**
* @brief Handle floating toolbar window webview2 controller creation
*
* @param hwnd
* @param result
* @param controller
* @return HRESULT
*/
HRESULT OnControllerCreatedFtbWnd( //
HWND hwnd, //
HRESULT result, //
ICoreWebView2Controller *controller //
)
{
if (!controller || FAILED(result))
{
OutputDebugString(fmt::format(L"Failed to create floating toolbar window webview2 controller.\n").c_str());
return E_FAIL;
}
/* 给 controller 和 webview 赋值 */
webviewControllerFtbWnd = controller;
webviewControllerFtbWnd->get_CoreWebView2(webviewFtbWnd.GetAddressOf());
if (!webviewFtbWnd)
{
OutputDebugString(fmt::format(L"Failed to get webview2 instance.\n").c_str());
return E_FAIL;
}
// Configure webviewFtbWindow settings
ComPtr<ICoreWebView2Settings> settings;
if (SUCCEEDED(webviewFtbWnd->get_Settings(&settings)))
{
settings->put_IsScriptEnabled(TRUE);
settings->put_AreDefaultScriptDialogsEnabled(TRUE);
settings->put_IsWebMessageEnabled(TRUE);
settings->put_AreHostObjectsAllowed(TRUE);
}
// Configure virtual host path
if (SUCCEEDED(webviewFtbWnd->QueryInterface(IID_PPV_ARGS(&webview3FtbWnd))))
{
// Assets mapping
webview3FtbWnd->SetVirtualHostNameToFolderMapping( //
L"appassets", //
::LocalAssetsPath.c_str(), //
COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY_CORS //
); //
}
// Set transparent background
if (SUCCEEDED(controller->QueryInterface(IID_PPV_ARGS(&webviewController2FtbWnd))))
{
COREWEBVIEW2_COLOR backgroundColor = {0, 0, 0, 0};
webviewController2FtbWnd->put_DefaultBackgroundColor(backgroundColor);
}
// Adjust to window size
RECT bounds;
GetClientRect(hwnd, &bounds);
bounds.right += boundRightExtra;
bounds.bottom += boundBottomExtra;
webviewControllerFtbWnd->put_Bounds(bounds);
// Navigate to HTML
HRESULT hr = webviewFtbWnd->NavigateToString(::HTMLStringFtbWnd.c_str());
if (FAILED(hr))
{
OutputDebugString(fmt::format(L"Failed to navigate to string.\n").c_str());
}
/* 使 floating toolbar 窗口可拖动 */
webviewFtbWnd->add_WebMessageReceived( //
Callback<ICoreWebView2WebMessageReceivedEventHandler>( //
[hwnd](ICoreWebView2 *sender, //
ICoreWebView2WebMessageReceivedEventArgs *args) -> HRESULT {
wil::unique_cotaskmem_string message;
args->get_WebMessageAsJson(&message);
std::wstring json = message.get();
if (json.find(L"dragStart") != std::string::npos)
{
ReleaseCapture();
SendMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0);
}
return S_OK;
})
.Get(),
nullptr);
/* Debug console */
// webviewFtbWindow->OpenDevToolsWindow();
return S_OK;
}
/**
* @brief Handle floating toolbar window webview2 environment creation
*
* @param hwnd
* @param result
* @param env
* @return HRESULT
*/
HRESULT OnFtbWindowEnvironmentCreated(HWND hwnd, HRESULT result, ICoreWebView2Environment *env)
{
if (FAILED(result) || !env)
{
OutputDebugString(fmt::format(L"Failed to create floating toolbar window webview2 environment.\n").c_str());
return result;
}
// Create WebView2 controller
return env->CreateCoreWebView2Controller( //
hwnd, //
Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>( //
[hwnd](HRESULT result, ICoreWebView2Controller *controller) -> HRESULT { //
return OnControllerCreatedFtbWnd(hwnd, result, controller); //
}) //
.Get() //
); //
}
/**
* @brief 初始化 floating toolbar 窗口的 webview
*
* @param hwnd
*/
void InitWebviewFtbWnd(HWND hwnd)
{
std::wstring appDataPath = GetAppdataPath();
CreateCoreWebView2EnvironmentWithOptions( //
nullptr, //
appDataPath.c_str(), //
nullptr, //
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>( //
[hwnd](HRESULT result, ICoreWebView2Environment *env) -> HRESULT { //
return OnFtbWindowEnvironmentCreated(hwnd, result, env); //
}) //
.Get() //
); //
}