From 5aed0fcbf022d164a88bdcf1fc6b9594e1bf3e6b Mon Sep 17 00:00:00 2001 From: "Lu Fan(SonnyCalcr)" <1730976608@qq.com> Date: Sun, 11 Oct 2026 02:21:40 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=83=A8=E5=88=86?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=89=93=E4=B8=8D=E5=BC=80=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E9=A1=B5=EF=BC=8CWebView2=20=E7=BC=BA=E5=A4=B1=E6=97=B6?= =?UTF-8?q?=E4=BB=8D=E5=8F=AF=E6=AD=A3=E5=B8=B8=E6=89=93=E5=AD=97=20(#801)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(installer): VC++ 运行库要求提到 14.40 并给出直接下载链接 服务端程序用 MSVC 14.40+ 编译并导入 msvcp140.dll,配上更旧的运行库, std::mutex 首次加锁即访问冲突、进程无声闪退,设置页因此打不开;原先 14.20 的门槛放过了装着 2019 / 早期 2022 运行库的机器。 - 缺失与「已装但过旧」分开提示,并显示当前版本 - 「是」直接下载 VC++ x64 运行库与 WebView2 Evergreen Bootstrapper - 更正 WebView2 缺失的影响:只有设置页需要它,默认 D2D 候选窗照常打字 * fix(server): WebView2 不可用时设置页给出提示,候选窗退回 D2D - 设置进程不再忽略 WebView2 environment / controller 创建失败(运行时缺失 时同步失败、回调不来,界面会一直停在启动画面),改为弹框说明原因、错误码 与下载直链后退出 - ui_backend = webview2 而检测不到运行时时,本次进程改用 D2D 渲染,保证能 正常打字;不改配置,装好运行时后下次启动自动恢复 - WebView2 下载地址统一为 Evergreen Bootstrapper 直链 --- installer/README.md | 7 ++- installer/msime_setup.iss | 66 ++++++++++++++------- server/src/config/ime_config.h | 3 + server/src/config/ime_config_appearance.cpp | 5 ++ server/src/main.cpp | 1 + server/src/settings/settings_app.cpp | 53 +++++++++++++++-- server/src/utils/common_utils.h | 2 + server/src/webview2/windows_webview2.cpp | 28 ++++++++- server/src/webview2/windows_webview2.h | 3 + 9 files changed, 136 insertions(+), 32 deletions(-) diff --git a/installer/README.md b/installer/README.md index a17ecbf9..d7de15b2 100644 --- a/installer/README.md +++ b/installer/README.md @@ -47,12 +47,13 @@ pwsh -File ./Prepare-PackageFiles.ps1 -TargetVersion 1.2.3 -RepoRoot .. ` ## 运行时依赖检查 -安装包不携带 WebView2 Runtime 和 VC 运行库——前者有自己的 Evergreen 更新通道,后者是系统级共享组件。但缺了任何一个,输入法装完就是坏的,而故障出现在安装结束之后(Server 起不来,或者候选窗一片空白),用户看不到任何解释。所以 `msime_setup.iss` 的 `InitializeSetup` 在安装开始前先查一遍,缺哪个就当场说清楚,并给出下载地址。 +安装包不携带 WebView2 Runtime 和 VC 运行库——前者有自己的 Evergreen 更新通道,后者是系统级共享组件。缺 VC 运行库 Server 起不来;缺 WebView2 设置页打不开,但默认的 D2D 候选窗(以及表情 / 键盘 / 手写面板,都是 `msimeui` 原生绘制)照常可用,选了 `appearance.ui_backend = "webview2"` 的 Server 启动时检测不到运行时会临时退回 D2D(`FallBackToD2dIfWebView2RuntimeMissing`,不改配置,装好后下次启动自动恢复)。这些故障都出现在安装结束之后,用户看不到任何解释。所以 `msime_setup.iss` 的 `InitializeSetup` 在安装开始前先查一遍,缺哪个就当场说清楚,并给出下载地址。 - **Microsoft Edge WebView2 Runtime**:读 EdgeUpdate 客户端键 `{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}` 的 `pv`。机器级安装由 32 位的 EdgeUpdate 写入,所以主查 `HKLM32`,再看 `HKLM64` 和 `HKCU`(用户级安装)。`pv` 缺失或残留成 `0.0.0.0` 都算没装 -- **Visual C++ 2015–2022 可再发行组件包(x64)**:读 `Software\Microsoft\VisualStudio\14.0\VC\Runtimes\x64` 的 `Installed` / `Major` / `Minor`,`HKLM32` 和 `HKLM64` 两个视图都查。要求 **14.20 以上**(VC++ 2019 起):`server_exe\` 里每个 exe 都导入了 `vcruntime140_1.dll`,它从那一版才开始随 redist 分发,只装了 2015 / 2017 版的机器同样有 `Installed=1`,却照样起不来。32 位 TSF DLL 用静态 CRT(`windows/CMakeLists.txt` 的 `CMAKE_MSVC_RUNTIME_LIBRARY`),所以不检查 x86 redist +- **Visual C++ 2015–2022 可再发行组件包(x64)**:读 `Software\Microsoft\VisualStudio\14.0\VC\Runtimes\x64` 的 `Installed` / `Major` / `Minor`,`HKLM32` 和 `HKLM64` 两个视图都查。要求 **14.40 以上**(常量 `VCRuntimeMinMinor`):`server_exe\` 里的 exe 用 MSVC 14.40+(VS 2022 17.10 起)编译并导入 `msvcp140.dll`,这一版起 `std::mutex` 的构造函数是 constexpr,配上更旧的 `msvcp140.dll` 会在第一次加锁时访问冲突、无声闪退——设置页「点了没反应」就是这个现象。装了 2019 / 早期 2022 运行库的机器同样有 `Installed=1`,所以必须比版本号。构建工具链升级到要求更高 redist 时同步调高这个常量。32 位 TSF DLL 用静态 CRT(`windows/CMakeLists.txt` 的 `CMAKE_MSVC_RUNTIME_LIBRARY`),所以不检查 x86 redist - **不要改成查 `System32` 里的 DLL。** Setup.exe 是 32 位进程,Pascal Script 的 `FileExists` 会被 WOW64 重定向到 `SysWOW64`,查到的是 x86 运行库;只装了 x64 redist 的机器会被误判成缺少 x64 运行库。绕开重定向的办法不可移植:`EnableFsRedirection` 在 Inno Setup 7 里已被移除,而它的替代品在 CI 用的 6.x 里还不存在 -- 缺组件时弹一次对话框:选「是」打开下载页并结束安装,选「否」继续装。**不阻断安装**——用户可能马上就要去补组件,也可能只是在准备环境。静默安装(CI、批量部署)用 `SuppressibleMsgBox` 默认「否」直接继续,缺什么已经写进安装日志(`/LOG=`) +- 缺组件或版本过旧时弹一次对话框,区分「没装」和「已装 x.y 但过旧」,列出直接下载链接(VC++:`https://aka.ms/vc14/vc_redist.x64.exe`;WebView2 Evergreen Bootstrapper:`https://go.microsoft.com/fwlink/p/?LinkId=2124703`)和对应的说明页:选「是」直接下载这些安装包并结束安装,选「否」继续装。**不阻断安装**——用户可能马上就要去补组件,也可能只是在准备环境。静默安装(CI、批量部署)用 `SuppressibleMsgBox` 默认「否」直接继续,缺什么已经写进安装日志(`/LOG=`) +- 安装时检查只是第一道关:用户可能选了「否」,也可能事后卸掉或损坏了 WebView2。所以设置进程在 WebView2 environment / controller 创建失败(含运行时缺失时的同步失败)时会弹框说明原因和错误码、给出同一条 WebView2 直链并退出,而不是留一个空白窗口 ## 依赖 diff --git a/installer/msime_setup.iss b/installer/msime_setup.iss index 8cac1561..5c6736ec 100644 --- a/installer/msime_setup.iss +++ b/installer/msime_setup.iss @@ -200,9 +200,15 @@ const 下载页也统一给简体中文版,省得用户落到英文页上再自己切。} WebView2DownloadUrl = 'https://developer.microsoft.com/zh-cn/microsoft-edge/webview2'; + { Evergreen Bootstrapper 直链,点开就是安装器。} + WebView2DirectUrl = 'https://go.microsoft.com/fwlink/p/?LinkId=2124703'; VCRuntimeKey = 'Software\Microsoft\VisualStudio\14.0\VC\Runtimes\x64'; VCRuntimeDownloadUrl = 'https://learn.microsoft.com/zh-cn/cpp/windows/latest-supported-vc-redist?view=msvc-170'; + { 微软给的「最新受支持 v14 x64 运行库」固定直链。} + VCRuntimeDirectUrl = 'https://aka.ms/vc14/vc_redist.x64.exe'; + { 见 VCRuntimeInstalledInView 上方的说明。} + VCRuntimeMinMinor = 40; var VersionDirName: String; @@ -215,9 +221,10 @@ var DataDirAdoptedEmpty: Boolean; { WebView2 Runtime 与 VC 运行库都不随包分发:前者有自己的 Evergreen 更新通道, - 后者是系统级共享组件,安装器不该替用户装。但缺了任何一个,输入法装完就是坏的, - 而故障现场在安装结束之后——Server 直接起不来,或者候选窗一片空白,用户看不到 - 任何解释。所以在安装开始前查一遍,把原因当场说清楚。 + 后者是系统级共享组件,安装器不该替用户装。缺 VC 运行库 Server 直接起不来; + 缺 WebView2 设置页打不开(默认 D2D 候选窗照常可用,选了 WebView2 渲染的 + Server 启动时会临时退回 D2D)。故障现场都在安装结束之后,用户看不到任何解释, + 所以在安装开始前查一遍,把原因当场说清楚。 返回空串表示没装;否则是 pv 里的版本号。} function ReadWebView2Version: String; @@ -244,9 +251,11 @@ end; // {sysnative} 对 64 位安装器又不可用。注册表访问没有这个问题,HKLM32 / HKLM64 // 两个视图都是显式指定的。 // -// x64 redist 的安装状态就记在这个键里。要求 14.20 以上(VC++ 2019 起):server_exe -// 里每个 exe 都导入了 vcruntime140_1.dll,而它是从那一版才开始随 redist 分发的, -// 只装了 2015/2017 版的机器同样有 Installed=1,却照样起不来。 +// x64 redist 的安装状态就记在这个键里。要求 14.40 以上:server_exe 里的 exe 都用 +// MSVC 14.40+(VS 2022 17.10 起)编译并导入 msvcp140.dll,这一版起 std::mutex 的 +// 构造函数改成了 constexpr,配上 14.40 之前的 msvcp140.dll 会在第一次加锁时访问冲突, +// 进程无声闪退(设置页打不开就是这个样子)。装了 2019 / 早期 2022 运行库的机器 +// 同样有 Installed=1,所以必须比版本号,不能只看装没装。 // 32 位 TSF DLL 用的是静态 CRT(windows/CMakeLists.txt 里的 // CMAKE_MSVC_RUNTIME_LIBRARY),所以不必检查 x86 redist。 // 注意:Inno Setup 的花括号注释不能嵌套,上面提到的常量会提前闭合 { } 注释, @@ -262,7 +271,7 @@ begin (Installed = 1) and RegQueryDWordValue(RootKey, VCRuntimeKey, 'Major', Major) and RegQueryDWordValue(RootKey, VCRuntimeKey, 'Minor', Minor) and - ((Major > 14) or ((Major = 14) and (Minor >= 20))); + ((Major > 14) or ((Major = 14) and (Minor >= VCRuntimeMinMinor))); end; { redist 的安装包是 32 位的,键通常落在 WOW6432Node 下,但较新的版本两个视图都写, @@ -273,7 +282,7 @@ begin VCRuntimeInstalledInView(HKLM32) or VCRuntimeInstalledInView(HKLM64); end; -{ 只用于写日志,不参与判定。} +{ 只用于日志和提示文字,不参与判定。} function ReadVCRuntimeVersion: String; begin Result := ''; @@ -298,6 +307,7 @@ var WebView2Version: String; NeedsWebView2: Boolean; NeedsVCRuntime: Boolean; + VCRuntimeVersion: String; Prompt: String; begin Result := True; @@ -310,29 +320,43 @@ begin Log('WebView2 Runtime found: ' + WebView2Version); NeedsVCRuntime := not VCRuntimeIsInstalled; + { 有版本号却没通过判定,就是装了但太旧,提示里要和「没装」分开说。} + VCRuntimeVersion := ReadVCRuntimeVersion; if NeedsVCRuntime then - Log('Prerequisite missing: Visual C++ 2015-2022 Redistributable (x64).') + Log('Prerequisite missing or outdated: Visual C++ 2015-2022 Redistributable (x64), found "' + + VCRuntimeVersion + '", need 14.' + IntToStr(VCRuntimeMinMinor) + ' or later.') else - Log('Visual C++ runtime found: ' + ReadVCRuntimeVersion); + Log('Visual C++ runtime found: ' + VCRuntimeVersion); if not (NeedsWebView2 or NeedsVCRuntime) then exit; - Prompt := '这台电脑缺少输入法运行所需的系统组件:' + #13#10; + Prompt := '这台电脑缺少输入法用到的系统组件,或版本过旧:' + #13#10; if NeedsVCRuntime then + begin Prompt := Prompt + #13#10 + - '● Visual C++ 2015-2022 可再发行组件包(x64)' + #13#10 + - ' 缺少它输入法主程序根本无法启动(提示找不到 VCRUNTIME140.dll 之类)。' + #13#10 + - ' 下载:' + VCRuntimeDownloadUrl + #13#10 + - ' 直链:https://aka.ms/vs/17/release/vc_redist.x64.exe' + #13#10; + '● Visual C++ 2015-2022 可再发行组件包(x64),需要 14.' + + IntToStr(VCRuntimeMinMinor) + ' 或更高版本' + #13#10; + if VCRuntimeVersion <> '' then + Prompt := Prompt + + ' 当前已安装 ' + VCRuntimeVersion + ',版本过旧,设置页等程序会启动即闪退。' + #13#10 + else + Prompt := Prompt + + ' 缺少它输入法主程序根本无法启动(提示找不到 VCRUNTIME140.dll 之类)。' + #13#10; + Prompt := Prompt + + ' 直接下载:' + VCRuntimeDirectUrl + #13#10 + + ' 说明页面:' + VCRuntimeDownloadUrl + #13#10; + end; if NeedsWebView2 then Prompt := Prompt + #13#10 + '● Microsoft Edge WebView2 Runtime' + #13#10 + - ' 缺少它候选窗口、设置页和表情 / 手写 / 键盘面板都打不开。' + #13#10 + - ' 下载:' + WebView2DownloadUrl + #13#10; + ' 缺少它设置页面打不开。默认的 D2D 候选窗不依赖它,打字不受影响;' + #13#10 + + ' 选了 WebView2 渲染的候选窗会临时改用 D2D,装好后自动恢复。' + #13#10 + + ' 直接下载:' + WebView2DirectUrl + #13#10 + + ' 说明页面:' + WebView2DownloadUrl + #13#10; Prompt := Prompt + #13#10 + - '点击「是」打开下载页面并结束本次安装,装好组件后重新运行本安装程序;' + #13#10 + - '点击「否」继续安装(装完仍需自行补齐上述组件,输入法才能正常工作)。'; + '点击「是」直接下载上述组件并结束本次安装,装好后重新运行本安装程序;' + #13#10 + + '点击「否」继续安装(装完仍需自行补齐上述组件,对应功能才能正常使用)。'; { 静默安装(CI、企业批量部署)不该被一个对话框卡住:默认继续,缺什么已经写进日志了。} if @@ -340,9 +364,9 @@ begin then begin if NeedsVCRuntime then - OpenDownloadPage(VCRuntimeDownloadUrl); + OpenDownloadPage(VCRuntimeDirectUrl); if NeedsWebView2 then - OpenDownloadPage(WebView2DownloadUrl); + OpenDownloadPage(WebView2DirectUrl); Result := False; end; end; diff --git a/server/src/config/ime_config.h b/server/src/config/ime_config.h index 56e7da8e..552a9615 100644 --- a/server/src/config/ime_config.h +++ b/server/src/config/ime_config.h @@ -535,6 +535,9 @@ const std::string &GetConfiguredUiBackend(); bool SetConfiguredUiBackend(const std::string &backend); // Process-lifetime renderer chosen at InitImeConfig. Independent of later reloads. bool UseD2dSmallWindowUi(); +// Switch this process to the D2D renderer without touching the configured value, so the user's +// choice comes back on the next start. Only valid before the candidate window is created. +void FallBackToD2dSmallWindowUi(); // "fluent" | "wechat" | "graphite" | "willow_green" | "autumn_osmanthus" | "microsoft" // — candidate-window and floating-toolbar skin. const std::string &GetConfiguredCandidateSkin(); diff --git a/server/src/config/ime_config_appearance.cpp b/server/src/config/ime_config_appearance.cpp index 23e79b04..9404261c 100644 --- a/server/src/config/ime_config_appearance.cpp +++ b/server/src/config/ime_config_appearance.cpp @@ -231,6 +231,11 @@ bool UseD2dSmallWindowUi() UiBackendPolicy::Backend::Native; } +void FallBackToD2dSmallWindowUi() +{ + g_ui_backend_active = "d2d"; +} + const std::string &GetConfiguredCandidateSkin() { return g_candidate_skin; diff --git a/server/src/main.cpp b/server/src/main.cpp index 55dd868a..5e8d923e 100644 --- a/server/src/main.cpp +++ b/server/src/main.cpp @@ -116,6 +116,7 @@ int CALLBACK WinMain(_In_ HINSTANCE hInstance, _In_ HINSTANCE /*hPrevInstance*/, // Initialize config InitImeConfig(); + FallBackToD2dIfWebView2RuntimeMissing(); VoiceInput::Initialize(); Global::candidate_ui.page_size = GetConfiguredCandidatePageSize(); diff --git a/server/src/settings/settings_app.cpp b/server/src/settings/settings_app.cpp index 5a868c49..5a0a8368 100644 --- a/server/src/settings/settings_app.cpp +++ b/server/src/settings/settings_app.cpp @@ -125,6 +125,36 @@ void TraceStartup(const char *stage) } } +// WebView2 起不来时设置窗口只剩一块空白(运行时缺失时同步失败、回调根本不来,splash 会一直转), +// 用户看不出是怎么回事。把原因和下载地址说清楚,然后退出,不留一个空窗口。 +void ReportWebViewStartupFailure(HWND hwnd, const wchar_t *stage, HRESULT hr) +{ + static bool reported = false; + if (reported) + return; + reported = true; + TraceStartup("ui: webview startup failed"); + SettingsSplash::Dismiss(); + + wchar_t message[1024]{}; + swprintf_s(message, + L"设置页面需要 Microsoft Edge WebView2 Runtime,但它没能启动(%s,错误码 0x%08X)。\r\n" + L"The settings page needs the Microsoft Edge WebView2 Runtime, but it could not start " + L"(%s, error 0x%08X).\r\n\r\n" + L"输入法打字不受影响,只有设置页面需要它。\r\n" + L"Typing is not affected; only the settings page needs it.\r\n\r\n" + L"请安装或修复 WebView2 Runtime 后重新打开设置:\r\n" + L"Install or repair the WebView2 Runtime, then open the settings again:\r\n" + L"%s\r\n\r\n" + L"是否现在打开下载链接?\r\nOpen the download link now?", + stage, static_cast(hr), stage, static_cast(hr), + CommonUtils::kWebView2RuntimeDownloadUrl); + if (MessageBoxW(hwnd, message, L"水杉输入法 / Metasequoia IME", MB_YESNO | MB_ICONERROR | MB_SETFOREGROUND) == + IDYES) + ShellExecuteW(hwnd, L"open", CommonUtils::kWebView2RuntimeDownloadUrl, nullptr, nullptr, SW_SHOWNORMAL); + DestroyWindow(hwnd); +} + nlohmann::json CandidateColorsToJson(const CandidateSkinCatalog::CandidateColors &colors) { nlohmann::json json = nlohmann::json::object(); @@ -1737,31 +1767,42 @@ void InitWebView(HWND hwnd) L"--disable-sync " L"--disable-prompt-on-repost " L"--no-first-run"); - CreateCoreWebView2EnvironmentWithOptions( + const HRESULT create_result = CreateCoreWebView2EnvironmentWithOptions( nullptr, user_data.c_str(), options.Get(), Callback( [hwnd](HRESULT result, ICoreWebView2Environment *environment) -> HRESULT { TraceStartup("ui: environment created"); if (FAILED(result) || !environment) { - SettingsSplash::Dismiss(); - return FAILED(result) ? result : E_FAIL; + const HRESULT hr = FAILED(result) ? result : E_FAIL; + ReportWebViewStartupFailure(hwnd, L"environment", hr); + return hr; } + // ICoreWebView2Environment3 需要较新的运行时,过旧的 WebView2 会停在这里。 ComPtr environment3; if (FAILED(environment->QueryInterface(IID_PPV_ARGS(&environment3)))) { - SettingsSplash::Dismiss(); + ReportWebViewStartupFailure(hwnd, L"environment3", E_NOINTERFACE); return E_NOINTERFACE; } - return environment3->CreateCoreWebView2CompositionController( + const HRESULT controller_request = environment3->CreateCoreWebView2CompositionController( hwnd, Callback( [hwnd](HRESULT controller_result, ICoreWebView2CompositionController *controller) -> HRESULT { - return OnControllerCreated(hwnd, controller_result, controller); + const HRESULT hr = OnControllerCreated(hwnd, controller_result, controller); + if (FAILED(hr)) + ReportWebViewStartupFailure(hwnd, L"controller", hr); + return hr; }) .Get()); + if (FAILED(controller_request)) + ReportWebViewStartupFailure(hwnd, L"controller", controller_request); + return controller_request; }) .Get()); + // 运行时缺失时这里同步返回失败,上面的回调不会被调用。 + if (FAILED(create_result)) + ReportWebViewStartupFailure(hwnd, L"environment", create_result); } UINT32 MouseKeys(WPARAM w_param) diff --git a/server/src/utils/common_utils.h b/server/src/utils/common_utils.h index 527275fa..de58b28b 100644 --- a/server/src/utils/common_utils.h +++ b/server/src/utils/common_utils.h @@ -12,6 +12,8 @@ void SendImeInputs(std::wstring words); namespace CommonUtils { +// Evergreen Bootstrapper 的官方直链,点开即下载安装器,用户不用在文档页里自己找。 +inline constexpr wchar_t kWebView2RuntimeDownloadUrl[] = L"https://go.microsoft.com/fwlink/p/?LinkId=2124703"; std::wstring get_local_appdata_path_w(); std::wstring get_ime_data_path_w(); // Directory holding config.toml. Same as the data directory unless METASEQUOIA_IME_CONFIG_DIR diff --git a/server/src/webview2/windows_webview2.cpp b/server/src/webview2/windows_webview2.cpp index 0ed83479..537dde44 100644 --- a/server/src/webview2/windows_webview2.cpp +++ b/server/src/webview2/windows_webview2.cpp @@ -494,9 +494,9 @@ void ReportSmallWindowWebviewUnavailable(HRESULT last_hr, SmallWindowWebviewFail L"shown for now: {}.\r\n\r\n" L"请安装或修复 Microsoft Edge WebView2 Runtime 后重新登录:\r\n" L"Install or repair the Microsoft Edge WebView2 Runtime, then sign in again:\r\n" - L"https://developer.microsoft.com/microsoft-edge/webview2/\r\n\r\n" + L"{}\r\n\r\n" L"错误码 / error: {:#x}", - hosts_zh, hosts_en, static_cast(last_hr)) + hosts_zh, hosts_en, CommonUtils::kWebView2RuntimeDownloadUrl, static_cast(last_hr)) : fmt::format(L"WebView2 运行时已启动,但{}没能创建,暂时无法显示。\r\n" L"The WebView2 Runtime started, but the following could not be created and cannot be " L"shown for now: {}.\r\n\r\n" @@ -1292,6 +1292,30 @@ void InitSmallWindowWebviews(HWND candHwnd, HWND menuHwnd, HWND ftbHwnd) BeginSmallWindowWebviewEnvironmentCreate(); } +// The WebView2 hosts are shown cloaked and only uncloak after their first paint, so without a +// Runtime the candidate window never appears and the IME cannot be used at all. The D2D renderer +// has no such dependency. The configured value is left alone: once the Runtime is installed, the +// next Server start goes back to WebView2 on its own. +void FallBackToD2dIfWebView2RuntimeMissing() +{ + if (UseD2dSmallWindowUi()) + { + return; + } + LPWSTR version = nullptr; + const HRESULT hr = GetAvailableCoreWebView2BrowserVersionString(nullptr, &version); + const bool available = SUCCEEDED(hr) && version && version[0] != L'\0'; + CoTaskMemFree(version); + if (available) + { + return; + } + FallBackToD2dSmallWindowUi(); + TraceSmallWindowWebview(fmt::format(L"edge webview runtime not found (hr={:#x}); ui_backend=webview2 falls back " + L"to d2d for this session", + static_cast(hr))); +} + bool PrepareTrayMenuWebviewForShow() { if (TrayMenuPresenter::Instance().IsBound()) diff --git a/server/src/webview2/windows_webview2.h b/server/src/webview2/windows_webview2.h index 6b933f1c..9fb2590a 100644 --- a/server/src/webview2/windows_webview2.h +++ b/server/src/webview2/windows_webview2.h @@ -102,6 +102,9 @@ void InflateCandWnd(const CandidateWindowContent &content, std::function std::pair LastCandidateSlotMeasuredSize(); void InflateMeasureDivCandWnd(const CandidateWindowContent &content, std::function onComplete = nullptr); void InitSmallWindowWebviews(HWND candHwnd, HWND menuHwnd, HWND ftbHwnd); +// appearance.ui_backend = "webview2" but no usable WebView2 Runtime: run this process on the D2D +// renderer so typing still works. Call after InitImeConfig and before CreateCandidateWindow. +void FallBackToD2dIfWebView2RuntimeMissing(); void ShutdownWebviews(); void UpdateSmallWindowWebviewVisibility(HWND hwnd, bool visible); // Kick (or retry) shared small-window WebView2 init. Returns true when the menu