#include "windows_webview2.h" #include "config/ime_config.h" #include "defines/globals.h" #include "utils/common_utils.h" #include "utils/ime_utils.h" #include #include #include #include #include #include #include #include "defines/defines.h" #include "global/globals.h" #include "fmt/xchar.h" #include "ipc/ipc.h" #include "settings/settings_launcher.h" #include #pragma comment(lib, "dcomp.lib") namespace json = boost::json; int FineTuneWindow(HWND hwnd); void ApplyConfiguredFloatingToolbarVisibility(); void ApplyConfiguredInputScheme(); constexpr int candidateBoundRightExtra = 1000; constexpr int candidateBoundBottomExtra = 1000; std::wstring bodyRes = L""; namespace { ComPtr smallWindowWebviewEnvironment; void SetWebviewMemoryUsageTarget(ComPtr webview, COREWEBVIEW2_MEMORY_USAGE_TARGET_LEVEL level) { if (!webview) return; ComPtr webview23; if (SUCCEEDED(webview.As(&webview23))) { webview23->put_MemoryUsageTargetLevel(level); } } } void UpdateSmallWindowWebviewVisibility(HWND hwnd, bool visible) { ComPtr controller; ComPtr webview; bool lowerMemoryWhenHidden = false; if (hwnd == ::global_hwnd) { controller = webviewControllerCandWnd; webview = webviewCandWnd; } else if (hwnd == ::global_hwnd_menu) { controller = webviewControllerMenuWnd; webview = webviewMenuWnd; lowerMemoryWhenHidden = true; } else if (hwnd == ::global_hwnd_ftb) { controller = webviewControllerFtbWnd; webview = webviewFtbWnd; lowerMemoryWhenHidden = true; } else { return; } if (controller) { controller->put_IsVisible(visible ? TRUE : FALSE); } if (lowerMemoryWhenHidden) { SetWebviewMemoryUsageTarget( webview, visible ? COREWEBVIEW2_MEMORY_USAGE_TARGET_LEVEL_NORMAL : COREWEBVIEW2_MEMORY_USAGE_TARGET_LEVEL_LOW); } } 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 webview, const std::wstring &newContent) { if (webview != nullptr) { std::wstring script; script.reserve(newContent.length() + 512); 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"); script.append(L"if (window.SetCandidateSelection) { window.SetCandidateSelection("); script.append(std::to_wstring(Global::candidate_ui.selected_index_in_page)); script.append(L"); }\n"); script.append(L"if (window.SetPreeditCaret) { window.SetPreeditCaret(); }\n"); // OutputDebugString(fmt::format(L"[msime]: UpdateHtmlContentWithJavaScript: {}", script).c_str()); webview->ExecuteScript(script.c_str(), nullptr); } } int PrepareHtmlForWnds() { // e.g. C:\\Users\\SonnyCalcr\\AppData\\Local\\MetasequoiaImeTsf std::wstring assetPath = fmt::format( // L"{}\\{}", // string_to_wstring(CommonUtils::get_local_appdata_path()), GlobalIme::AppName); // // 候选窗口 // const bool isHorizontal = GetConfiguredCandidateWindowLayout() == "horizontal"; std::wstring htmlCandWnd; std::wstring bodyHtmlCandWnd; std::wstring measureHtmlCandWnd; if (isHorizontal) { htmlCandWnd = L"/html/webview2/candwnd/horizontal_candidate_window_dark.html"; bodyHtmlCandWnd = L"/html/webview2/candwnd/body/horizontal_candidate_window_dark.html"; measureHtmlCandWnd = L"/html/webview2/candwnd/body/horizontal_candidate_window_dark_measure.html"; } else { htmlCandWnd = L"/html/webview2/candwnd/vertical_candidate_window_dark.html"; bodyHtmlCandWnd = L"/html/webview2/candwnd/body/vertical_candidate_window_dark.html"; measureHtmlCandWnd = L"/html/webview2/candwnd/body/vertical_candidate_window_dark_measure.html"; } std::wstring entireHtmlPathCandWnd = assetPath + htmlCandWnd; ::HTMLStringCandWnd = ReadHtmlFile(entireHtmlPathCandWnd); std::wstring bodyHtmlPathCandWnd = assetPath + bodyHtmlCandWnd; ::BodyStringCandWnd = ReadHtmlFile(bodyHtmlPathCandWnd); std::wstring measureHtmlPathCandWnd = assetPath + measureHtmlCandWnd; ::MeasureStringCandWnd = ReadHtmlFile(measureHtmlPathCandWnd); // // 托盘语言区菜单窗口 // std::wstring htmlMenuWnd = L"/html/webview2/menu/default.html"; std::wstring entireHtmlPathMenuWnd = assetPath + htmlMenuWnd; ::HTMLStringMenuWnd = ReadHtmlFile(entireHtmlPathMenuWnd); // // settings 窗口 // 这里暂时没有用到,因为 settings 窗口使用的是映射 url 导航 // /* std::wstring htmlSettingsWnd = L"/html/webview2/settings/default.html"; std::wstring entireHtmlPathSettingsWnd = assetPath + htmlSettingsWnd; ::HTMLStringSettingsWnd = ReadHtmlFile(entireHtmlPathSettingsWnd); */ // // floating toolbar 窗口 // std::wstring htmlFtbWnd = L"/html/webview2/ftb/default.html"; std::wstring entireHtmlPathFtbWnd = assetPath + htmlFtbWnd; ::HTMLStringFtbWnd = ReadHtmlFile(entireHtmlPathFtbWnd); return 0; } bool ApplyConfiguredCandidateWindowLayout() { // PrepareHtmlForWnds also refreshes the other small-window templates. They are // cheap local reads and keeping this in one place prevents the paths drifting. PrepareHtmlForWnds(); if (!webviewCandWnd || HTMLStringCandWnd.empty()) { return false; } return SUCCEEDED(webviewCandWnd->NavigateToString(HTMLStringCandWnd.c_str())); } // // // 候选窗口 webview // // void UpdateMeasureContentWithJavaScript(ComPtr 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 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 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 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"", size)); // result = result.substr(0, pos) + L""; result = result.substr(0, pos); } UpdateHtmlContentWithJavaScript(webviewCandWnd, result); } void InflateMeasureDivCandWnd(std::wstring &str) { str.erase(std::remove(str.begin(), str.end(), L'\uE000'), str.end()); std::wstringstream wss(str); std::wstring token; std::vector 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"", size)); // result = result.substr(0, pos) + L""; 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; } UpdateSmallWindowWebviewVisibility(hwnd, IsWindowVisible(hwnd) != FALSE); // Configure WebView settings ComPtr settings; if (SUCCEEDED(webviewCandWnd->get_Settings(&settings))) { settings->put_IsScriptEnabled(TRUE); settings->put_AreDefaultScriptDialogsEnabled(FALSE); settings->put_IsWebMessageEnabled(TRUE); settings->put_AreHostObjectsAllowed(FALSE); // Since we only use WebMessages settings->put_IsZoomControlEnabled(FALSE); settings->put_IsStatusBarEnabled(FALSE); settings->put_AreDefaultContextMenusEnabled(FALSE); settings->put_AreDevToolsEnabled(FALSE); // Try to disable browser accelerators (Ctrl+R, F5, etc.) ComPtr settings3; if (SUCCEEDED(settings->QueryInterface(IID_PPV_ARGS(&settings3)))) { settings3->put_AreBrowserAcceleratorKeysEnabled(FALSE); // settings3->put_IsPinchZoomEnabled(FALSE); // Unsupported in this header version } // Try to disable autofill and password saving ComPtr settings5; if (SUCCEEDED(settings->QueryInterface(IID_PPV_ARGS(&settings5)))) { settings5->put_IsGeneralAutofillEnabled(FALSE); settings5->put_IsPasswordAutosaveEnabled(FALSE); } // Try to disable built-in error pages for a cleaner UI ComPtr settings6; if (SUCCEEDED(settings->QueryInterface(IID_PPV_ARGS(&settings6)))) { settings6->put_IsBuiltInErrorPageEnabled(FALSE); } } webviewControllerCandWnd->put_ZoomFactor(1.0); // 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); // The candidate page uses the extra viewport for stable off-screen // measurement before its host window is resized to the measured content. bounds.right += candidateBoundRightExtra; bounds.bottom += candidateBoundBottomExtra; 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(); webviewCandWnd->add_WebMessageReceived( Microsoft::WRL::Callback( [hwnd](ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) -> HRESULT { wil::unique_cotaskmem_string message; HRESULT hr = args->TryGetWebMessageAsString(&message); if (SUCCEEDED(hr) && message.get()) { std::wstring msg(message.get()); // 解析 msg,执行相应操作 try { json::value val = json::parse(wstring_to_string(msg)); std::string type = json::value_to(val.at("type")); if (type == "delete") { int idx = json::value_to(val.at("data")); if (idx >= 0 && idx < 9) { PostMessage(::global_hwnd, WM_DELETE_CANDIDATE, idx, 0); } } else if (type == "pin") { int idx = json::value_to(val.at("data")); if (idx >= 0 && idx < 9) { PostMessage(::global_hwnd, WM_PIN_TO_TOP_CANDIDATE, idx, 0); } } else if (type == "candidate") { int idx = json::value_to(val.at("data")); if (idx >= 0 && idx < 9) { PostMessage(::global_hwnd, WM_COMMIT_CANDIDATE, idx, 0); } } } catch (const std::exception &e) { #ifdef FANY_DEBUG OutputDebugString( fmt::format(L"[msime]: Exception happens when parsing cand wnd webview2 message").c_str()); #endif return S_OK; } } return S_OK; }) .Get(), nullptr); webviewCandWnd->add_NavigationCompleted( Microsoft::WRL::Callback( [hwnd](ICoreWebView2 *, ICoreWebView2NavigationCompletedEventArgs *args) -> HRESULT { BOOL success = FALSE; args->get_IsSuccess(&success); if (success && ::is_global_wnd_cand_shown) { std::wstring str = GetPreeditWithCaretMarker() + L"," + Global::CandidateString; InflateMeasureDivCandWnd(str); FineTuneWindow(hwnd); } return S_OK; }) .Get(), nullptr); 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, // Microsoft::WRL::Callback( // [hwnd](HRESULT result, // ICoreWebView2Controller *controller) -> HRESULT { // return OnControllerCreatedCandWnd(hwnd, result, controller); // }) // .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)) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Failed to create menu window webview2 controller.").c_str()); #endif return E_FAIL; } /* 给 controller 和 webview 赋值 */ webviewControllerMenuWnd = controller; webviewControllerMenuWnd->get_CoreWebView2(webviewMenuWnd.GetAddressOf()); if (!webviewMenuWnd) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Failed to get webview2 instance.").c_str()); #endif return E_FAIL; } UpdateSmallWindowWebviewVisibility(hwnd, IsWindowVisible(hwnd) != FALSE); // Configure webviewMenuWindow settings ComPtr settings; if (SUCCEEDED(webviewMenuWnd->get_Settings(&settings))) { settings->put_IsScriptEnabled(TRUE); settings->put_AreDefaultScriptDialogsEnabled(FALSE); settings->put_IsWebMessageEnabled(TRUE); settings->put_AreHostObjectsAllowed(FALSE); settings->put_AreDefaultContextMenusEnabled(FALSE); settings->put_AreDevToolsEnabled(FALSE); settings->put_IsZoomControlEnabled(FALSE); settings->put_IsStatusBarEnabled(FALSE); ComPtr settings3; if (SUCCEEDED(settings.As(&settings3))) { settings3->put_AreBrowserAcceleratorKeysEnabled(FALSE); } ComPtr settings5; if (SUCCEEDED(settings.As(&settings5))) { settings5->put_IsGeneralAutofillEnabled(FALSE); settings5->put_IsPasswordAutosaveEnabled(FALSE); } } webviewControllerMenuWnd->put_ZoomFactor(1.0); // 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); webviewControllerMenuWnd->put_Bounds(bounds); // Navigate to HTML HRESULT hr = webviewMenuWnd->NavigateToString(::HTMLStringMenuWnd.c_str()); if (FAILED(hr)) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Failed to navigate to string.").c_str()); #endif } /* Debug console */ // webviewMenuWindow->OpenDevToolsWindow(); webviewMenuWnd->add_WebMessageReceived( Microsoft::WRL::Callback( [hwnd](ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) -> HRESULT { wil::unique_cotaskmem_string message; HRESULT hr = args->TryGetWebMessageAsString(&message); if (SUCCEEDED(hr) && message.get()) { std::wstring msg(message.get()); // 解析 msg,执行相应操作 json::value val = json::parse(wstring_to_string(msg)); std::string type = json::value_to(val.at("type")); if (type == "floatingToggle") { bool needShown = json::value_to(val.at("data")); if (SetConfiguredFloatingToolbarEnabled(needShown)) { ApplyConfiguredFloatingToolbarVisibility(); PostSettingsConfig(); } } else if (type == "settings") { OpenSettingsApplication(); ShowWindow(::global_hwnd_menu, SW_HIDE); } } return S_OK; }) .Get(), nullptr); 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) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Failed to create menu window webview2 environment.").c_str()); #endif return result; } // Create WebView2 controller return env->CreateCoreWebView2Controller( // hwnd, // Microsoft::WRL::Callback( // [hwnd](HRESULT result, ICoreWebView2Controller *controller) -> HRESULT { // return OnControllerCreatedMenuWnd(hwnd, result, controller); // }) // .Get() // ); // } // // // settings 窗口 webview // // HRESULT EnsureCompositionVisualTreeSettingsWnd(HWND hwnd) { if (dcompDeviceSettingsWnd && dcompTargetSettingsWnd && dcompRootVisualSettingsWnd) { return S_OK; } HRESULT hr = DCompositionCreateDevice(nullptr, __uuidof(IDCompositionDevice), reinterpret_cast(dcompDeviceSettingsWnd.GetAddressOf())); if (FAILED(hr)) { return hr; } hr = dcompDeviceSettingsWnd->CreateTargetForHwnd(hwnd, TRUE, &dcompTargetSettingsWnd); if (FAILED(hr)) { return hr; } hr = dcompDeviceSettingsWnd->CreateVisual(&dcompRootVisualSettingsWnd); if (FAILED(hr)) { return hr; } hr = dcompTargetSettingsWnd->SetRoot(dcompRootVisualSettingsWnd.Get()); if (FAILED(hr)) { return hr; } return dcompDeviceSettingsWnd->Commit(); } /** * @brief Handle settings window webview2 controller creation * * @param hwnd * @param result * @param controller * @return HRESULT */ HRESULT OnControllerCreatedSettingsWnd( // HWND hwnd, // HRESULT result, // ICoreWebView2CompositionController *controller // ) { if (!controller || FAILED(result)) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Failed to create settings window webview2 controller.").c_str()); #endif return E_FAIL; } /* 给 controller 和 webview 赋值 */ webviewCompositionControllerSettingsWnd = controller; if (FAILED(webviewCompositionControllerSettingsWnd.As(&webviewControllerSettingsWnd))) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Failed to get the base WebView2 controller.").c_str()); #endif return E_NOINTERFACE; } webviewControllerSettingsWnd->get_CoreWebView2(webviewSettingsWnd.GetAddressOf()); if (!webviewSettingsWnd) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Failed to get webview2 instance.").c_str()); #endif return E_FAIL; } // Configure webviewSettingsWindow settings ComPtr settings; if (SUCCEEDED(webviewSettingsWnd->get_Settings(&settings))) { settings->put_IsScriptEnabled(TRUE); settings->put_AreDefaultScriptDialogsEnabled(TRUE); settings->put_IsWebMessageEnabled(TRUE); settings->put_AreHostObjectsAllowed(TRUE); settings->put_IsZoomControlEnabled(FALSE); } webviewControllerSettingsWnd->put_ZoomFactor(1.0); // Configure virtual host path if (SUCCEEDED(webviewSettingsWnd->QueryInterface(IID_PPV_ARGS(&webview3SettingsWnd)))) { const std::wstring assetPath = fmt::format( // L"{}\\{}\\html\\webview2\\settings\\ime-settings\\dist", // string_to_wstring(CommonUtils::get_local_appdata_path()), // GlobalIme::AppName // ); // Assets mapping webview3SettingsWnd->SetVirtualHostNameToFolderMapping( // L"imesettings", // assetPath.c_str(), // COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_ALLOW // ); // } // The settings page is fully opaque. Keeping the composition surface // transparent makes DWM briefly expose the host backdrop on input-driven // WebView repaints, which looks like the window/taskbar is flashing. if (SUCCEEDED(webviewControllerSettingsWnd.As(&webviewController2SettingsWnd))) { COREWEBVIEW2_COLOR backgroundColor = {255, 32, 32, 32}; webviewController2SettingsWnd->put_DefaultBackgroundColor(backgroundColor); } const HRESULT compositionResult = EnsureCompositionVisualTreeSettingsWnd(hwnd); if (FAILED(compositionResult)) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Failed to initialize DirectComposition.").c_str()); #endif return compositionResult; } const HRESULT rootVisualResult = webviewCompositionControllerSettingsWnd->put_RootVisualTarget(dcompRootVisualSettingsWnd.Get()); if (FAILED(rootVisualResult)) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Failed to attach the WebView root visual.").c_str()); #endif return rootVisualResult; } dcompDeviceSettingsWnd->Commit(); // 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/index.html"; HRESULT hr = webviewSettingsWnd->Navigate(url.c_str()); if (FAILED(hr)) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Failed to navigate to string.").c_str()); #endif } EventRegistrationToken navCompletedToken; webviewSettingsWnd->add_NavigationCompleted( Microsoft::WRL::Callback( // [hwnd](ICoreWebView2 *sender, ICoreWebView2NavigationCompletedEventArgs *args) -> HRESULT { BOOL success; args->get_IsSuccess(&success); if (success) { // 隐藏窗口 #ifdef FANY_DEBUG OutputDebugString( fmt::format(L"[msime]: Webview2 settings window loaded and already hidden window").c_str()); #endif BOOL cloak = FALSE; DwmSetWindowAttribute(hwnd, DWMWA_CLOAK, &cloak, sizeof(cloak)); } PostSettingsWindowState(hwnd); PostSettingsConfig(); return S_OK; }) .Get(), &navCompletedToken); /* 处理 js 发过来的消息 */ webviewSettingsWnd->add_WebMessageReceived( Microsoft::WRL::Callback( [hwnd](ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) -> HRESULT { wil::unique_cotaskmem_string message; HRESULT hr = args->TryGetWebMessageAsString(&message); if (SUCCEEDED(hr) && message.get()) { std::wstring msg(message.get()); // 解析 msg,执行相应操作 json::value val = json::parse(wstring_to_string(msg)); std::string type = json::value_to(val.at("type")); /* 使 settings 窗口可拖动 */ if (type == "dragStart") { ReleaseCapture(); PostMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); } else if (type == "resizeHitTest" || type == "resizeStart") { std::string hit = json::value_to(val.at("data")); int hitTest = HTCLIENT; if (hit == "left") { hitTest = HTLEFT; } else if (hit == "right") { hitTest = HTRIGHT; } else if (hit == "top") { hitTest = HTTOP; } else if (hit == "bottom") { hitTest = HTBOTTOM; } else if (hit == "left-top") { hitTest = HTTOPLEFT; } else if (hit == "right-top") { hitTest = HTTOPRIGHT; } else if (hit == "left-bottom") { hitTest = HTBOTTOMLEFT; } else if (hit == "right-bottom") { hitTest = HTBOTTOMRIGHT; } if (hitTest != HTCLIENT) { ReleaseCapture(); PostMessage(hwnd, WM_NCLBUTTONDOWN, hitTest, 0); } } else if (type == "focus") { SetFocus(hwnd); } else if (type == "windowControl") { std::string value = json::value_to(val.at("data")); if (value == "minimize") { ShowWindow(hwnd, SW_MINIMIZE); } else if (value == "maximize") { ShowWindow(hwnd, SW_MAXIMIZE); } else if (value == "close") { ShowWindow(hwnd, SW_HIDE); } else if (value == "restore") { ShowWindow(hwnd, SW_RESTORE); } } else if (type == "maximizeButtonRect") { try { auto &data = val.at("data").as_object(); double x = data.if_contains("x") ? json::value_to(*data.if_contains("x")) : 0.0; double y = data.if_contains("y") ? json::value_to(*data.if_contains("y")) : 0.0; double width = data.if_contains("width") ? json::value_to(*data.if_contains("width")) : 0.0; double height = data.if_contains("height") ? json::value_to(*data.if_contains("height")) : 0.0; double scale = data.if_contains("dpr") ? json::value_to(*data.if_contains("dpr")) : 0.0; if (scale <= 0.0) { scale = static_cast(GetDpiForWindow(hwnd)) / 96.0; } const int left = static_cast(std::lround(x * scale)); const int top = static_cast(std::lround(y * scale)); const int right = static_cast(std::lround((x + width) * scale)); const int bottom = static_cast(std::lround((y + height) * scale)); maximizeButtonRectSettingsWnd = {left, top, right, bottom}; hasMaximizeButtonRectSettingsWnd = true; } catch (const std::exception &) { } } else if (type == "configRequest") { PostSettingsConfig(); } else if (type == "configUpdate") { try { const auto &data = val.at("data").as_object(); const std::string path = json::value_to(data.at("path")); if (path == "input.schema") { const std::string value = json::value_to(data.at("value")); if (SetConfiguredInputScheme(value)) { ApplyConfiguredInputScheme(); PostSettingsConfig(); } } else if (path == "input.shuangpin_schema") { const std::string value = json::value_to(data.at("value")); if (SetConfiguredShuangpinSchema(value)) { PostSettingsConfig(); } } else if (path == "input.wubi_schema") { const std::string value = json::value_to(data.at("value")); if (SetConfiguredWubiSchema(value)) { PostSettingsConfig(); } } else if (path == "appearance.candidate_window_layout") { const std::string value = json::value_to(data.at("value")); if (SetConfiguredCandidateWindowLayout(value)) { ApplyConfiguredCandidateWindowLayout(); PostSettingsConfig(); } } else if (path == "general.floating_toolbar") { const bool value = json::value_to(data.at("value")); if (SetConfiguredFloatingToolbarEnabled(value)) { ApplyConfiguredFloatingToolbarVisibility(); PostSettingsConfig(); } } else if (path == "general.paging_minus_equal") { const bool value = json::value_to(data.at("value")); if (SetConfiguredPagingMinusEqualEnabled(value)) { PostSettingsConfig(); } } else if (path == "general.paging_tab") { const bool value = json::value_to(data.at("value")); if (SetConfiguredPagingTabEnabled(value)) { PostSettingsConfig(); } } else if (path == "general.paging_comma_period") { const bool value = json::value_to(data.at("value")); if (SetConfiguredPagingCommaPeriodEnabled(value)) { SendToTsfWorkerThreadViaNamedpipe( Global::DataFromServerMsgTypeToTsfWorkerThread::PagingCommaPeriodChanged, value ? L"1" : L"0"); PostSettingsConfig(); } } else if (path == "general.paging_page_up_down") { const bool value = json::value_to(data.at("value")); if (SetConfiguredPagingPageUpDownEnabled(value)) { PostSettingsConfig(); } } else if (path == "general.candidate_arrow_navigation") { const bool value = json::value_to(data.at("value")); if (SetConfiguredCandidateArrowNavigationEnabled(value)) { PostSettingsConfig(); } } else if (path == "helpcode.show_sp_helpcode_in_candidate_window") { const bool value = json::value_to(data.at("value")); if (SetConfiguredShowShuangpinHelpcodeInCandidateWindow(value)) { PostSettingsConfig(); } } else if (path == "helpcode.shuangpin_helpcode") { const bool value = json::value_to(data.at("value")); if (SetConfiguredShuangpinHelpcodeEnabled(value)) { PostSettingsConfig(); } } else if (path == "helpcode.quanpin_helpcode") { const bool value = json::value_to(data.at("value")); if (SetConfiguredQuanpinHelpcodeEnabled(value)) { PostSettingsConfig(); } } else if (path == "helpcode.show_qp_helpcode_in_candidate_window") { const bool value = json::value_to(data.at("value")); if (SetConfiguredShowQuanpinHelpcodeInCandidateWindow(value)) { PostSettingsConfig(); } } } catch (const std::exception &) { } } } return S_OK; }) .Get(), nullptr); /* 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) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Failed to create settings window webview2 environment.").c_str()); #endif return result; } ComPtr env3; if (FAILED(env->QueryInterface(IID_PPV_ARGS(&env3))) || !env3) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Failed to get ICoreWebView2Environment3 for composition.").c_str()); #endif return E_NOINTERFACE; } // Create WebView2 controller return env3->CreateCoreWebView2CompositionController( // hwnd, // Microsoft::WRL::Callback( // [hwnd](HRESULT result, ICoreWebView2CompositionController *controller) -> HRESULT { // return OnControllerCreatedSettingsWnd(hwnd, result, controller); // }) // .Get() // ); // } /** * @brief Post the window state of the settings window, 即,是否最大化了,供 settings 窗口的 js 进行相应的调整 * * @param hwnd */ void PostSettingsWindowState(HWND hwnd) { if (!::webviewSettingsWnd) { return; } nlohmann::json payload = {{"type", "windowState"}, {"data", {{"isMaximized", IsZoomed(hwnd) != FALSE}}}}; const std::wstring message = string_to_wstring(payload.dump()); ::webviewSettingsWnd->PostWebMessageAsJson(message.c_str()); } void PostSettingsConfig() { if (!::webviewSettingsWnd) { return; } nlohmann::json payload = { {"type", "configSnapshot"}, {"data", {{"input", {{"schema", GetConfiguredInputSchemeName()}, {"shuangpin_schema", GetConfiguredShuangpinSchema()}, {"wubi_schema", GetConfiguredWubiSchema()}}}, {"general", {{"floating_toolbar", GetConfiguredFloatingToolbarEnabled()}, {"paging_minus_equal", GetConfiguredPagingMinusEqualEnabled()}, {"paging_comma_period", GetConfiguredPagingCommaPeriodEnabled()}, {"paging_tab", GetConfiguredPagingTabEnabled()}, {"paging_page_up_down", GetConfiguredPagingPageUpDownEnabled()}, {"candidate_arrow_navigation", GetConfiguredCandidateArrowNavigationEnabled()}}}, {"appearance", {{"candidate_window_layout", GetConfiguredCandidateWindowLayout()}}}, {"helpcode", {{"shuangpin_helpcode", GetConfiguredShuangpinHelpcodeEnabled()}, {"quanpin_helpcode", GetConfiguredQuanpinHelpcodeEnabled()}, {"show_sp_helpcode_in_candidate_window", GetConfiguredShowShuangpinHelpcodeInCandidateWindow()}, {"show_qp_helpcode_in_candidate_window", GetConfiguredShowQuanpinHelpcodeInCandidateWindow()}}}}}}; const std::wstring message = string_to_wstring(payload.dump()); ::webviewSettingsWnd->PostWebMessageAsJson(message.c_str()); } /** * @brief 初始化 settings 窗口的 webview * * @param hwnd */ void InitWebviewSettingsWnd(HWND hwnd) { std::wstring appDataPath = GetAppdataPath(); CreateCoreWebView2EnvironmentWithOptions( // nullptr, // appDataPath.c_str(), // nullptr, // Microsoft::WRL::Callback( // [hwnd](HRESULT result, ICoreWebView2Environment *env) -> HRESULT { // return OnSettingsWindowEnvironmentCreated(hwnd, result, env); // }) // .Get() // ); // } // // // floating toolbar(ftb) 窗口 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)) { #ifdef FANY_DEBUG OutputDebugString( fmt::format(L"[msime]: Failed to create floating toolbar window webview2 controller.").c_str()); #endif return E_FAIL; } /* 给 controller 和 webview 赋值 */ webviewControllerFtbWnd = controller; webviewControllerFtbWnd->get_CoreWebView2(webviewFtbWnd.GetAddressOf()); if (!webviewFtbWnd) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Failed to get webview2 instance.").c_str()); #endif return E_FAIL; } UpdateSmallWindowWebviewVisibility(hwnd, IsWindowVisible(hwnd) != FALSE); // Configure webviewFtbWindow settings ComPtr settings; if (SUCCEEDED(webviewFtbWnd->get_Settings(&settings))) { settings->put_IsScriptEnabled(TRUE); settings->put_AreDefaultScriptDialogsEnabled(FALSE); settings->put_IsWebMessageEnabled(TRUE); settings->put_AreHostObjectsAllowed(FALSE); // 禁用右键菜单和开发者工具 settings->put_AreDefaultContextMenusEnabled(FALSE); settings->put_AreDevToolsEnabled(FALSE); // 禁止界面缩放 settings->put_IsZoomControlEnabled(FALSE); settings->put_IsStatusBarEnabled(FALSE); ComPtr settings3; if (SUCCEEDED(settings.As(&settings3))) { settings3->put_AreBrowserAcceleratorKeysEnabled(FALSE); } ComPtr settings5; if (SUCCEEDED(settings.As(&settings5))) { settings5->put_IsGeneralAutofillEnabled(FALSE); settings5->put_IsPasswordAutosaveEnabled(FALSE); } } // 初始时缩放设置成 1.0 webviewControllerFtbWnd->put_ZoomFactor(1.0); // 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); webviewControllerFtbWnd->put_Bounds(bounds); // Navigate to HTML HRESULT hr = webviewFtbWnd->NavigateToString(::HTMLStringFtbWnd.c_str()); if (FAILED(hr)) { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Failed to navigate to string.").c_str()); #endif } /* Debug console */ // webviewFtbWindow->OpenDevToolsWindow(); /* 处理 js 发过来的消息 */ webviewFtbWnd->add_WebMessageReceived( Microsoft::WRL::Callback( [hwnd](ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) -> HRESULT { wil::unique_cotaskmem_string message; HRESULT hr = args->TryGetWebMessageAsString(&message); if (SUCCEEDED(hr) && message.get()) { std::wstring msg(message.get()); // 解析 msg,执行相应操作 json::value val = json::parse(wstring_to_string(msg)); std::string type = json::value_to(val.at("type")); /* 使 floating toolbar 窗口可拖动 */ if (type == "dragStart") { ReleaseCapture(); PostMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); } else if (type == "changeIMEMode") { std::string mode = json::value_to(val.at("data")); if (mode == "cn") // Change to CN { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Change to CN").c_str()); #endif SendToTsfWorkerThreadViaNamedpipe(Global::DataFromServerMsgTypeToTsfWorkerThread::SwitchToCn, L""); } else if (mode == "en") // Change to EN { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Change to EN").c_str()); #endif SendToTsfWorkerThreadViaNamedpipe(Global::DataFromServerMsgTypeToTsfWorkerThread::SwitchToEn, L""); } } else if (type == "changeCharMode") { std::string mode = json::value_to(val.at("data")); if (mode == "fullwidth") { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Change to fullwidth").c_str()); #endif SendToTsfWorkerThreadViaNamedpipe( Global::DataFromServerMsgTypeToTsfWorkerThread::SwitchToFullwidth, L""); } else if (mode == "halfwidth") { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Change to halfwidth").c_str()); #endif SendToTsfWorkerThreadViaNamedpipe( Global::DataFromServerMsgTypeToTsfWorkerThread::SwitchToHalfwidth, L""); } } else if (type == "changePuncMode") { std::string mode = json::value_to(val.at("data")); if (mode == "puncEn") { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Change to puncEn").c_str()); #endif SendToTsfWorkerThreadViaNamedpipe( Global::DataFromServerMsgTypeToTsfWorkerThread::SwitchToPuncEn, L""); } else if (mode == "puncCn") { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Change to puncCn").c_str()); #endif SendToTsfWorkerThreadViaNamedpipe( Global::DataFromServerMsgTypeToTsfWorkerThread::SwitchToPuncCn, L""); } } else if (type == "openSettings") { #ifdef FANY_DEBUG OutputDebugString(fmt::format(L"[msime]: Open settings").c_str()); #endif OpenSettingsApplication(); } } return S_OK; }) .Get(), nullptr); 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) { #ifdef FANY_DEBUG OutputDebugString( fmt::format(L"[msime]: Failed to create floating toolbar window webview2 environment.").c_str()); #endif return result; } // Create WebView2 controller return env->CreateCoreWebView2Controller( // hwnd, // Callback( // [hwnd](HRESULT result, ICoreWebView2Controller *controller) -> HRESULT { // return OnControllerCreatedFtbWnd(hwnd, result, controller); // }) // .Get() // ); // } /** * @brief Initialize the candidate, tray menu, and floating toolbar WebViews in * one environment so they share a browser process and user data folder. */ void InitSmallWindowWebviews(HWND candHwnd, HWND menuHwnd, HWND ftbHwnd) { auto options = Microsoft::WRL::Make(); options->put_AdditionalBrowserArguments( // L"--disable-features=TranslateUI " L"--disable-background-networking " L"--disable-default-apps " L"--disable-sync " L"--disable-prompt-on-repost " L"--no-first-run"); const std::wstring appDataPath = GetAppdataPath(); CreateCoreWebView2EnvironmentWithOptions( nullptr, appDataPath.c_str(), options.Get(), Callback( [candHwnd, menuHwnd, ftbHwnd](HRESULT result, ICoreWebView2Environment *env) -> HRESULT { if (FAILED(result) || !env) { ShowErrorMessage(candHwnd, L"Failed to create the shared WebView2 environment."); return FAILED(result) ? result : E_FAIL; } smallWindowWebviewEnvironment = env; const HRESULT candResult = OnEnvironmentCreated(candHwnd, S_OK, env); const HRESULT menuResult = OnMenuWindowEnvironmentCreated(menuHwnd, S_OK, env); const HRESULT ftbResult = OnFtbWindowEnvironmentCreated(ftbHwnd, S_OK, env); if (FAILED(candResult)) return candResult; if (FAILED(menuResult)) return menuResult; return ftbResult; }) .Get()); } /** * @brief 更新 floating toolbar 窗口的中英文切换状态 * * @param webview * @param cnEnState 1: 中文, 0: 英文 */ void UpdateFtbCnEnState(ComPtr webview, int cnEnState) { if (webview == nullptr) { return; } if (cnEnState == 1) { std::wstring script; script.reserve(256); script.append(L"document.getElementById('cn').style.display = 'flex';"); script.append(L"document.getElementById('en').style.display = 'none';"); webview->ExecuteScript(script.c_str(), nullptr); } else if (cnEnState == 0) { std::wstring script; script.reserve(256); script.append(L"document.getElementById('cn').style.display = 'none';"); script.append(L"document.getElementById('en').style.display = 'flex';"); webview->ExecuteScript(script.c_str(), nullptr); } } /** * @brief 更新 floating toolbar 窗口的中英文切换状态和标点切换状态 * * @param webview * @param cnEnState 1: 中文, 0: 英文 * @param puncState 1: 中文标点, 0: 英文标点 */ void UpdateFtbCnEnAndPuncState(ComPtr webview, int cnEnState, int puncState) { if (webview == nullptr) { return; } std::wstring script; script.reserve(256); if (cnEnState == 1) { script.append(L"document.getElementById('cn').style.display = 'flex';"); script.append(L"document.getElementById('en').style.display = 'none';"); } else if (cnEnState == 0) { script.append(L"document.getElementById('cn').style.display = 'none';"); script.append(L"document.getElementById('en').style.display = 'flex';"); } if (puncState == 1) { script.append(L"document.getElementById('puncCn').style.display = 'flex';"); script.append(L"document.getElementById('puncEn').style.display = 'none';"); } else if (puncState == 0) { script.append(L"document.getElementById('puncCn').style.display = 'none';"); script.append(L"document.getElementById('puncEn').style.display = 'flex';"); } webview->ExecuteScript(script.c_str(), nullptr); } /** * @brief 更新 floating toolbar 窗口的中英文切换状态和标点切换状态 * * @param webview * @param cnEnState 1: 中文, 0: 英文 * @param doubleSingleByteState 1: 全角, 0: 半角 * @param puncState 1: 中文标点, 0: 英文标点 */ void UpdateFtbCnEnAndDoubleSingleAndPuncState( // ComPtr webview, // int cnEnState, // int doubleSingleByteState, // int puncState // ) { if (webview == nullptr) { return; } std::wstring script; script.reserve(256); if (cnEnState == 1) { script.append(L"document.getElementById('cn').style.display = 'flex';"); script.append(L"document.getElementById('en').style.display = 'none';"); } else if (cnEnState == 0) { script.append(L"document.getElementById('cn').style.display = 'none';"); script.append(L"document.getElementById('en').style.display = 'flex';"); } if (doubleSingleByteState == 1) { script.append(L"document.getElementById('fullwidth').style.display = 'flex';"); script.append(L"document.getElementById('halfwidth').style.display = 'none';"); } else if (doubleSingleByteState == 0) { script.append(L"document.getElementById('fullwidth').style.display = 'none';"); script.append(L"document.getElementById('halfwidth').style.display = 'flex';"); } if (puncState == 1) { script.append(L"document.getElementById('puncCn').style.display = 'flex';"); script.append(L"document.getElementById('puncEn').style.display = 'none';"); } else if (puncState == 0) { script.append(L"document.getElementById('puncCn').style.display = 'none';"); script.append(L"document.getElementById('puncEn').style.display = 'flex';"); } webview->ExecuteScript(script.c_str(), nullptr); } /** * @brief 更新 floating toolbar 窗口的标点切换状态 * * @param webview * @param puncState 1: 中文标点, 0: 英文标点 */ void UpdateFtbPuncState(ComPtr webview, int puncState) { if (webview == nullptr) { return; } if (puncState == 1) { std::wstring script; script.reserve(256); script.append(L"document.getElementById('puncCn').style.display = 'flex';"); script.append(L"document.getElementById('puncEn').style.display = 'none';"); webview->ExecuteScript(script.c_str(), nullptr); } else if (puncState == 0) { std::wstring script; script.reserve(256); script.append(L"document.getElementById('puncCn').style.display = 'none';"); script.append(L"document.getElementById('puncEn').style.display = 'flex';"); webview->ExecuteScript(script.c_str(), nullptr); } } /** * @brief 更新 floating toolbar 窗口的全角和半角状态 * * @param webview * @param doubleSingleByteState 0: 半角, 1: 全角 */ void UpdateFtbDoubleSingleByteState(ComPtr webview, int doubleSingleByteState) { if (webview == nullptr) { return; } std::wstring script; script.reserve(256); if (doubleSingleByteState == 0) { script.append(L"document.getElementById('halfwidth').style.display = 'flex';"); script.append(L"document.getElementById('fullwidth').style.display = 'none';"); } else if (doubleSingleByteState == 1) { script.append(L"document.getElementById('halfwidth').style.display = 'none';"); script.append(L"document.getElementById('fullwidth').style.display = 'flex';"); } webview->ExecuteScript(script.c_str(), nullptr); }