diff --git a/installer/README.md b/installer/README.md index 585f5ebc..23e9eba3 100644 --- a/installer/README.md +++ b/installer/README.md @@ -42,6 +42,15 @@ pwsh -File ./Prepare-PackageFiles.ps1 -TargetVersion 1.2.3 -RepoRoot .. ` - 安装器在数据目录里放一个 `.metasequoiaime-data` 标记文件。覆盖安装的清理和卸载的整目录删除**只在看到这个标记(或目录就是历史默认位置)时才执行**——用户可能把数据目录指到一个本来就有自己文件的文件夹 - 静默安装用 `/DATADIR="D:\MetasequoiaIME"` 指定;该值在 `PrepareToInstall` 里和向导页走同一套校验 +## 运行时依赖检查 + +安装包不携带 WebView2 Runtime 和 VC 运行库——前者有自己的 Evergreen 更新通道,后者是系统级共享组件。但缺了任何一个,输入法装完就是坏的,而故障出现在安装结束之后(Server 起不来,或者候选窗一片空白),用户看不到任何解释。所以 `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 +- **不要改成查 `System32` 里的 DLL。** Setup.exe 是 32 位进程,Pascal Script 的 `FileExists` 会被 WOW64 重定向到 `SysWOW64`,查到的是 x86 运行库;只装了 x64 redist 的机器会被误判成缺少 x64 运行库。绕开重定向的办法不可移植:`EnableFsRedirection` 在 Inno Setup 7 里已被移除,而它的替代品在 CI 用的 6.x 里还不存在 +- 缺组件时弹一次对话框:选「是」打开下载页并结束安装,选「否」继续装。**不阻断安装**——用户可能马上就要去补组件,也可能只是在准备环境。静默安装(CI、批量部署)用 `SuppressibleMsgBox` 默认「否」直接继续,缺什么已经写进安装日志(`/LOG=`) + ## 依赖 - Windows 10/11,PowerShell 7(`pwsh`) diff --git a/installer/msime_setup.iss b/installer/msime_setup.iss index 11400af8..7382e2ae 100644 --- a/installer/msime_setup.iss +++ b/installer/msime_setup.iss @@ -171,6 +171,17 @@ const 整目录清理——用户可能把数据目录指到一个本来就有自己文件的文件夹。} DataDirMarkerName = '.metasequoiaime-data'; + { WebView2 Evergreen Runtime 在 EdgeUpdate 里的固定客户端 ID。} + WebView2ClientKey = + 'Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}'; + { 安装器界面本身就是中文(InitializeSetup 在选语言对话框之前跑,拿不到用户选的语言), + 下载页也统一给简体中文版,省得用户落到英文页上再自己切。} + WebView2DownloadUrl = + 'https://developer.microsoft.com/zh-cn/microsoft-edge/webview2'; + 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'; + var VersionDirName: String; DataDirValue: String; @@ -180,6 +191,139 @@ var CloudCandidatesIndex: Integer; UserConfigExistedBeforeInstall: Boolean; +{ WebView2 Runtime 与 VC 运行库都不随包分发:前者有自己的 Evergreen 更新通道, + 后者是系统级共享组件,安装器不该替用户装。但缺了任何一个,输入法装完就是坏的, + 而故障现场在安装结束之后——Server 直接起不来,或者候选窗一片空白,用户看不到 + 任何解释。所以在安装开始前查一遍,把原因当场说清楚。 + + 返回空串表示没装;否则是 pv 里的版本号。} +function ReadWebView2Version: String; +begin + Result := ''; + { 机器级安装由 32 位的 EdgeUpdate 写入,键落在 WOW6432Node 下。本安装器跑在 + 64 位模式,HKLM 默认就是 64 位视图,所以主查 HKLM32;HKLM64 作为后备, + 以防将来的 ARM64 / 原生 64 位布局换了位置。用户级安装写在 HKCU。 + 卸载没清干净的机器上 pv 可能残留成 0.0.0.0。} + if not RegQueryStringValue(HKLM32, WebView2ClientKey, 'pv', Result) then + if not RegQueryStringValue(HKLM64, WebView2ClientKey, 'pv', Result) then + if not RegQueryStringValue(HKCU, WebView2ClientKey, 'pv', Result) then + Result := ''; + Result := Trim(Result); + if Result = '0.0.0.0' then + Result := ''; +end; + +// 用注册表判定,而不是去 System32 里找 vcruntime140.dll:Setup.exe 是 32 位进程, +// Pascal Script 的 FileExists 会被 WOW64 重定向到 SysWOW64([Files] 和 Exec 里的 +// {sys} 由 Inno 自己处理,脚本函数不管),结果查的是 x86 运行库——只装了 x64 redist +// 的机器会被误判成缺少 x64 运行库。绕开重定向的两个办法都不可移植: +// EnableFsRedirection 在 Inno Setup 7 里已被移除,而它的替代品在 6.x 里还不存在; +// {sysnative} 对 64 位安装器又不可用。注册表访问没有这个问题,HKLM32 / HKLM64 +// 两个视图都是显式指定的。 +// +// x64 redist 的安装状态就记在这个键里。要求 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。 +// 注意:Inno Setup 的花括号注释不能嵌套,上面提到的常量会提前闭合 { } 注释, +// 所以这一段只能用行注释。 +function VCRuntimeInstalledInView(RootKey: Integer): Boolean; +var + Installed: Cardinal; + Major: Cardinal; + Minor: Cardinal; +begin + Result := + RegQueryDWordValue(RootKey, VCRuntimeKey, 'Installed', Installed) and + (Installed = 1) and + RegQueryDWordValue(RootKey, VCRuntimeKey, 'Major', Major) and + RegQueryDWordValue(RootKey, VCRuntimeKey, 'Minor', Minor) and + ((Major > 14) or ((Major = 14) and (Minor >= 20))); +end; + +{ redist 的安装包是 32 位的,键通常落在 WOW6432Node 下,但较新的版本两个视图都写, + 所以两边都查。} +function VCRuntimeIsInstalled: Boolean; +begin + Result := + VCRuntimeInstalledInView(HKLM32) or VCRuntimeInstalledInView(HKLM64); +end; + +{ 只用于写日志,不参与判定。} +function ReadVCRuntimeVersion: String; +begin + Result := ''; + if not RegQueryStringValue(HKLM32, VCRuntimeKey, 'Version', Result) then + if not RegQueryStringValue(HKLM64, VCRuntimeKey, 'Version', Result) then + Result := ''; + Result := Trim(Result); +end; + +procedure OpenDownloadPage(const Url: String); +var + ErrorCode: Integer; +begin + { 安装器是提权进程,用它拉起的浏览器会以管理员身份运行;和 LaunchInstalledComponents + 一样交还给原用户。} + ShellExecAsOriginalUser( + 'open', Url, '', '', SW_SHOWNORMAL, ewNoWait, ErrorCode); +end; + +function InitializeSetup: Boolean; +var + WebView2Version: String; + NeedsWebView2: Boolean; + NeedsVCRuntime: Boolean; + Prompt: String; +begin + Result := True; + + WebView2Version := ReadWebView2Version; + NeedsWebView2 := WebView2Version = ''; + if NeedsWebView2 then + Log('Prerequisite missing: Microsoft Edge WebView2 Runtime.') + else + Log('WebView2 Runtime found: ' + WebView2Version); + + NeedsVCRuntime := not VCRuntimeIsInstalled; + if NeedsVCRuntime then + Log('Prerequisite missing: Visual C++ 2015-2022 Redistributable (x64).') + else + Log('Visual C++ runtime found: ' + ReadVCRuntimeVersion); + + if not (NeedsWebView2 or NeedsVCRuntime) then + exit; + + Prompt := '这台电脑缺少输入法运行所需的系统组件:' + #13#10; + if NeedsVCRuntime then + 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; + if NeedsWebView2 then + Prompt := Prompt + #13#10 + + '● Microsoft Edge WebView2 Runtime' + #13#10 + + ' 缺少它候选窗口、设置页和表情 / 手写 / 键盘面板都打不开。' + #13#10 + + ' 下载:' + WebView2DownloadUrl + #13#10; + Prompt := Prompt + #13#10 + + '点击「是」打开下载页面并结束本次安装,装好组件后重新运行本安装程序;' + #13#10 + + '点击「否」继续安装(装完仍需自行补齐上述组件,输入法才能正常工作)。'; + + { 静默安装(CI、企业批量部署)不该被一个对话框卡住:默认继续,缺什么已经写进日志了。} + if + SuppressibleMsgBox(Prompt, mbConfirmation, MB_YESNO, IDNO) = IDYES + then + begin + if NeedsVCRuntime then + OpenDownloadPage(VCRuntimeDownloadUrl); + if NeedsWebView2 then + OpenDownloadPage(WebView2DownloadUrl); + Result := False; + end; +end; + { 上一次安装(或历史版本)的数据目录。没有注册表值就是历史默认位置。} function ResolvePreviousDataDir: String; var