fix(installer): 定时器分辨率 1.5 秒 → 改用 nsDialogs 定时器(拖不动/进度跳/卡收尾三症状同根)
真机取证:StdUtils.TimerCreate 实测**每 ~1500ms 才跳一次**(真机 tick 计数 7 次/10.7 秒),
且请求 4/20/60/250ms 四个间隔出来的都是 1500ms 且完全同步 —— 等于与请求值无关的低分辨率实现。
上一版拿它做 60ms 轮询,于是:
- **窗口拖不动**:1.5 秒才采一次光标,而人手一次拖(按下→移动→松开)不到 1.5 秒,
永远采不到「左键按下」那一刻。判别性实验(wine,人手速度 ~0.6 秒):旧代码 dx=0/dy=0,
新代码 dx=200/dy=100。(上一轮 wine 测的是 6 秒慢速拖,慢到 1.5s 的 tick 也能采到,所以漏了。)
- **进度只显示 0% 与 100%**:真因不是 tick,而是分段耗时极不均匀(几乎全在解压,后 4 段每段 <1ms)
→ 每段加 400ms 下限,真机 trace 407/484/656ms,肉眼可见。
- **装完卡在「收尾」**:翻页那次 BM_CLICK 只发一次,丢了就永远停在第 4 页 → 改成装完后每 tick 重试
($instDone 门防止安装途中的 Sleep 泵出 tick 乱点)。
- 顺带修好 P5「完成」页:定时器改为每页重建,P5 有独立一份,悬停与拖动都恢复。
改法:${StdUtils.TimerCreate} → ${NSD_CreateTimer}(挂在当前页对话框上,每页 NewPage 重建、
KillTick 里 ${NSD_KillTimer},删掉 $timerId);Stage 加 STAGE_DWELL 400 + GetTickCount 补 Sleep。
真机验证:每页 tick ≈63ms 且 P1..P5 都活着;分段停留 ≥400ms;P4 自动翻到完成页;
静默 /S exit=0 / 7.8s、旧版在跑时 exit=0 / 10.5s 且 running_after=0。
wine 回归:人手速度拖动、原生控件隐藏、P3 翻页、✕ 退出无 StdUtils 报错、/S /currentuser /D= 正常。
真机无输入桌面,鼠标驱动的悬停/拖动只能靠 wine 几何证据 + 真机 tick 频率证据。
This commit is contained in:
1 parent
f09cb07a23
commit
b1c6d5d54a
1 file changed
+48
-21
@@ -81,6 +81,9 @@
|
||||
!define STAGE_Y 156
|
||||
!define STAGE_W 456
|
||||
!define STAGE_H 18
|
||||
; 每一段进度(stage-N.bmp)至少显示多久(毫秒):见 Stage 里的注释 ——
|
||||
; 耗时几乎全在「解压」那一段,不给个下限的话后几段肉眼根本看不见。
|
||||
!define STAGE_DWELL 400
|
||||
!define PROG_X 272
|
||||
!define PROG_Y 180
|
||||
!define PROG_W 456
|
||||
@@ -133,8 +136,8 @@ Var stCancel
|
||||
Var stBrowse
|
||||
Var stFinish
|
||||
Var uiPage ; 0=无页面,1..5=当前页;定时器靠它判断还能碰哪些控件
|
||||
Var timerId
|
||||
Var timerOn ; 定时器是否已经创建过(销毁时只认这个哨兵,不靠 id 猜)
|
||||
Var timerOn ; tick 定时器当前是不是活的(建/销毁都认这个哨兵,幂等)
|
||||
Var stageT0 ; 上一段进度的时刻(GetTickCount),用来保证每段都看得见
|
||||
Var gDpi ; 系统 DPI(LOGPIXELSX)
|
||||
Var gArt ; 本次要用的素材目录($PLUGINSDIR\1x 或 \2x)
|
||||
Var gCx
|
||||
@@ -149,6 +152,7 @@ Var licOk ; 许可页勾选
|
||||
Var runApp ; 完成页「立即运行」
|
||||
Var modeAll ; 安装模式单选:1=为所有用户,0=仅为我
|
||||
Var instStarted ; 安装流程是否已经跑起来(一次性,见 DoInstall 开头的守卫)
|
||||
Var instDone ; 安装流程是否已经跑完(tick 里据此才去点「下一步」翻到完成页)
|
||||
Var gAdminDbg ; CheckAdmin 的原始值(判定为非提权时写进 %TEMP% 便于现场定位)
|
||||
Var gIsAdmin ; CheckAdmin 的结果:1 = 当前进程已提权
|
||||
Var dragCx ; 拖动锚点:按下瞬间的光标屏幕坐标
|
||||
@@ -843,6 +847,12 @@ Function Tick
|
||||
; 定时器不触发)。改成自绘页 + 定时器驱动,消息泵一直是通的。
|
||||
${If} $instStarted == 0
|
||||
Call DoInstall
|
||||
${ElseIf} $instDone == 1
|
||||
; 安装已经跑完,但页面还停在「正在安装」(真机复验:卡在最后一段不翻页)。
|
||||
; 翻页靠点被藏起来的「下一步」,这一下有可能没生效,所以每个 tick 重试一次,
|
||||
; 直到页面真的不再是这一页 —— 翻页成功后 $uiPage 立刻变了,不会多点到下一页去。
|
||||
; ⚠️ 必须等 $instDone:安装途中的 Sleep 会把消息泵转起来,那时候也会跑进 Tick,
|
||||
; 没有这道门就会在装到一半时去点「下一步」。
|
||||
GetDlgItem $0 $HWNDPARENT 1
|
||||
SendMessage $0 ${BM_CLICK} 0 0
|
||||
${EndIf}
|
||||
@@ -975,14 +985,15 @@ Function NewPage
|
||||
${If} $hPage == error
|
||||
Abort
|
||||
${EndIf}
|
||||
; 悬停/拖动全靠这个 60ms 轮询(POC 结论:比窗口子类化稳,代价是光标移动要等一个 tick)。
|
||||
; 建在页面里而不是 .onGUIInit:StdUtils 的定时器要挂在页对话框上才跑得起来。
|
||||
; ⚠️ 全程只建一个:它挂在 StdUtils 的隐藏窗口上,不随页面消失,而 NewPage 每页都会跑 ——
|
||||
; 不加哨兵就会越翻页越多(越跑越卡,退出时还会漏掉没销毁的那些)。
|
||||
${If} $timerOn == 0
|
||||
StrCpy $timerOn 1
|
||||
${StdUtils.TimerCreate} $timerId Tick 60
|
||||
${EndIf}
|
||||
; 悬停/拖动/阶段刷新全靠这个 tick 轮询(POC 结论:比窗口子类化稳,代价是光标移动要等一个 tick)。
|
||||
; ⚠️ 定时器用 nsDialogs 自带的 ${NSD_CreateTimer},**不要**用 StdUtils::TimerCreate:
|
||||
; 真机实测(构建机 aeiva)StdUtils 那个不管请求 4ms / 20ms / 60ms / 250ms,
|
||||
; 一律按 ~1500ms 的心跳跑(几个定时器还完全同步),wine 里也一样 ——
|
||||
; 后果就是「拖不动窗口」(人手一次拖动不到 1.5 秒,tick 根本采不到按下状态)、
|
||||
; 悬停要一秒多才反应。nsDialogs 的定时器实测就是请求的 60ms。
|
||||
; 它挂在**当前页的内层对话框**上(页没了定时器也没了),所以每页(=每次 NewPage)都要建一次。
|
||||
${NSD_CreateTimer} Tick 60
|
||||
StrCpy $timerOn 1
|
||||
Push ${WIN_W}
|
||||
Call S
|
||||
Pop $R0
|
||||
@@ -1248,6 +1259,18 @@ Function Stage
|
||||
${If} $uiPage != 4
|
||||
Return
|
||||
${EndIf}
|
||||
; 每一段至少显示 ${STAGE_DWELL} 毫秒再换下一段。
|
||||
; 真机复验原话:「进度条只显示 0% 和 100%,中间的输出会在几帧内突然跳完」——
|
||||
; 因为耗时几乎全在「解压」那一段,剩下几段每段不到 1 毫秒,肉眼只能看到头和尾。
|
||||
; 段本身是真做过的,这里只是保证它**看得见**(四段加起来最多多花 1.6 秒)。
|
||||
System::Call 'kernel32::GetTickCount() i .R1'
|
||||
IntOp $R1 $R1 - $stageT0
|
||||
${If} $R1 < ${STAGE_DWELL}
|
||||
IntOp $R1 ${STAGE_DWELL} - $R1
|
||||
Sleep $R1
|
||||
${EndIf}
|
||||
System::Call 'kernel32::GetTickCount() i .R1'
|
||||
StrCpy $stageT0 $R1
|
||||
; 段号先搬进全局变量:SetBmp 会踩掉 $R0..$R7(它的局部值都放那儿)
|
||||
StrCpy $tmpTxt $R0
|
||||
StrCpy $tmpPath "stage-$tmpTxt.bmp"
|
||||
@@ -1504,29 +1527,29 @@ FunctionEnd
|
||||
; 逐项对照 electron-builder 的 templates/nsis/installer.nsi,只去掉 setInstallSectionSpaceRequired
|
||||
; (那是给组件页算体积用的,我们没有组件页)。
|
||||
; 安装器 GUI 结束时收尾(正常点「完成」退出、以及中途关窗都走这里)。
|
||||
; 不销毁的话 StdUtils 插件在卸载时会弹「Plug-in unloaded before all timers were destroyed」。
|
||||
; 只认哨兵 $timerOn:对没建过 / 已销毁的 id 调 TimerDestroy 本身也是一种报错源。
|
||||
Function AbortCleanup
|
||||
; 定时器是 nsDialogs 建在当前页对话框上的,页被销毁时它自己也随窗口没了;
|
||||
; 这里显式再收一次只是为了「页面还在、窗口要关」的那条路,靠 $timerOn 幂等,重复调用无害。
|
||||
Function KillTick
|
||||
${If} $timerOn == 1
|
||||
StrCpy $timerOn 0
|
||||
${StdUtils.TimerDestroy} $R0 $timerId
|
||||
StrCpy $timerId 0
|
||||
${NSD_KillTimer} Tick
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
Function AbortCleanup
|
||||
Call KillTick
|
||||
FunctionEnd
|
||||
|
||||
Function .onGUIEnd
|
||||
${If} $timerOn == 1
|
||||
StrCpy $timerOn 0
|
||||
${StdUtils.TimerDestroy} $R0 $timerId
|
||||
StrCpy $timerId 0
|
||||
${EndIf}
|
||||
Call KillTick
|
||||
FunctionEnd
|
||||
|
||||
Function .onInit
|
||||
StrCpy $timerId 0
|
||||
StrCpy $timerOn 0
|
||||
StrCpy $timerOn 0
|
||||
StrCpy $uiPage 0
|
||||
StrCpy $instStarted 0
|
||||
StrCpy $instDone 0
|
||||
SetOutPath $INSTDIR
|
||||
${LogSet} on
|
||||
!insertmacro check64BitAndSetRegView
|
||||
@@ -1595,6 +1618,9 @@ Function DoInstall
|
||||
Return
|
||||
${EndIf}
|
||||
StrCpy $instStarted 1
|
||||
; 进度分段的时间基准:第一段从开工这一刻算起,保证 stage-1 也至少显示 STAGE_DWELL 毫秒
|
||||
System::Call 'kernel32::GetTickCount() i .R0'
|
||||
StrCpy $stageT0 $R0
|
||||
|
||||
; 静默安装没有 UI 可选安装模式,per-machine 这一趟要在这里补提权。
|
||||
; 比上游多一个 $installMode == "all" 的条件:上游只在「已有 per-machine 安装」时提权,
|
||||
@@ -1694,6 +1720,7 @@ Function DoInstall
|
||||
!insertmacro customInstall
|
||||
!endif
|
||||
!insertmacro StageCall 5
|
||||
StrCpy $instDone 1
|
||||
|
||||
; 真卸载器在这里现写。先删掉 installApplicationFiles 落下来的那个占位文件,
|
||||
; 免得「万一没写成功、$INSTDIR 里却留着一个假卸载器」这种最坏情况。
|
||||
|
||||
Reference in new issue
Block a user